你和你的朋友共同完成一个任务,你们随时交流对方的工作进度,因为有些任务是要对等待对方先完成再做,这个过程就是线程通信。
Table of Contents
synchronized实现通信
进入等待(是Object类的方法):
- void wait();
- void wait(long timeoutMillis)
- void wait(long timeoutMillis, int nanos)
- wait方法执行后,会释放监视器,会释放锁!
唤醒(是Object类的方法):
- void notify(); 随机唤醒一个正在wait的方法;
- void notifyAll(); 唤醒所有正在wait的方法;
第一个同步程序:用三个线程模拟三个人报数。
/**
* 功能:三个线程轮流报数
*
* @author KYLE
*
*/
public class Main {
public static void main(String[] args) {
CountOff co = new CountOff();
Thread t1 = new Thread(co);
Thread t2 = new Thread(co);
Thread t3 = new Thread(co);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
class CountOff implements Runnable {
private volatile int initNum = 1;
private final int endNum = 10;
@Override
public void run() {
synchronized (this) {
while(initNum <= 10) {
try {
System.out.println(Thread.currentThread().getName() + "报数:" + initNum++);
this.notifyAll(); //唤醒其他线程开始执行
this.wait(); //进入等待!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
}
}
}
重入锁(ReentrantLock)实现通信
进入等待(juc.locks.Condition接口):
- void await() 将线程放在这个条件的等待集中。
- boolean await(long time, TimeUnit unit) 使当前线程等待直到发出信号或中断,或指定的等待时间过去。
- long awaitNanos(long nanosTimeout) 使当前线程等待直到发出信号或中断,或指定的等待时间过去。
- void awaitUninterruptibly() 使当前线程等待直到发出信号。
- boolean awaitUntil(Date deadline) 使当前线程等待直到发出信号或中断,或者指定的最后期限过去。
唤醒:
- void signal() 随机唤醒一个等待集中的线程,解除其阻塞状态。
- void signalAll() 唤醒所有等待线程,解除阻塞状态。
第一个重入锁实现的线程间通信:
程序功能:A线程通知B线程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadDemo {
public static void main(String[] args) {
ReentrantLockCondition rc = new ReentrantLockCondition();
Thread t1 = new Thread(rc,"线程1");
t1.start();
//通知rc对象的t1线程继续执行!!!
//如果没有以下三段代码,则在Console只会显示:线程1开始执行。。。
rc.lock.lock();
rc.cond.signal(); //通知rc对象的t1线程:可以继续执行了!
rc.lock.unlock();
}
}
class ReentrantLockCondition implements Runnable {
public ReentrantLock lock = new ReentrantLock();
public Condition cond = lock.newCondition(); //newCondition方法是juc.locks.Lock接口中的方法
@Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"开始执行。。。");
cond.await(); //使得本线程进入等待,并且能够释放当前锁
System.out.println(Thread.currentThread().getName()+"线程继续执行。。。");
} catch(InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
输出:
线程1开始执行。。。
线程1线程继续执行。。。
线程之间的通讯工具类
为了能够更好控制线程之间的通信,Java为我们提供了一些工具类:
- CountDownLatch(闭锁)
- CyclicBarrier(栅栏)
- Semaphore(信号量)
CountDownLatch(闭锁)
CountDownLatch的功能:CountDownLatch是一个同步的辅助类,允许一个或多个线程一直等待,直到其它线程完成它们的操作。
特性:
- 来自juc.CountDownLatch类
- 它常用的API其实就两个:await()和countDown()
理解:
- count初始化CountDownLatch,然后需要等待的线程调用await方法。await方法会一直受阻塞直到count=0。而其它线程完成自己的操作后,调用countDown()使计数器count减1。当count减到0时,所有在等待的线程均会被释放
- 说白了就是通过count变量来控制等待,如果count值为0了(其他线程的任务都完成了),那就可以继续执行。
- 实例:你是一个实习生,其他的员工还没下班,你也不好意思走,等其他的员工都走光了,你再走。
第一个CountDownLatch程序
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) {
YourName aa = new YourName();
aa.GoOffWork(); //下班了
}
}
//你是一个实习生,在XxOo公司上班,其他的员工还没下班,你也不好意思走,等其他的员工都走光了,你再走。(改编自:Java3y)
class YourName {
private int num; //公司人数
private final CountDownLatch cd;
public YourName() {
Random rand = new Random();
this.num = rand.nextInt(10); //随机获取一个公司人数
cd = new CountDownLatch(num); //你必须等待公司全部人走了你才能走
}
//下班了
public void GoOffWork() {
System.out.println("现在6点下班了。。。");
//启动你自己的下班线程
new Thread(new Runnable() {
@Override
public void run() {
try {
//这里调用的是await()而不是wait()方法
cd.await(); //要等待其他人都走了(执行完毕),你才能走(执行)
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("其他员工都走了,我也可以下班了");
}
}).start();
//公司其他员工的下班线程
for(int i=0; i<num; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("员工xx下班了!");
cd.countDown();
}
}).start();
}
}
}
CyclicBarrier(栅栏)
理解:
- CyclicBarrier允许一组线程互相等待,直到到达某个点。
- 叫做cyclic是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用(对比于CountDownLatch是不能重用的)
- 区别:CountDownLatch注重的是等待其他线程完成;CyclicBarrier注重的是:当线程到达某个状态后,暂停下来等待其他线程,所有线程均到达以后,继续执行。
- 例如:你和你的朋友们(一组线程)约定一起去旅游,要等到所有朋友都到齐了以后(线程间相互等待),才能出发(到达某个点)。
第一个CyclicBarrier程序
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Main {
public static void main(String[] args) {
Meet meet = new Meet();
Thread t0 = new Thread(meet);
Thread t1 = new Thread(meet);
Thread t2 = new Thread(meet);
Thread t3 = new Thread(meet);
Thread t4 = new Thread(meet);
t0.start();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Meet implements Runnable {
private final CyclicBarrier cb = new CyclicBarrier(5, ()->{
System.out.println("所有朋友都到了,可以出发去旅游了!");
}); //你有5个朋友相约一起去旅游
@Override
public void run() {
try {
synchronized (this) {
speedTime();
}
cb.await(); //等待所有朋友到齐
} catch(InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
//每个朋友需要到达聚会点的所花时间
public void speedTime() {
try {
Thread.sleep(2000); //模拟这个朋友需要2s才能到达聚会点
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("朋友(线程):"+Thread.currentThread().getName()+"到达了!");
}
}
Semaphore(信号量)
背景:
- 无论是内部锁synchronized还是重入锁ReentrantLock,一次都只允许一个线程访问一个资源;
- 那么,有些资源必须允许多个线程访问同时访问,应该怎么实现呢?
- 例如,某个小店(资源)一次只能允许5个顾客(多个线程)同时挑选购买,超过5个就需要排队等待了。
理解:
- Semaphore(信号量)实际上就是可以指定多个线程同时访问某一资源的一种解决方案。
构造方法:
- Semaphore(int permits) 使用给定数量的许可和非公平方式设置创建 Semaphore 。
- Semaphore(int permits, boolean fair) 使用给定数量的许可和公平的方式设置创建 Semaphore 。
核心方法:
- void acquire() 从此信号量上获取一个许可;如果没有则一直阻塞,直到有一个可用;或者线程为interrupted
- void acquire(int permits) 从此信号量获取给定数量的许可;阻塞直到所有可用;或者线程为 interrupted
- void acquireUninterruptibly() 从此信号量获取许可,如果没有一直阻塞,直到有一个可用
- boolean tryAcquire(int permits) 只有在调用时所有许可都可用时,才从此信号量获取给定数量的许可。【尝试获取】
- boolean tryAcquire(int permits, long timeout, TimeUnit unit) 如果在给定的等待时间内所有许可都可用,且当前线程不是 interrupted ,则从此信号量获取给定数量的许可。【时间等待获取】
- boolean tryAcquire(long timeout, TimeUnit unit) 如果在给定的等待时间内有可用的并且当前线程不是 interrupted ,则从该信号量获取许可。【时间条件获取】
- void release() 释放许可。
第一个信号量程序
import java.util.concurrent.Semaphore;
public class Main {
public static void main(String[] args) {
SemaphoreExample se = new SemaphoreExample();
//准备6个线程
Thread t0 = new Thread(se);
Thread t1 = new Thread(se);
Thread t2 = new Thread(se);
Thread t3 = new Thread(se);
Thread t4 = new Thread(se);
Thread t5 = new Thread(se);
t0.start();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class SemaphoreExample implements Runnable {
private final Semaphore sema = new Semaphore(5); //某一资源同一时刻只能允许5个线程访问
@Override
public void run() {
try {
sema.acquire(); //申请对该资源的访问,但构造方法规定该资源一次只能允许5个线程同时访问
System.out.println("线程"+Thread.currentThread().getName()+"正在访问该资源!");
Thread.sleep(3000); //一旦申请成功,立即休眠3s,模拟该线程执行任务
} catch(InterruptedException e) {
e.printStackTrace();
} finally {
sema.release();
}
}
}