1. 进程与线程(Process与Thread)
程序:指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念 。 进程:是执行程序的一次执行过程,是一个动态的概念 。是系统资源分配的单位。 一个进程可以包含有多个线程,一个进程至少 有一个线程,否则无存在的意义。 线程:CPU调度和执行的单位 。
多线程就是分时利用CPU,宏观上让所有线程一起执行 ,也叫并发。
核心概念
线程就是独立的执行路径;
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
main()称为主线程,为系统的入口,用于执行整个程序;
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为干预的;
对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制;
线程会带来额外的开销,如CPU调度时间,并发控制开销;
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
2. 线程创建(重点)
1)、继承Thread类
a.创建Thread类 b.重写run()方法 c.调用start()开启线程
public class Test01 extends Thread { @Override public void run () { for (int i = 0 ; i <20 ; i++) { System.out.println("我在看代码" ); } } public static void main (String[] args) { Test01 t= new Test01(); t.start(); for (int i = 0 ; i < 2000 ; i++) { System.out.println(“我在学多线程"+i); } } }
总结:线程开启不一定立即执行,由CPU调度执行
2)、实现Runnable接口
a.实现Runnable接口 b.重写run()方法 c.创建线程对象,调用start()方法启动线程
public class Test01 implements Runnable { @Override public void run () { for (int i = 0 ; i <20 ; i++) { System.out.println("我在看代码" ); } } public static void main (String[] args) { Test01 t= new Test01(); new Thread(t).start(); for (int i = 0 ; i < 2000 ; i++) { System.out.println(“我在学多线程"+i); } } }
小结
以上两者区别
继承Thread类:
子类继承Thread类具备多线程能力
启动线程:子类对象.start()
不建议使用:避免OOP单继承局限性
实现Runnable接口
实现Runnable接口具备多线程能力
启动线程:new Thread(子类对象).start()
推荐使用 :避免单继承局限性,方便同一个对象被多个线程使用
练习:买票
public class TestTread implements Runnable { private int ticketNums = 10 ; @Override public void run () { while (true ){ if (ticketNums<=0 ){ break ; } try { Thread.sleep(200 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-->拿到了第" +ticketNums-- +"票" ); } } public static void main (String[] args) { TestTread ticket = new TestTread(); new Thread(ticket,"小明" ).start(); new Thread(ticket,"老师" ).start(); new Thread(ticket,"黄牛" ).start(); } }
发现问题:不同的人拿到了同一张票。多个线程操作同一资源的情况下,线程不安全,数据紊乱。
3)、实现Callable接口(了解即可)
3. 静态代理
——多线程底层实现原理 例子: 真实角色:你; 代理角色:婚庆公司; 结婚:实现结婚接口
public class StaticProxy { public static void main (String[] args) { WeddingCompany company = new WeddingCompany(new You()); company.happyMarry(); } } interface Marry { void happyMarry () ; } class You implements Marry { @Override public void happyMarry () { System.out.println("结婚了真开心!" ); } } class WeddingCompany implements Marry { private Marry target; public WeddingCompany (Marry target) { this .target = target; } @Override public void happyMarry () { before(); this .target.happyMarry(); after(); } private void after () { System.out.println("结婚之后,收尾款" ); } private void before () { System.out.println("结婚前,布置现场" ); } }
总结:
静态代理有如下要素: 1.目标角色(真实角色)。 2.代理角色。 3.目标角色和代理角色实现同一接口。 4.代理角色持有目标角色的引用。
优点 :代理对象可以做很多真实对象不能做的事情, 真实对象专注自己的事情
结合线程来看: 线程体(也就是我们要执行的具体任务)实现了Runnable接口和run方法。 同时Thread类也实现了Runnable接口。此时,线程体就相当于目标角色,Thread就相当于代理角色。当程序调用了Thread的start()方法后,Thread的run()方法会在某个特定的时候被调用。 Thread类中对Runnable接口的run方法的实现:
@Override public void run () { if (target != null ) { target.run(); } }
start()与run()方法的区别:
start:用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
run: run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:调用start方法方可启动线程,真正实现了多线程运行。而run方法只是thread的一个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。
4. Lambda表达式
1.作用
避免匿名内部类定义过多
使代码看起来简介
简化代码,只留下核心逻辑
2.函数式接口
定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
3.lambda简单推导
public class TestLambda { static class Like2 implements Ilike { @Override public void lambda () { System.out.println("i like lambda2" ); } } public static void main (String[] args) { Ilike like = new Like(); like.lambda(); like = new Like2(); like.lambda(); class Like3 implements Ilike { @Override public void lambda () { System.out.println("i like lambda4" ); } } like = new Like3(); like.lambda(); like = new Ilike() { @Override public void lambda () { System.out.println("i like lambda5" ); } }; like.lambda(); like = ()->{ System.out.println("i like lambda5" ); }; like.lambda(); } } interface Ilike { void lambda () ; } class Like implements Ilike { @Override public void lambda () { System.out.println("i like lambda" ); } }
总结:
5. 线程状态
1、线程状态
2、线程停止的方法
建议线程正常停止(自己停下来)——>利用次数,不建议死循环
建议使用标志位进行终止变量——>设置一个标志位flag,当flag = false,则终止线程运行。
不要使用stop或者destroy等过时或者JDK不建议使用的方法
public class TestStop implements Runnable { private boolean flag = true ; @Override public void run () { int i = 0 ; while (flag){ System.out.println("run...Thread" + i++); } } public void stop () { this .flag = false ; } public static void main (String[] args) { TestStop stop = new TestStop(); new Thread(stop).start(); for (int i = 0 ; i < 1000 ; i++) { System.out.println("main" +i); if (i==900 ){ stop.stop(); System.out.println("线程停止了" ); } } } }
3、线程休眠
leep(时间)指定当前线程阻塞的毫秒数;
sleep存在异常InterruptedException;
sleep时间达到后线程进入就绪状态;
sleep可以模拟网络延时,倒计时等;
每一个对象都有一个锁,sleep不会释放锁。
4、线程礼让 - yield()
礼让线程,让当前正在执行的线程暂停,但不阻塞;
将线程从运行状态转为就绪状态;
让CPU重新调度,礼让不一定成功 ,看CPU心情。
public class TestYield { public static void main (String[] args) { MyYield myYield = new MyYield(); new Thread(myYield,"a" ).start(); new Thread(myYield,"b" ).start(); } } class MyYield implements Runnable { @Override public void run () { System.out.println(Thread.currentThread().getName()+"-->START" ); Thread.yield(); System.out.println(Thread.currentThread().getName()+"-->END" ); } }
不进行礼让(或礼让但失败了):
进行礼让且礼让成功:
5、线程强制执行 - join()
join合并线程,待此线程执行完后,再执行其它线程,其它线程阻塞;
可以想象成插队
public class TestJoin implements Runnable { @Override public void run () { for (int i = 0 ; i < 100 ; i++) { System.out.println("线程VIP来了" +i); } } public static void main (String[] args) throws InterruptedException { TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); for (int i = 0 ; i < 1000 ; i++) { if (i == 200 ){ thread.start(); thread.join(); } System.out.println("main" +i); } } }
测试结果:
线程插队,执行完(打印100次)之后再执行主线程
6、观测线程状态
public class TestState { public static void main (String[] args) throws InterruptedException { Thread thread = new Thread(()->{ for (int i = 0 ; i < 5 ; i++) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("///" ); }); Thread.State state = thread.getState(); System.out.println(state); thread.start(); state = thread.getState(); System.out.println(); while (state != Thread.State.TERMINATED){ Thread.sleep(100 ); state = thread.getState(); System.out.println(state); } } }
测试结果:
7、线程的优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级来决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1~10.
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
使用一下方法改变或获取优先级
getPriority.setPriority(int xxx)
public class TestPriority { public static void main (String[] args) { System.out.println(Thread.currentThread().getName()+"-->" +Thread.currentThread().getPriority()); MyPriority myPriority = new MyPriority(); Thread t1 = new Thread(myPriority); Thread t2 = new Thread(myPriority); Thread t3 = new Thread(myPriority); Thread t4 = new Thread(myPriority); Thread t5 = new Thread(myPriority); Thread t6 = new Thread(myPriority); t1.start(); t2.setPriority(1 ); t2.start(); t3.setPriority(4 ); t3.start(); t4.setPriority(Thread.MAX_PRIORITY); t4.start(); } } class MyPriority implements Runnable { @Override public void run () { System.out.println(Thread.currentThread().getName()+"-->" +Thread.currentThread().getPriority()); } }
测试结果: 总结 :优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了。这都看CPU的调度。
8、守护线程 - setDaemon(true)
线程分为用户线程 和守护线程
虚拟机必须确保用户线程执行完毕 (如,main)
虚拟机不用等待守护线程执行完毕 (如,后台记录操作日志,监控内存,垃圾回收……)
public class TestDaemon { public static void main (String[] args) { God god = new God(); You you = new You(); Thread thread = new Thread(god); thread.setDaemon(true ); thread.start(); new Thread(you).start(); } } class God implements Runnable { @Override public void run () { while (true ){ System.out.println("上帝保佑你" ); } } } class You implements Runnable { @Override public void run () { for (int i = 0 ; i < 36500 ; i++) { System.out.println("你一生都开心的活着" ); } System.out.println("=====GoodBye World!======" ); } }
测试结果:You的线程执行完毕,虚拟机即可进行GC。
6. 线程同步(重点、难点)
6.1 线程同步
并发:同一个对象 被多个线程 同时操作 (如:上万人同时抢票)
处理多线程问题时,多线程访问同一个对象(并发),并且某些线程还想修改这个对象。这时则需要线程同步。
线程同步 其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池 形成队列,等待前面的线程使用完毕,下一个线程再使用。
线程同步的形成条件:==队列+锁==(每个对象都有一把锁)
6.2 三大不安全案例
(1)买票
public class UnsafeBuyTicket implements Runnable { private int ticketNums = 10 ; boolean flag = true ; @Override public void run () { while (flag){ buy(); } } public void buy () { if (ticketNums<=0 ){ flag = false ; return ; } try { Thread.sleep(200 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"-->拿到了第" +ticketNums-- +"票" ); } public static void main (String[] args) { UnsafeBuyTicket ticket = new UnsafeBuyTicket(); new Thread(ticket,"小明" ).start(); new Thread(ticket,"老师" ).start(); new Thread(ticket,"黄牛" ).start(); } }
结果出现负数
(2)不安全的银行
public class UnsafeBank { public static void main (String[] args) { Account account = new Account(100 ,"基金" ); Drawing you = new Drawing(account,50 ,"你" ); Drawing wo = new Drawing(account,100 ,"我" ); you.start(); wo.start(); } } class Account { int money; String name; public Account (int money, String name) { this .money = money; this .name = name; } } class Drawing extends Thread { Account account; int drawingMoney; int nowMoney; public Drawing (Account account, int drawingMoney, String name) { super (name); this .account = account; this .drawingMoney = drawingMoney; } @Override public void run () { if (account.money-drawingMoney<0 ){ System.out.println(Thread.currentThread().getName()+"钱不够,取不了" ); return ; } account.money = account.money - drawingMoney; nowMoney = nowMoney + drawingMoney; System.out.println(account.name+"余额为:" +account.money); System.out.println(this .getName()+"手里的钱:" +nowMoney); } }
测试结果:
基金余额为:100 你手里的钱:50 我钱不够,取不了
添加休眠后,you线程暂停,wo线程执行。放大了问题的发生性。 此时测试结果:
基金余额为:50 你手里的钱:50 基金余额为:-50 我手里的钱:100
两个人都在余额100时取钱,线程不安全。
(3)不安全的集合
public class UnsafeList { public static void main (String[] args) throws InterruptedException { List<String> list = new ArrayList<String>(); for (int i = 0 ; i < 10000 ; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } Thread.sleep(3000 ); System.out.println(list.size()); } }
6.3 同步方法和同步方法块
同步方法
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:==synchronized方法和synchronized块==
同步方法: public synchronized void method(int args) }
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
缺陷:若将一个大的方法申明为synchronized将会影响效率
方法里需要修改的内容才需要锁,否则浪费资源
对于刚才买票的例子:
public class UnsafeBuyTicket implements Runnable { private int ticketNums = 10 ; boolean flag = true ; @Override public void run () { while (flag){ try { buy(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void buy () throws InterruptedException { if (ticketNums<=0 ){ flag = false ; return ; } Thread.sleep(200 ); System.out.println(Thread.currentThread().getName()+"-->拿到了第" +ticketNums-- +"票" ); } public static void main (String[] args) { UnsafeBuyTicket ticket = new UnsafeBuyTicket(); new Thread(ticket,"小明" ).start(); new Thread(ticket,"老师" ).start(); new Thread(ticket,"黄牛" ).start(); } }
测试结果:当前这个人买到票才释放锁,才能再买票
同步代码块
同步块: synchronized ``(Obj){}
Obj称之为同步监视器
Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this , 就是这个对象本身,或者是class [反射中讲解]
同步监视器的执行过程
1.第一个线程访问,锁定同步监视器,执行其中代码. 2.第二个线程访问,发现同步监视器被锁定,无法访问. 3.第一个线程访问完毕,解锁同步监视器 4.第二个线程访问, 发现同步监视器没有锁,然后锁定并访问 对于银行取钱的例子:
public class UnsafeBank { public static void main (String[] args) { Account account = new Account(100 ,"基金" ); Drawing you = new Drawing(account,50 ,"你" ); Drawing wo = new Drawing(account,100 ,"我" ); you.start(); wo.start(); } } class Account { int money; String name; public Account (int money, String name) { this .money = money; this .name = name; } } class Drawing extends Thread { Account account; int drawingMoney; int nowMoney; public Drawing (Account account, int drawingMoney, String name) { super (name); this .account = account; this .drawingMoney = drawingMoney; } @Override public void run () { synchronized (account){ if (account.money-drawingMoney<0 ){ System.out.println(Thread.currentThread().getName()+"钱不够,取不了" ); return ; } try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } account.money = account.money - drawingMoney; nowMoney = nowMoney + drawingMoney; System.out.println(account.name+"余额为:" +account.money); System.out.println(this .getName()+"手里的钱:" +nowMoney); } } }
测试结果: 如果使用同步方法,锁的是this,是对象本身(银行),对于改变的用户没有用。锁的对象就是变化的量,需要增删改的量。
对于集合的例子:
public class UnsafeList { public static void main (String[] args) throws InterruptedException { List<String> list = new ArrayList<String>(); for (int i = 0 ; i < 10000 ; i++) { new Thread(()->{ synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); } Thread.sleep(3000 ); System.out.println(list.size()); } }
7. 死锁
1、定义
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。 某一个同步块同时拥有“两个以上对象的锁” 时,就可能会发生“死锁”的问题。
public class DeadLock { public static void main (String[] args) { Makeup m1 = new Makeup(0 ,"灰姑凉" ); Makeup m2 = new Makeup(1 ,"白雪公主" ); m1.start(); m2.start(); } } class Lipstick {} class Mirror {} class Makeup extends Thread { static Lipstick lipstick = new Lipstick(); static Mirror mirror = new Mirror(); int choice; String name; public Makeup (int choice, String name) { this .choice = choice; this .name = name; } @Override public void run () { try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } private void makeup () throws InterruptedException { if (choice == 0 ){ synchronized (lipstick){ System.out.println(this .name+"获得口红的锁" ); Thread.sleep(1000 ); synchronized (mirror){ System.out.println(this .name+"获得镜子的锁" ); } } }else { synchronized (mirror){ System.out.println(this .name+"获得镜子的锁" ); Thread.sleep(2000 ); synchronized (lipstick) { System.out.println(this .name + "获得口红的锁" ); } } } } }
执行结果: 将程序修改一下,把同步代码块拿出来,两个人不抱着对方的锁就好了。
if (choice == 0 ){ synchronized (lipstick){ System.out.println(this .name+"获得口红的锁" ); Thread.sleep(1000 ); } synchronized (mirror){ System.out.println(this .name+"获得镜子的锁" ); } }else { synchronized (mirror){ System.out.println(this .name+"获得镜子的锁" ); Thread.sleep(2000 ); } synchronized (lipstick) { System.out.println(this .name + "获得口红的锁" ); } }
执行结果:正常结束。
2.死锁避免方法
产生死锁的四个必要条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在未使用完之前不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
只要破坏其中一个。就可以避免死锁。
8. Lock锁
从JDK 5.0开始,Java提供了更强大的线程同步机制—— 通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
ReentrantLock类(可重入锁 )实现了Lock ,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
语法:
举例,买票:
class TestLock2 implements Runnable { int ticketNums = 10 ; private final ReentrantLock lock = new ReentrantLock(); @Override public void run () { while (true ) { try { lock.lock(); if (ticketNums > 0 ) { try { Thread.sleep(200 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums-- + "票" ); } else { break ; } }finally { lock.unlock(); } } } }
synchronized与Lock对比
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁, 出了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源) >同步方法(在方法体之外)
9. 线程通信—生产者消费者模式
(1)应用场景:生产者和消费者问题 ◆ 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。 ◆ 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。 ◆ 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。 (2)分析: 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。 ◆ 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后又需要马上通知消费者消费。 ◆ 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。 ◆ 在生产者消费者问题中,仅有synchronized是不够的 ◆ synchronized可阻止并发更新同一个共享资源,实现了同步 ◆ synchronized不能用来实现不同线程之间的消息传递(通信) (3)解决方式: 并发协作模型“生产者/消费者模式”–>
a.管程法
◆生产者:负责生产数据的模块(可能是方法,对象,线程,进程); ◆消费者:负责处理数据的模块(可能是方法,对象,线程,进程); ◆缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区“ 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据 分析代码,需要四个类:
产品类,这里用鸡举例,每只鸡有编号id
生产者负责生产,也就是把鸡不断放入缓冲区
消费者负责消费,也就是从缓冲池不断取出鸡
缓冲区,定义容器大小和放入、取出的方法
利用缓冲区容量来设定线程什么时候等待,什么时候唤醒,实现线程通信。
class Productor extends Thread { SynContainer container; public Productor (SynContainer container) { this .container = container; } @Override public void run () { for (int i = 0 ; i < 100 ; i++) { container.push(new Chicken(i)); System.out.println("生产了第" +i+"只鸡" ); } } }
class Consumer extends Thread { SynContainer container; public Consumer (SynContainer container) { this .container = container; } @Override public void run () { for (int i = 0 ; i < 100 ; i++) { System.out.println("消费了----第" +container.pop().id+"只鸡" ); } } }
class Chicken { int id; public Chicken (int id) { this .id = id; } }
class SynContainer { Chicken[] chickens = new Chicken[10 ]; int count = 0 ; public synchronized void push (Chicken chicken) { if (count == chickens.length){ try { this .wait(); } catch (InterruptedException e) { e.printStackTrace(); } } chickens[count] = chicken; count++; this .notifyAll(); } public synchronized Chicken pop () { if (count == 0 ){ try { this .wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count--; Chicken chicken = chickens[count]; this .notifyAll(); return chicken; } }
public class TestPC { public static void main (String[] args) { SynContainer container = new SynContainer(); new Productor(container).start(); new Consumer(container).start(); } }
b.信号灯法,通过标志位** 利用判断标志位来设定线程什么时候等待,什么时候唤醒,实现线程通信。
class Player extends Thread { TV tv; public Player (TV tv) { this .tv = tv; } @Override public void run () { for (int i = 0 ; i < 20 ; i++) { if (i%2 ==0 ){ this .tv.play("快乐大本营" ); }else { this .tv.play("天天向上" ); } } } }
class Watcher extends Thread { TV tv; public Watcher (TV tv) { this .tv = tv; } @Override public void run () { for (int i = 0 ; i < 20 ; i++) { tv.watch(); } } }
class TV { String voice; boolean flag = true ; public synchronized void play (String voice) { if (!flag){ try { this .wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员表演了:" +voice); this .notifyAll(); this .voice = voice; this .flag = !this .flag; } public synchronized void watch () { if (flag){ try { this .wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观看了:" +voice); this .notifyAll(); this .flag = !this .flag; } }
10. 线程池
使用线程池
◆ 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。 ◆ 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。 可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。 ◆ 好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理…
corePoolSize
: 核心池的大小 maximumPoolSize
:最大线程数 keepAliveTime
: 线程没有任务时最多保持多长时间后会终止 ◆ JDK 5.0起提供了线程池相关API: ExecutorService 和Executors ◆ ExecutorService: 真正的线程池接口。常见子类ThreadPoolExecutor void execute(Runnable command)
:执行任务/命令,没有返回值,一般用来执行Runnable Future submit(Callable task)
:执行任务,有返回值,一般用来执行Callable void shutdown()
:关闭连接池 ◆ Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
public class TestPool { public static void main (String[] args) { ExecutorService service = Executors.newFixedThreadPool(10 ); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); service.shutdown(); } } class MyThread implements Runnable { @Override public void run () { System.out.println(Thread.currentThread().getName()); } }
测试结果:
If you get gains,please give a like!