前言:
我们都了解在CPU执行时,它是并发执行的,并不是获取到一个线程必执行完才能执行下一个,而是以抢占的方式来执行的,有时后我们希望CPU按照某种规律来执行,此时就需要各个线程之间的协调通讯。
一、线程间通讯的方式:
在java中为我们提供了不同的方式来实现了线程之间的通讯,来看看具体有哪些方式,可以实现线程之间的通讯;
1、Object中的wait()、notify()、notifyAll():
wait():此方法是让一个线程进入等待状态,可以设置等待的时间也可以不设置,设置后等待具体时间后再次环境,不设置则必须使用其他方法唤醒该线程:
notify():此方法唤醒jvm中等待队列中的某一个线程,让其重新进入就绪状态:
notifyAll():此方法唤醒jvm中所有等待的线程,让其全部进入就绪状态:
例如我们有一个需求,用两个线程输出一个从零开始到十以内数字的奇数和偶数,如果要实现这个需求我们简单地分析一下,例如有两个线程、奇数线程和偶数线程,进入了代码,假设偶数线程获取到了锁,下一步判断此时数字是否是偶数,如果是则执行逻辑,执行完后且数字加一,且唤醒等待的奇数线程,如果不是偶数,则偶数线进入程等待状态,释偶数线程自己获取的锁,这时等待即奇数线程会得到锁,执行代码:
注意:我们要使用wait()、notify()、notifyAll()三个方法,都是依赖synchronized关键字的,必须在其同步代码快中
public class ObjectDemo{ private int number = 0; private Object object = new Object(); //奇数打印方法 public void odd(){ while(number < 10){ synchronized (object){ if(number%2 == 1){ System.out.println("奇数" + number++); object.notify();//唤醒偶数线程 }else{ try { object.wait();//奇数线程等待 } catch (InterruptedException e) { e.printStackTrace(); } } } } } //偶数打印方法 public void event(){ while(number < 10){ synchronized (object){ if(number%2 == 0){ System.out.println("偶数" + number++); object.notify();//唤醒奇数线程 }else{ try { object.wait();//偶数线程等待 } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
测试类:
public class ThreadDemo { public static void main(String[] args) { ObjectDemo demo = new ObjectDemo(); Runnable task1 = demo::odd; Runnable task2 = demo::event; Thread thread1 = new Thread(task1); Thread thread2 = new Thread(task2); thread1.start(); thread2.start(); } }
测试结果:
偶数0
奇数1
偶数2
奇数3
偶数4
奇数5
偶数6
奇数7
偶数8
奇数9
Process finished with exit code 0
2、Condition中的await()、signal()、signalAll():
await():此方法同Object中的wait()方法一样都是让一个线程进入等待状态:
signal():此方法同Object中的notify()方法一样都是唤醒jvm中等待队列中的某一个线程,让其重新进入就绪状态:
notifyAll():此方法同Object中的notifyAll()方法一样都是唤醒jvm中所有等待的线程,让其全部进入就绪状态:
这里我们依旧用上面的例子来演示一下Condition中的方法,用Condition实现多线程输出从零开始到十以内的奇数和偶数:
public class ObjectDemo{ private int number = 0; private Lock lock = new ReentrantLock(true); private Condition condition = lock.newCondition(); //奇数打印方法 public void odd(){ while(number < 10){ try{ lock.lock(); if(number%2 == 1){ System.out.println("奇数" + number++); condition.signalAll(); }else{ try { condition.await();//奇数线程等待 } catch (InterruptedException e) { e.printStackTrace(); } } }finally { lock.unlock(); } } } //偶数打印方法 public void event(){ while(number < 10){ try{ lock.lock(); if(number%2 == 0){ System.out.println("偶数" + number++); condition.signalAll();//唤醒奇数线程 }else{ try { condition.await();//偶数线程等待 } catch (InterruptedException e) { e.printStackTrace(); } } }finally { lock.unlock(); } } } }
测试类:
public class ThreadDemo { public static void main(String[] args) { ObjectDemo demo = new ObjectDemo(); Runnable task1 = demo::odd; Runnable task2 = demo::event; Thread thread1 = new Thread(task1); Thread thread2 = new Thread(task2); thread1.start(); thread2.start(); } }
打印结果:
偶数0
奇数1
偶数2
奇数3
偶数4
奇数5
偶数6
奇数7
偶数8
奇数9
3、CountDownLatch:
CountDownLatch是在java1.5被引入的,存在于java.util.concurrent包下,CountDownLatch能够使一个线程等待其他线程完成任务后再开始执行,是通过一个计数器来实现的,计数器的初始值为线程的数量,用于某个线程线程A等待若干个线程执行完毕后才会执行的场景。
例如:一个班有五个学生,老师组织去夏令营,结束的时候老师必须等待所有同学都离开后才能离开,五个学生看做五条学生线程,老师看做一条老师线程,那么老师线程必须等待五条学生线程执行完毕后才能执行,来看一下这个例子的实现:
public class CountDownLatchDemo { private CountDownLatch latch = new CountDownLatch(5); //学生离开方法 public void studentsLeave(){ try{ System.out.println(Thread.currentThread().getName() + "离开"); }finally { latch.countDown(); } } //老师离开方法 public void teacherLeave(){ try { System.out.println(Thread.currentThread().getName() + "等待学生离开中……"); latch.await(); System.out.println(Thread.currentThread().getName() + "离开"); } catch (InterruptedException e) { e.printStackTrace(); } } }
测试类:
public class ThreadDemo { public static void main(String[] args) { CountDownLatchDemo demo = new CountDownLatchDemo(); Runnable task1 = demo::teacherLeave; Runnable task2 = demo::studentsLeave; Thread teacher = new Thread(task1, "老师"); Thread student1 = new Thread(task2,"学生1"); Thread student2 = new Thread(task2,"学生2"); Thread student3 = new Thread(task2,"学生3"); Thread student4 = new Thread(task2,"学生4"); Thread student5 = new Thread(task2,"学生5"); teacher.start(); student1.start(); student2.start(); student3.start(); student4.start(); student5.start(); } }
打印结果:
老师等待学生离开中……
学生1离开
学生2离开
学生3离开
学生4离开
学生5离开
老师离开
Process finished with exit code 0
4、CyclicBarrier:
CyclicBarrier是在java1.5被引入的,存在于java.util.concurrent包下,CyclicBarrier能够实现一组线程等待至某个状态之后再开始执行,CyclicBarrier底层是基于ReentrantLock和Condition实现的:
例如放学后三个基友约好一起去吃饭,把三个基友看做三条线程,在这里我们改变一些测试类的写法,前面接触了线程池,在这里就定义一个线程池工具类,将任务交给线程池来执行,来看一下实现:
public class DefaultExecutor { private static ExecutorService service = null; private DefaultExecutor(){} public static ExecutorService getThreadPool(){ if(service == null){ synchronized (DefaultExecutor.class){ if(service == null){ service = Executors.newFixedThreadPool(10); } } } return service; }; }
public class CyclicBarrierDemo { private CyclicBarrier cyclicBarrier = new CyclicBarrier(3); public void startThread(){ try { //调用cyclicBarrier的await方法等待其他线程准备就绪 System.out.println("开始准备启动线程……" + Thread.currentThread().getName()); cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println("线程执行完毕" + Thread.currentThread().getName()); } }
测试代码:
public class ThreadDemo { public static void main(String[] args) { CyclicBarrierDemo demo = new CyclicBarrierDemo(); Runnable task1 = demo::startThread; ExecutorService executorService = DefaultExecutor.getThreadPool(); for(int i= 0;i< 3; i++){ executorService.execute(task1); } } }
打印结果:
开始准备启动线程……pool-1-thread-1 开始准备启动线程……pool-1-thread-2 开始准备启动线程……pool-1-thread-3 线程执行完毕pool-1-thread-3 线程执行完毕pool-1-thread-1 线程执行完毕pool-1-thread-2
5、Semaphore:
Semaphore是在java1.5被引入的,存在于java.util.concurrent包下,用于控制对某组资源的访问权限:例如厕所只有两个确有很多人在使用,如果厕所有人其他人就得等待,来看一下这个列子的实现:
public class SemaphoreDemo { //等待上厕所的人数 private int count; //厕所的个数,在创建Semaphore对象时需要传入一个数字,这个数字就是我们定义的厕所个数 private Semaphore semaphore; //定义构造发方法初始化上面两个参数 public SemaphoreDemo(int count, Semaphore semaphore){ this.count = count; this.semaphore = semaphore; } public void startDo(){ try { //等候的人要排队获取厕所的使用权,获取资源 semaphore.acquire(); System.out.println("线程"+ Thread.currentThread().getName() + "获取到了厕所使用权"); //获取到使用权后睡两秒,作为使用的时间 Thread.sleep(2000); //使用完毕后,要起身离开,也就是可以让其他人用,释放资源 semaphore.release(); System.out.println("线程"+ Thread.currentThread().getName() + "用完了厕所"); } catch (InterruptedException e) { e.printStackTrace(); } } }
测试代码:
public class ThreadDemo { public static void main(String[] args) { SemaphoreDemo demo = new SemaphoreDemo(8, new Semaphore(2)); Runnable task1 = demo::startDo; ExecutorService executorService = DefaultExecutor.getThreadPool(); //在这里驾驶有五个人要使用厕所 for(int i= 0;i< 5; i++){ executorService.execute(task1); } executorService.shutdown(); } }
打印结果:
线程pool-1-thread-1获取到了厕所使用权 线程pool-1-thread-2获取到了厕所使用权 线程pool-1-thread-1用完了厕所 线程pool-1-thread-3获取到了厕所使用权 线程pool-1-thread-2用完了厕所 线程pool-1-thread-4获取到了厕所使用权 线程pool-1-thread-3用完了厕所 线程pool-1-thread-5获取到了厕所使用权 线程pool-1-thread-4用完了厕所 线程pool-1-thread-5用完了厕所 Disconnected from the target VM, address: '127.0.0.1:54704', transport: 'socket' Process finished with exit code 0
二、总结:
上面我们大体了解了一下线程之间时如何实现通讯的那么来看一下实现线程之间通讯的方法又有那些区别和相同:
sleep和wait:
(1):sleep不需要在同步代码快或同步方法中使用,而wait必须要在同步代码快或同步方法中使用
(2):sleep是属于Thread类的静态方法作用于当前线程,而wait确实属于Object的实例方法;
(3):sleep方法在调用时,不会释放当前线程拥有的锁,而wait调用时则会释放当前线程拥有的锁;
(4):sleep唤醒只能等待超时或调用interrupt()方法,wait则调用notify或notifyAll方法;
wait和notify:
(1):wait和notify都是属于Object的实例方法:
(2):wait时作用于当前线程,会让当前线程进入阻塞状态,并且释放线程锁占有的锁,notify则是唤醒等待当前线程锁的那个线程执行;
(3):wait和notify的使用都必须在获取锁的情况下;