线程通信知识点

一、线程的等待和通知

并发编程中可以通过锁对象控制线程,如:让线程等待,通知线程执行。

注意:线程的等待和通知必须由锁对象来完成,否则出现线程状态异常:IllegalMonitorStateException

任何对象都可以作为锁,方法是 Object 类定义,等待和通知必须是同一个锁对象完成

synchronized的 等待和通知方法

方法名

说明

wait() weɪt

让当前持有锁的线程等待,直到被锁的 notify 唤醒(会自动释放锁)

wait(long)

线程等待一定时间,到时候会自动唤醒

notify() nəʊtɪfaɪ

随机选择一个等待的线程唤醒

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 重入锁
  • blockingqueue 阻塞队列
  • CountDownLatch 类 倒数
  • Semaphore 类 信号量
  • CyclicBarrier 类 循环栅栏

ReentrantLock 的等待和通知

使用Condition接口 ,翻译为条件 kənˈdɪʃ(ə)n

创建方法

Condition condition = Lock对象.newCondition()

下面的方法必须包含在 lock 的 try-finally 中使用

ReentrantLock 方法

方法名

说明

await()

让当前持有锁的线程等待,直到被锁的singal唤醒(自动释放锁)

await(long)

线程等待一定时间,到时候会自动唤醒

singal()

随机选择一个等待的线程唤醒

singalAll()

唤醒所有等待该锁的线程

ReentrantLock 用法

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class AwaitNotifyDemo {
    //    创建锁对象
    private static Lock lock = new ReentrantLock();
    //    获得条件对象
    private static Condition condition = lock.newCondition();

    public static void testAwait() {
        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(() -> {
                AwaitNotifyDemo.testAwait();
            }).start();
        }

        try {
//            过5s主线程通知子线程执行
            Thread.sleep(2000);
//            通知需要在上锁的代码内执行
            lock.lock();
//            唤醒所有线程
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}

blockingqueue 阻塞队列

BlockingQueue 是一系列特殊的集合,这种集合会有临界值,达到临界值后自动让线程等待,也会自动唤醒线程。 blɑːkɪŋ kjuː

常用实现类

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 类

kaʊnt daʊn lætʃ 倒数

作用:一个或多个线程等待其它线程工作执行完再执行自己的任务

创建:

new CountDownLatch(倒数次数)

CountDownLatch 方法:

方法

说明

await()

让当前线程等待

countDown()

倒数一次,次数-1,当次数为0,自动唤醒等待的线程

int getCount()

获得当前倒数次数

CountDownLatch 用法

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 类

seməfɔːr

信号量,可以用于控制执行任务的数量,主要用于限流(限制并发量) 可以重复使用的

创建方法:

new Semaphore(信号量大小)
new Semaphore(信号量大小,是否公平锁)

Semaphore 方法

方法

说明

void accquired() eɪ siː siː kwaɪər

请求信号量,信号量会减1,为0时就会阻塞

void release() rɪˈliːs

释放信号量,信号量会加1,会唤醒一个阻塞的线程

Semaphore 限流案例:

/**
 * 信号量案例
 */
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 类

ˈsaɪklɪk ˈbæriər

循环栏栅,让多个线程等待一个线程,线程准备好后一起执行,类似CountDownLatch,区别是:可以重复使用

创建方法:

//        创建栏栅对象
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("发令枪响了!~ !!!");
        });

CyclicBarrier 案例

/**
 * 循环栏栅
 */
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是一个抽象类,用于开发并发编程的类,包含:

1) 成员变量

    /**
     * The synchronization state.
     */
    private volatile int state; //同步器状态

对于CountDownLatch来说,state就是倒数数量

对于Semaphore来说,就是信号量数

对于ReentrantLock,是上锁的状态

2) 双向链表

处于等待的线程队列

对于Semaphore和ReentrantLock来说,可以保存公平锁的等待顺序

自学任务:

ThreadLocal是什么?起什么作用?它的原理是什么?

线程局部变量,解决线程同步问题,将变量在每个线程 保存副本,每个线程使用自己的变量互不影响;每个线程内部又一个 map 叫 ThreadLockMap,ThreadLock 帮助线程将变量存放到自己的 map 中。

总结

需要掌握内容:

1) 线程的等待和通知通过哪些方法实现,分别介绍一下

2) 介绍一下生产者消费者模式

3) 介绍下阻塞队列是什么,有什么作用

4) 介绍下并发包中的常用类

5) 介绍下AQS

  • 19
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值