目标
1、线程的等待和通知
2、生产者和消费者模式
3、JUC工具类
4、AQS
线程的等待和通知
并发编程中可以通过锁对象控制线程,如:让线程等待,通知线程执行
注意:线程的等待和通知必须由锁对象完成,否则出现线程状态异常 IllegalMonitorStateException
任何对象都可以作为锁,方法是Object类定义,等待和通知必须是同一个锁对象完成
| 方法名 | 说明 |
|---|---|
| wait() | 让当前持有锁的线程等待,直到被锁的notify唤醒(自动释放锁) |
| wait(long) | 线程等待一定时间,到时候会自动唤醒 |
| notify() | 随机选择一个等待的线程唤醒 |
| notifyAll() | 唤醒所有等待该锁的线程 |
等待和通知案例:
public class WaitNotifyDemo {
public synchronized static void testWait(){
try {
System.out.println(Thread.currentThread().getName() +"开始等待");
//让线程等待
WaitNotifyDemo.class.wait();
System.out.println(Thread.currentThread().getName() +"执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// WaitNotifyDemo demo = new WaitNotifyDemo();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
WaitNotifyDemo.testWait();
}).start();
}
//过5s主线程通知子线程执行
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通知需要上上锁的代码内执行
synchronized (WaitNotifyDemo.class) {
WaitNotifyDemo.class.notifyAll();
}
}
}
线程交替输出: A线程循环输出A,B线程循环输出B,要求两个线程交替输出ABABAB…
public class ThreadPrintDemo {
public static void main(String[] args) {
new Thread(() -> {
synchronized (ThreadPrintDemo.class) {
for (int i = 0; i < 10; i++) {
System.out.println("A");
try {
//通知对方线程执行
ThreadPrintDemo.class.notify();
//当前线程等待
ThreadPrintDemo.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(() -> {
synchronized (ThreadPrintDemo.class) {
for (int i = 0; i < 10; i++) {
System.out.println("B");
try {
//通知对方线程执行
ThreadPrintDemo.class.notify();
//当前线程等待
ThreadPrintDemo.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
try {
Thread.sleep(500);
synchronized (ThreadPrintDemo.class){
ThreadPrintDemo.class.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
生产者消费者模式
多线程环境下有的线程用于生产数据就属于生产者,有的线程用于使用数据就属于消费者,可能会出现问题:
- 耦合性高,生产者和消费者直接交互,相互影响,不利于代码维护
- 速度不一致,生产者线程太快消费者来不及消费,数据过量造成资源浪费,反之消费者速度过快,消费者会一直等待
实现方法:
- 需要定义一个缓冲区,缓冲区有临界值
- 生产者将数据存入缓冲区,缓冲区满了,就让生产者等待,通知消费者消费
- 消费者从缓冲区取出数据,缓冲区空了,就让消费者等待,通知生产者生产
/**
* 包子
*/
public class Baozi {
private long id;
public Baozi(long id) {
this.id = id;
}
@Override
public String toString() {
return "包子{" +
"id=" + id +
'}';
}
}
/**
* 包子铺
*/
public class BaoziShop {
//临界值
public static final int MAX_COUNT = 100;
//缓冲区
private List<Baozi> baozis = new ArrayList<>();
/**
* 做包子
*/
public synchronized void makeBaozi(){
//判断缓冲区是否满了
if(baozis.size() >= MAX_COUNT){
System.out.println("包子铺满了,师傅等待 " + Thread.currentThread().getName());
try {
//满了,生产者等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//没有满,通知消费者来买
this.notifyAll();
}
//做包子放缓冲区
Baozi baozi = new Baozi(baozis.size());
baozis.add(baozi);
System.out.println("师傅" + Thread.currentThread().getName() + "做了" + baozi);
}
/**
* 卖包子
*/
public synchronized void sellBaozi(){
//判断缓冲区是否空了
if(baozis.size() == 0){
System.out.println("包子铺空了,消费者等待 " + Thread.currentThread().getName());
try {
//空了,消费者等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//没有空,通知师傅来做
this.notifyAll();
}
//从缓冲区拿包子,卖给消费者
if(baozis.size() > 0){
Baozi baozi = baozis.remove(0);
System.out.println("消费者" + Thread.currentThread().getName() + "吃了" + baozi);
}
}
}
public class BaoziShopDemo {
public static void main(String[] args) {
BaoziShop shop = new BaoziShop();
//两个师傅分别做了50包子
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 50; j++) {
shop.makeBaozi();
}
}).start();
}
//10个消费者吃10个包子
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10; j++) {
shop.sellBaozi();
}
}).start();
}
}
}
JUC工具类
java.util.concurrent 并发包
JUC下提供大量的并发开发工具类
常用的有:
- ReentrantLock 重入锁
- 阻塞队列
- CountDownLatch类
- Semaphone类
- CyclicBarrier 类
ReentrantLock的等待和通知
使用Condition接口 ,翻译为条件
创建方法
Condition condition = Lock对象.newCondition()
下面的方法必须包含在 lock的try-finally中使用
| 方法名 | 说明 |
|---|---|
| await() | 让当前持有锁的线程等待,直到被锁的singal唤醒(自动释放锁) |
| await(long) | 线程等待一定时间,到时候会自动唤醒 |
| singal() | 随机选择一个等待的线程唤醒 |
| singalAll() | 唤醒所有等待该锁的线程 |
/**
* 等待和通知案例
*/
public class WaitNotifyDemo2 {
//创建锁对象
private static Lock lock = new ReentrantLock();
//获得条件对象
private static Condition condition = lock.newCondition();
public static void testWait(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() +"开始等待");
//让线程等待
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"执行完毕");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
WaitNotifyDemo2.testWait();
}).start();
}
//过5s主线程通知子线程执行
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通知需要上上锁的代码内执行
lock.lock();
try{
//唤醒所有线程
condition.signalAll();
}finally {
lock.unlock();
}
}
}
阻塞队列
BlockingQueue 是一系列特殊的集合,这种集合会有临界值,达到临界值后自动让线程等待,也会自动唤醒线程
常用实现类:
ArrayBlockingQueue 数组结构的阻塞队列
LinkedBlockingQueue 链表结构的阻塞队列
…
创建
new ArrayBlockingQueue(临界值)
用法
| 方法 | 说明 |
|---|---|
| put(T) | 添加数据到末尾,达到临界值后自动阻塞线程,小于临界值后会自动唤醒线程 |
| T take() | 从头部删除一个数据,如果空了自动阻塞线程,非空后会自动唤醒线程 |
| int size() | 数据个数 |
/**
* 包子铺 阻塞队列版
*/
public class BaoziShop2 {
//临界值
public static final int MAX_COUNT = 100;
//阻塞队列
private BlockingQueue<Baozi> baozis = new ArrayBlockingQueue<>(MAX_COUNT);
/**
* 做包子
*/
public void makeBaozi(){
Baozi baozi = new Baozi(baozis.size());
try {
//做包子放缓冲区
baozis.put(baozi);
System.out.println("师傅" + Thread.currentThread().getName() + "做了" + baozi);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 卖包子
*/
public void sellBaozi(){
try {
Baozi baozi = baozis.take();
System.out.println("消费者" + Thread.currentThread().getName() + "吃了" + baozi);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CountDownLatch 类
作用:一个或多个线程等待其它线程工作执行完再执行自己的任务
创建:
new CountDownLatch(倒数次数)
主要方法:
| 方法 | 说明 |
|---|---|
| await() | 让当前线程等待 |
| countDown() | 倒数一次,次数-1,当次数为0,自动唤醒等待的线程 |
| int getCount() | 获得当前倒数次数 |
PS: CountDownLatch对象只能使用一次
/**
* 倒数案例
*/
public class CountDownLatchDemo {
public static void main(String[] args) {
//创建倒数对象
CountDownLatch countDownLatch = new CountDownLatch(3);
//创建三个线程,分别倒数一次
Thread thread = new Thread(()->{
System.out.println(Thread.currentThread().getName() + "把饭吃了,准备好了!!" + countDownLatch.getCount());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//倒数一次
countDownLatch.countDown();
});
Thread thread2 = new Thread(()->{
System.out.println(Thread.currentThread().getName() + "把垃圾倒了,准备好了!!" + countDownLatch.getCount());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
Thread thread3 = new Thread(()->{
System.out.println(Thread.currentThread().getName() + "把女朋友哄了,准备好了!!" + countDownLatch.getCount());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
thread.start();
thread2.start();
thread3.start();
try {
System.out.println("等等,我去找我兄弟去!!!");
//阻塞当前线程
countDownLatch.await();
System.out.println("我兄弟都来了!!!上啊!!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Semaphore类
信号量,可以用于控制执行任务的数量,主要用于限流(限制并发量)
创建方法:
new Semaphore(信号量大小)
new Semaphore(信号量大小,是否公平锁)
用法
| 方法 | 说明 |
|---|---|
| void accquired() | 请求信号量,信号量会减1,为0时就会阻塞 |
| void release() | 释放信号量,信号量会加1,会唤醒一个阻塞的线程 |
限流案例
/**
* 信号量案例
*/
public class SemaphoneDemo {
public static void main(String[] args) {
//创建信号量对象
Semaphore semaphore = new Semaphore(10);
//限制只有10个线程能执行
for (int i = 0; i < 100; i++) {
final int n = i;
new Thread(() -> {
//消耗一个信号量
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "消耗了信号量" + n);
}).start();
}
try {
Thread.sleep(3000);
//释放信号量
semaphore.release(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CyclicBarrier类
循环栏栅,让多个线程等待一个线程,线程准备好后一起执行,类似CountDownLatch,区别是:可以重复使用
/**
* 循环栏栅
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
//创建栏栅对象
CyclicBarrier barrier = new CyclicBarrier(3,() -> {
System.out.println("发令枪响了!~!!");
});
// for (int j = 0; j < 2; j++) {
// //重置数量
// barrier.reset();
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "准备好了!!");
try {
Thread.sleep(2000);
//开始等待, parties减1,到0就全部唤醒执行
barrier.await();
System.out.println(Thread.currentThread().getName() + "冲啊!!");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
// }
}
}
AQS
AbstractQueuedSynchronizer 抽象队列同步器
大量并发包中的工具类使用了AQS,基于AQS实现工具类功能的开发
使用了AQS的类:
- CountDownLatch
- Semaphone
- ReentrantLock
AQS是一个抽象类,用于开发并发编程的类,包含:
/**
* The synchronization state.
*/
private volatile int state; //同步器状态
1) 成员变量
对于CountDownLatch来说,state就是倒数数量
对于Semaphore来说,就是信号量数
对于ReentrantLock,是上锁的状态
2) 双向链表
保存处于等待状态的线程
对于Semaphore和ReentrantLock来说,可以保存公平锁的等待顺序
总结
线程局部变量,解决线程同步问题,将变量在每个线程保存副本,每个线程使用自己的变量互不影响;每个线程内部有一个map叫ThreadLocalMap,ThreadLocal帮助线程将变量存放到自己的map中。
2678





