day13【线程池、死锁、线程状态、等待与唤醒】
反馈和复习
1.课上笔记错别字很多,注意一下
2.昨天基本没问题,就是搞混了
3.单词不会读(百度)
4.课间放歌太好听
1.synchronized关键字
a.作用: 控制多行代码的原子性
b.用法:
同步代码块:
synchronized(锁对象){
需要同步的代码(需要保证原子性的代码)
}
同步方法:
public synchronized void 方法名(){
需要同步的代码(需要保证原子性的代码)
}
Lock锁:
Lock lock = new ReentrantLock();
lock.lock();
需要同步的代码(需要保证原子性的代码)
lock.unlock();
2.各种高并发情况下使用的线程安全的类
ArrayList线程不安全-->CopyOnWriteArrayList线程安全
HashSet线程不安全 --> CopyOnWriteArraySet线程安全
HashMap线程不安全--> HashTable(全局锁,性能较低),线程安全
ConcurrentHashMap(局部锁+CAS,性能较高)线程安全
Semaphore: 控制线程最多的并发数量
CountDownLatch: 可以运行一个线程等待另外一个线程执行完毕后再继续执行
CyclicBarrier: 可以让多个线程到达某种条件之后,再执行其他任务(五个人都到了,再开会)
Exchanger: 用于两个线程之间数据交换
今日内容
1.线程池【重点】
2.死锁【了解】
3.线程的状态【重点也是难点,也是面试中经常会问的】
4.定时器【重点,一共有四种不同的定时器任务】
第一章 线程池
5.1 线程池的思想
我们需要使用线程时,我们会临时创建一个线程,然后启动,而线程的创建,以及线程使用完毕之后的销毁,都是需要消耗性能的.
思考: 能不能事先把线程对象创建好,然后需要用到时直接把创建好的线程拿过来使用,
使用完毕,重新归还,等待下次继续使用???
能! 其实这就是线程池的思想
5.2 线程池介绍
线程池: 其实就是一个保存多个线程对象的容器,其中的线程可以反复使用,节省了创建和销毁的所消耗性能
5.3 线程池的使用
线程池的顶层接口:
java.util.concurrent.Executor
线程池的子接口:
java.util.concurrent.ExecutorService
线程池有一个工具类:其作用是帮助我们创建一个线程池对象
java.util.concurrent.Executors
工具类中静态方法:创建一个线程池对象
public static ExecutorService newFixedThreadPool(int nThreads);
创建一个具有指定线程个数的线程池对象
如何向线程池中提交任务呢??
调用ExecutorService接口中规定的方法:
public Future<?> submit(Runnable task); 向线程池中提交无返回值的任务
public Future<T> submit(Callable<T> task);向线程池中提交有返回值的任务,返回Future类型,
表示返回了封装线程执行完毕之后结果的那个对象
5.4 线程池的练习
public class TestThreadPoolDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建一个线程池,使用多态接收
ExecutorService service = Executors.newFixedThreadPool(3);
//2.向线程池中提交 无返回值 任务
// for (int i = 0; i < 10; i++) {
// service.submit(new Runnable() {
// @Override
// public void run() {
// System.out.println(Thread.currentThread().getName()+"执行了....");//pool-1-thread-1
// }
// });
// }
//3.向线程池中提交 有返回值 任务
//提交
Future<Integer> future = service.submit(new Callable<Integer>(){
@Override
public Integer call() {
//求1-100的和任务
int sum = 0;
for (int i = 1; i < 101; i++) {
sum+=i;
}
return sum;
}
});
//从future中取出结果
Integer result = future.get();// 因为方法具有阻塞功能,会等待任务直接完毕之后再返回结果
System.out.println("result = " + result);
//如果想要整个进程停止
//那么需要关闭线程池
service.shutdown();
}
}
第二章 死锁
6.1 什么是死锁
在多线程中有多把锁,最后导致所有都在等待,造成的现象称为死锁
6.2 产生死锁的条件
a.至少有2个线程
b.至少有2个锁对象
c.必须有synchronized的嵌套
6.3 死锁演示
public class TestDeadLock {
public static void main(String[] args) {
//1.先创建2把锁对象
Object obj1 = new Object();
Object obj2 = new Object();
//2.再创建2个线程
new Thread(new Runnable() {
@Override
public void run() {
//3.必须有synchronized嵌套
while (true){
synchronized (obj1){
System.out.println("线程1抢到了obj1,还需要抢obj2..");
synchronized (obj2){
System.out.println("线程1抢到了obj2,那么可以执行了...");
System.out.println(Thread.currentThread().getName()+"执行了..");
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
//4.必须有synchronized嵌套
while (true){
synchronized (obj2){
System.out.println("线程2抢到obj2,还需要抢obj1...");
synchronized (obj1){
System.out.println("线程2抢到obj1,那么可以执行了...");
System.out.println(Thread.currentThread().getName()+"执行了....");
}
}
}
}
}).start();
}
}
注意:如果出现了死锁怎么办??
无解!! 我们只能事先尽量避免死锁
第三章 线程的状态【重点,面试常问】
1.线程的六种状态
-
新建状态(New)
刚刚创建的且未调用start方法的线程
-
可运行状态(Runnable)
处于新建状态的线程调用了start方法之后 注意:只有新建状态的线程才能调用start()
-
受(锁)阻塞状态(Blocked)
线程运行的过程中遇到了同步方法,同步代码块,Lock锁,但是锁已经被其他线程持有了
-
限时等待状态(Timed_waiting)
线程执行到代码Thread.sleep(毫秒值),线程就处于限时等待状态
-
无限等待状态(Waiting)
-
线程如何进入Waiting(无线等待状态)
a.该线程必须先持有锁对象 b.调用锁对象的wait方法,进入无限等待 c.进入无限等待之前,会自动释放锁对象
-
其他线程如何唤醒Waiting状态的线程
a.其他线程先持有锁对象(就是进入无限等待线程释放的那个锁对象) b.调用锁对象的notify方法,唤醒无限等待的线程 c.被唤醒的无限等待线程,先进入锁阻塞,直到再次持有锁对象才能进入可运行状态
-
-
消亡状态(Terminated)
当线程的任务执行完毕,此时线程处于消亡状态 注意:处于消亡状态的线程,不能再调用start方法起死回生!
第四章 等待唤醒机制
1.等待唤醒机制(Wait和Notify)
public class TestWaitingDemo {
public static void main(String[] args) throws InterruptedException {
//如何让一个线程进入 无限等待
//1.创建一把锁对象
Object obj = new Object();
//2.创建一个线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj){
//1.进入这个代码块,说明线程A抢到锁了
System.out.println("线程A持有锁对象obj...");
//2.进入无限等待
System.out.println("线程A进入了无限等待了...");
try {
obj.wait(); //进入无限等待之前会自动释放锁对象
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.醒来后会继续执行
System.out.println("线程A从无限等待中醒来了...");
}
}
}).start();
Thread.sleep(2000);
//3.再来一个线程,负责唤醒线程A
new Thread(new Runnable() {
@Override
public void run() {
//1.持有相同的锁对象
synchronized (obj){
System.out.println("线程B持有锁对象obj...");
System.out.println("线程B唤醒了线程A....");
//2.调用锁对象的notify方法
obj.notify();
//3.线程执行一下任务
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}
}).start();
}
}
注意:
a.只有线程进入了无限等待,其他线程调用锁对象.notify()才有作用,否则也可以调用,但是没有任何作用(不会报错)
b.锁对象.notify方法只能唤醒一个线程,具体是哪一个是随机的
c.锁对象.notifyAll方法可以唤醒多个线程,谁抢到锁谁执行
2.生产者与消费者问题(代码演示)
- 需求介绍
需求:
生产者消费者案例
需要两个线程: 线程1包子铺线程,负责生产包子,线程2吃货线程,负责吃包子
如果没有包子,那么吃货线程等待,包子铺线程执行(做包子),做完之后唤醒吃货线程
如果有包子,那么包子铺线程等待,吃货线程执行(吃包子),吃饭之后唤醒包子铺线程
- 画图分析
-
代码实现
public class TestDemo { public static void main(String[] args) { //1.创建一个集合 ArrayList<String> arr = new ArrayList<String>(); arr.add("包子"); //2.创建一个锁 Object obj = new Object(); //3.线程1:包子铺线程 Thread t1 = new Thread(new Runnable() { @Override public void run() { while (true) { //a.同步代码块 synchronized (obj){ //b.判断 if (arr.size() > 0) { //c.有包子,那么无限等待 try { System.out.println("包子铺发现有包子,进入无限等待..."); obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //d.没有包子,做包子 System.out.println("包子铺做了一个包子...."); arr.add("包子"); //e.通知吃货线程 System.out.println("包子铺做完包子,唤醒吃货..."); obj.notify(); } } } }); //4.线程2:吃货线程 Thread t2 = new Thread(new Runnable() { @Override public void run() { while (true) { //a.同步代码块 synchronized (obj){ //b.判断 if (arr.size() == 0) { try { System.out.println("吃货发现没有包子,进入无限等待...."); obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //c.有包子,吃包子 System.out.println("吃货吃了包子..."); arr.remove(0); //d.通知包子铺做 System.out.println("吃货吃完包子,唤醒包子铺..."); obj.notify(); } } } }); //5.启动 t1.start(); t2.start(); } }
第五章 定时器
5.1 什么是定时器
可以让某个线程在某个时间做指定的任务,或者某个时间以后指定的时间间隔中反复做某个任务!!
5.2 定时器Timer的使用
-
构造方法
public Timer():构造一个定时器
-
成员方法
public void schedule(TimerTask task, long delay); //在指定的时间之后执行指定的任务 public void schedule(TimerTask task, long delay, long period);//在指定的时间之后开始周期性的执行任务,周期的时间间隔是period public void schedule(TimerTask task, Date time);//在指定的时间点执行指定的任务 public void schedule(TimerTask task, Date firstTime,long period);//在指定的时间点第一次执行任务,继续周期性执行任务,周期的时间间隔period
-
案例演示
public class TestTimer {
public static void main(String[] args) {
//1.创建一个定时器
Timer timer = new Timer();
//2.任务1
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("任务1执行...");
}
},2000);
//3.任务2
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("任务2执行...");
}
},2000,1000);
//4.任务3
Calendar cc = Calendar.getInstance();
cc.add(Calendar.SECOND,5);
Date date = cc.getTime();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("任务3执行了...");
}
},date);
//5.任务4
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("任务4执行...");
}
},date,1000);
}
}
总结
1.线程池【理解】
a.怎么创建???
ExecutorService service = Executors.newFixedThreadPool(int 线程个数);
b.提交任务
service.submit(Runnable 任务); //提交无返回值任务
Future<T> future = service.submit(Callable<T> 任务);//提交有返回值任务
通过 future.get() 该方法会阻塞,直到线程执行完毕,返回结果
c.关闭线程池
service.shutDown();
2.死锁【了解】
a.多个线程
b.多把锁
c.嵌套获取锁
死锁只能尽量避免
3.线程的状态(等待和唤醒机制) 【掌握】
a.NEW(新建状态)
b.RUNNABLE(可运行状态)
c.TERMINATED(消亡状态)
d.BLOCKED(锁阻塞状态)
e.TIMED_WAITING(限时等待状态)
f.WAITING(无限等待状态)
怎么进入WAITING状态??
a.当前线程获取锁对象
b.调用锁对象.wait()方法
c.进入WAITING之前自动释放锁对象
其他线程怎么唤醒WAITING的线程??
a.其他线程持有锁对象
b.调用锁对象.notify()方法
c.WAITING的线程就会醒来,先进入BLOCKED状态,直到再次获取到锁对象
需要练习两个相关案例demo05和demo06
4.Timer【理解】
四个方法
public void schedule(TimerTask task, long delay);
public void schedule(TimerTask task, long delay, long period);
public void schedule(TimerTask task, Date time);
public void schedule(TimerTask task, Date firstTime,long period);