13天Java进阶笔记-day8-线程状态、volatile关键字、原子性、并发包、死锁、线程池

第一章 线程状态

线程状态概述

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:

这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析

线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread只有线程对象,没有线程特征。
Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。调用了t.start()方法 :就绪(经典教法)
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

睡眠方法

我们看到状态中有一个状态叫做计时等待,可以通过Thread类的方法来进行演示.

public static void sleep(long time) 让当前线程进入到睡眠状态,到毫秒后自动醒来继续执行

public class Test{
  public static void main(String[] args){
    for(int i = 1;i<=5;i++){
      	Thread.sleep(1000);
        System.out.println(i)   
    } 
  }
}

这时我们发现主线程执行到sleep方法会休眠1秒后再继续执行。

等待和唤醒

Object类的方法

public void wait() : 让当前线程进入到等待状态 此方法必须锁对象调用.

public class Demo1_wait {
    public static void main(String[] args) throws InterruptedException {
	   // 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
        new Thread(() -> {
            try {

                System.out.println("begin wait ....");
                synchronized ("") {
                    "".wait();
                }
                System.out.println("over");
            } catch (Exception e) {
            }
        }).start();
    }

public void notify() : 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.

public class Demo2_notify {
    public static void main(String[] args) throws InterruptedException {
	   // 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
        new Thread(() -> {
            try {

                System.out.println("begin wait ....");
                synchronized ("") {
                    "".wait();
                }
                System.out.println("over");
            } catch (Exception e) {
            }
        }).start();

        //步骤2:  加入如下代码后, 3秒后,会执行notify方法, 唤醒wait中线程.
        Thread.sleep(3000);
        new Thread(() -> {
            try {
                synchronized ("") {
                    System.out.println("唤醒");
                    "".notify();
                }
            } catch (Exception e) {
            }
        }).start();
    }
}

第二章 线程通信

  • 多个线程因为在同一个进程中,所以互相通信比较容易
  • 线程通信一定是多个线程在操作同一个资源才需要进行通信
  • 线程通信必须先保证线程安全,否则毫无意义

线程通信的核心方法:

  • public void wait(): 让当前线程进入到等待状态 此方法必须锁对象调用.
  • public void notify() : 唤醒当前锁对象上等待状态的某个线程 此方法必须锁对象调用
  • public void notifyAll() : 唤醒当前锁对象上等待状态的全部线程 此方法必须锁对象调用

第三章 线程池

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复的使用,省去了频繁创建和销毁线程对象的操作,无需反复创建线程而消耗过多资源。

合理利用线程池能够带来三个好处

  1. 降低资源消耗。
    减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度
    不需要频繁的创建线程,如果有线程可以直接用,不会出现系统僵死!
  3. 提高线程的可管理性(线程池可以约束系统最多只能有多少个线程,不会因为线程过多而死机)

线程池的核心思想:线程复用,同一个线程可以被重复使用,来处理多个任务。

创建线程池

线程池在Java中的代表类:ExecutorService(接口)。

Java在Executors类下提供了一个静态方法得到一个线程池的对象:
public static ExecutorService newFixedThreadPool(int nThreads):创建一个线程池返回。

  • ExecutorService提交线程任务对象执行的方法:
    Future<?> submit(Runnable task):提交一个Runnable的任务对象给线程池执行。
  • Future<?> submit(Callable task):提交一个Callable的任务对象给线程池执行。
  • pools.shutdown(); // 等待任务执行完毕以后才会关闭线程池
  • pools.shutdownNow(); // 立即关闭线程池的代码,无论任务是否执行完毕
  • 线程池中的线程可以被复用,线程用完以后可以继续去执行其他任务。

Runnable任务对象给线程池执行

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolsDemo02 {
    public static void main(String[] args) {
        // a.创建一个线程池,指定线程的固定数量是3.
        // new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
        ExecutorService pools = Executors.newFixedThreadPool(3);
        // b.创建线程的任务对象。
        Runnable target = new MyRunnable();
        // c.把线程任务放入到线程池中去执行。
        pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
        pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
        pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
        pools.submit(target); // 不会再创建新线程,会复用之前的线程来处理这个任务

        pools.shutdown(); // 等待任务执行完毕以后才会关闭线程池
        //pools.shutdownNow(); // 立即关闭线程池的代码,无论任务是否执行完毕!
    }
}

class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i  = 0 ; i < 5 ; i++ ){
            System.out.println(Thread.currentThread().getName()+" => "+i);
        }
    }
}

Callable任务对象给线程池执行

public class ThreadPoolsDemo03 {
    public static void main(String[] args) {
        // a.创建一个线程池,指定线程的固定数量是3.
        // new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
        ExecutorService pools = Executors.newFixedThreadPool(3);
        Future<String> t1 = pools.submit(new MyCallable(10)); // 提交任务,此时会创建一个新线程,自动启动线程执行!
        Future<String> t2 = pools.submit(new MyCallable(20)); // 提交任务,此时会创建一个新线程,自动启动线程执行!
        Future<String> t3 = pools.submit(new MyCallable(30)); // 提交任务,此时会创建一个新线程,自动启动线程执行!
        Future<String> t4 = pools.submit(new MyCallable(40)); // 复用之前的某个线程

        try{
            // b.可以得到线程池执行的任务结构
            String rs1 = t1.get();
            String rs2 = t2.get();
            String rs3 = t3.get();
            String rs4 = t4.get();
            System.out.println(rs1);
            System.out.println(rs2);
            System.out.println(rs3);
            System.out.println(rs4);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

// 1.定义一个线程任务类实现Callable接口 , 申明线程执行的结果类型。
class MyCallable implements Callable<String>{
    private int n;
    public MyCallable(int n){
        this.n = n;
    }
    // 2.重写线程任务类的call方法,这个方法可以直接返回执行的结果。
    @Override
    public String call() throws Exception {
        int sum = 0 ;
        for(int i = 1 ; i <= n ; i++){
            System.out.println(Thread.currentThread().getName()+" => "+i);
            sum += i ;
        }
        return Thread.currentThread().getName()+"计算1-"+n+"的和:"+sum;
    }
}

第四章 死锁

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

java 死锁产生的四个必要条件:

  • 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  • 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
  • 求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有
  • 循环等待,即存在一个等待循环队列:p1要p2的资源,p2要p1的资源。这样就形成了一个等待环路

​ 当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失

第五章 volatile关键字

问题:线程修改了某个成员变量的值,但是在主线程中读取到的还是之前的值修改后的值无法读取到。

原因:按照JMM模型,所有的成员变量和静态变量都存在于主内存中,主内存中的变量可以被多个线程共享。每个线程都存在一个专属于自己的工作内存,工作内存一开始存储的是成员变量的副本。所以线程很多时候都是直接访问自己工作内存中的该变量,其他线程对主内存变量值的修改将不可见

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UFtLNAor-1654912511724)(imgs/image-20220606111513140.png)]

希望所有线程对于主内存的成员变量修改,其他线程是可见的。

  • 加锁:可以实现其他线程对变量修改的可见性
    某一个线程进入synchronized代码块前后,执行过程入如下:
    • 线程获得锁
    • 清空工作内存
    • 从主内存拷贝共享变量最新的值到工作内存成为副本
  • 可以给成员变量加上一个volatile关键字,立即就实现了成员变量多线程修改的可见性

volatilesynchronized的区别。

  • volatile只能修饰实例变量和静态变量,而synchronized可以修饰方法,以及代码块。
  • volatile保证数据的可见性,但是不保证原子性(多线程进行写操作,不保证线程安全);而synchronized是一种排他(互斥)的机制,
public class VolatileDemo01 {
    public  static void main(String[] args) {
        // 1.启动线程,把线程对象中的flag改为true。
        VolatileThread t = new VolatileThread();
        t.start();

        // 2.定义一个死循环
        while(true){
            // 这里读取到了flag值一直是false,虽然线程已经把它的值改成了true。
            if(t.isFlag()){
                System.out.println("执行了循环一次~~~~~~~");
            }
        }
       /* while(true){
            synchronized ("ddd"){
                // 这里读取到了flag值一直是false,虽然线程已经把它的值改成了true。
                if(t.isFlag()){
                    System.out.println("执行了循环一次~~~~~~~");
                }
            }
        }*/
    }
}
// 线程类。
class VolatileThread extends Thread {
    // 定义成员变量
    // volatile可以实现变量一旦被子线程修改,其他线程可以马上看到它修改后的最新值!
    private volatile boolean flag = false ;
    public boolean isFlag() {
        return flag;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 将flag的值更改为true
        this.flag = true ;
        System.out.println("线程修改了flag=" + flag);
    }
}

第六章 原子性

原子性是指在一次操作或者多次操作中,所有的操作全部都得到了执行并且不会受到任何因素的干扰。最终结果要保证线程安全。

在多线程环境下,volatile关键字可以保证共享数据的可见性,但是并不能保证对数据操作的原子性(在多线程环境下volatile修饰的变量也是线程不安全的)。
volatile的使用场景

  • 开关控制
    用可见性特点,控制某一段代码执行或者关闭

  • 多个线程操作共享变量,但是是有一个线程对其进行写操作,其他的线程都是读

    此时加上更好,其他线程可以立即读取到最新值。volatile不能保证变量操作的原子性(安全性)。

解决方法一-加锁

public class VolatileAtomicThread implements Runnable {
    // 定义一个int类型的遍历
    private volatile int count = 0 ;
    @Override
    public void run() {
        // 对该变量进行++操作,100次
        for(int x = 0 ; x < 100 ; x++) {
           synchronized (this){
               count++ ;
               System.out.println(Thread.currentThread().getName() + "count =========>>>> " + count);
           }
        }
    }
}

class VolatileAtomicThreadDemo {
    public static void main(String[] args) {
        // 创建VolatileAtomicThread对象
        Runnable target = new VolatileAtomicThread() ;
        // 开启100个线程对执行这一个任务。
        for(int x = 0 ; x < 100 ; x++) {
            new Thread(target).start();
        }
    }

}

这种方法虽然安全性得到了保证,但是性能不好

解决方法二-基于CAS方式的原子类

Java已经提供了一些本身即可实现原子性(线程安全)的类。

  • 概述:java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。
  • 操作整型的原子类
    • public AtomicInteger(): 初始化一个默认值为0的原子型Integer
    • public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
    • int get(): 获取值
    • int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
    • int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
    • int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
    • int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
public class VolatileAtomicThread implements Runnable {
    // 原子类中封装好了整型变量,默认值是0
    private AtomicInteger atomicInteger = new AtomicInteger();
    @Override
    public void run() {
        // 对该变量进行++操作,100次
        for(int x = 0 ; x < 100 ; x++) {
            int count = atomicInteger.incrementAndGet(); // 底层变量+1且返回!
            System.out.println("count =========>>>> " + count);
        }
    }
}

class VolatileAtomicThreadDemo {
    public static void main(String[] args) {
        // 创建VolatileAtomicThread对象
        Runnable target = new VolatileAtomicThread() ;
        // 开启100个线程对执行这一个任务。
        for(int x = 0 ; x < 100 ; x++) {
            new Thread(target).start();
        }
    }

}

CAS与Synchronized总结

Synchronized是从悲观的角度出发:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。因此Synchronized我们也将其称之为悲观锁。jdk中的ReentrantLock也是一种悲观锁。性能较差

CAS是从乐观的角度出发:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。CAS这种机制我们也可以将其称之为乐观锁。综合性能较好

第七章 并发包

并发包的来历:
在实际开发中如果不需要考虑线程安全问题,大家不需要做线程安全,因为如果做了反而性能不好!
但是开发中有很多业务是需要考虑线程安全问题的,此时就必须考虑了。否则业务出现问题。
Java为很多业务场景提供了性能优异,且线程安全的并发包,程序员可以选择使用!

ConcurrentHashMap

Map集合中的经典集合:HashMap它是线程不安全的,性能好,如果在要求线程安全的业务情况下就不能用这个集合做Map集合,否则业务会崩溃

为了保证线程安全,可以使用Hashtable。注意:线程中加入了计时,Hashtable是线程安全的Map集合,但是性能较差!(已经被淘汰了,虽然安全,但是性能差)

为了保证线程安全,再看ConcurrentHashMap(不止线程安全,而且效率高,性能好,最新最好用的线程安全的Map集合)ConcurrentHashMap保证了线程安全,综合性能较好!

  • HashMap是线程不安全的。
  • Hashtable线程安全基于synchronized,综合性能差,被淘汰了。
  • ConcurrentHashMap:线程安全的,分段式锁,综合性能最好,线程安全开发中推荐使用
public class ConcurrentHashMapDemo {
    // 定义一个静态的HashMap集合,只有一个容器。
    // public static Map<String,String> map = new HashMap<>();
     public static Map<String,String> map = new Hashtable<>();
    //public static Map<String,String> map = new ConcurrentHashMap<>();

    public static void main(String[] args) throws InterruptedException {
        // HashMap线程不安全演示。
        // 需求:多个线程同时往HashMap容器中存入数据会出现安全问题。
        // 具体需求:提供2个线程分别给map集合加入50万个数据!
        new AddMapDataThread().start();
        new AddMapDataThread().start();

        //休息10秒,确保两个线程执行完毕
        Thread.sleep(1000 * 4);
        //打印集合大小
        System.out.println("Map大小:" + map.size());
    }
}

class AddMapDataThread extends Thread{
    @Override
    public void run() {
        for(int i = 0 ; i < 1000000 ; i++ ){
            ConcurrentHashMapDemo.map.put(Thread.currentThread().getName()+"键:"+i , "值"+i);
        }
    }
}

CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作,再执行自己。

例如:

线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行

需求:

提供A线程,打印 A , C
提供B线程,打印 B

构造器:

public CountDownLatch(int count)// 初始化唤醒需要的down几步。

方法:
public void await() throws InterruptedException// 让当前线程等待,必须down完初始化的数字才可以被唤醒,否则进入无限等待
public void countDown() // 计数器进行减1 (down 1)

public class CountDownLatchDemo {
    public static void main(String[] args) {
        //创建1个计数器:用来控制 A , B线程的执行流程的。
        CountDownLatch down = new CountDownLatch(1);
        new ThreadA(down).start();
        new ThreadB(down).start();
    }
}

class ThreadA extends Thread{
    private CountDownLatch down;
    public ThreadA(CountDownLatch down){
        this.down = down;
    }
    @Override
    public void run() {
        System.out.println("A");
        try {
            down.await(); // A线程你进入等待,让B线程执行自己!
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("C");
    }
}

class ThreadB extends Thread{
    private CountDownLatch down;
    public ThreadB(CountDownLatch down){
        this.down = down;
    }
    @Override
    public void run() {
        System.out.println("B");
        down.countDown(); // 这里相当于是-1,代表自己执行完毕了。A线程被唤醒!!
    }
}

CyclicBarrier

CyclicBarrier作用:某个线程任务必须等待其他线程执行完毕以后才能最终触发自己执行。

例如:公司召集5名员工开会,等5名员工都到了,会议开始。我们创建5个员工线程,1个开会任务,几乎同时启动。使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。

构造器:
public CyclicBarrier(int parties, Runnable barrierAction)
// 用于在线程到达屏障5时,优先执行barrierAction,方便处理更复杂的业务场景
方法:
public int await()
// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞

  • 可以实现多线程中,某个任务在等待其他线程执行完毕以后触发。
  • 循环屏障可以实现达到一组屏障就触发一个任务执行!
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 1.创建一个任务循环屏障对象。
        /**
         * 参数一:代表多少个线程的执行。
         * 参数二:到达执行屏障就开始触发的线程任务。
         */
        CyclicBarrier cb = new CyclicBarrier(5 , new MeetingRunnable());
        new PeopleThread(cb).start();
        new PeopleThread(cb).start();
        new PeopleThread(cb).start();
        new PeopleThread(cb).start();
        new PeopleThread(cb).start();

        new PeopleThread(cb).start();
        new PeopleThread(cb).start();
        new PeopleThread(cb).start();
        new PeopleThread(cb).start();
        new PeopleThread(cb).start();

    }
}

// 任务类:开始开会的任务
class MeetingRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("人员到齐了开始由"+Thread.currentThread().getName()+"主持会议!");
    }
}

// 员工类
class PeopleThread extends Thread{
    private CyclicBarrier cb ;
    public PeopleThread(CyclicBarrier cb) {
        this.cb = cb;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println("员工:"+Thread.currentThread().getName()+"进入会议室");
            cb.await(); // 自己做完了,告诉循环屏障我结束了!
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Semaphore

  • Semaphore(发信号)的主要作用是控制线程的并发数量。

  • synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。

  • Semaphore可以设置同时允许几个线程执行。

  • Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。

Semaphore的构造器:

  • public Semaphore(int permits): permits 表示许可线程的数量
  • public Semaphore(int permits, boolean fair):fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程

Semaphore的方法:

  • public void acquire() throws InterruptedException 表示获取许可
  • public void release() release() 表示释放许可
public class SemaphoreDemo {
    public static void main(String[] args) {
        Service service = new Service();
        for(int i = 1 ; i <= 5 ; i++ ){
            new MyThread(service,"线程:"+i).start();
        }
    }
}
// 执行的任务。
class Service{
    // 可以同时支持多个线程进入共享资源区执行。
    private Semaphore semaphore = new Semaphore(2);
    public void showMethod(){
        try {
            semaphore.acquire();
            long startTimer = System.currentTimeMillis();
            System.out.println("进入时间:"+startTimer);
            System.out.println(Thread.currentThread().getName()+"进入资源执行");
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        long endTimer = System.currentTimeMillis();
        System.out.println("结束时间:"+endTimer);
        semaphore.release();
        //acquire()和release()方法之间的代码为"同步代码"
    }
}

// 线程类。
class MyThread extends Thread{
    private Service service;
    public MyThread(Service service , String name){
        super(name);
        this.service = service;
    }
    @Override
    public void run() {
        service.showMethod();
    }
}

Exchanger

Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

Exchanger构造方法:public Exchanger()

Exchanger重要方法:public V exchange(V x)

分析:
(1)需要2个线程
(2)需要一个交换对象负责交换两个线程执行的结果。

  • Exchanger可以实现线程间的数据交换。
  • 一个线程如果等不到对方的数据交换就会一直等待。
  • 我们也可以控制一个线程等待的时间。
  • 必须双方都进行交换才可以正常进行数据的交换。
public class ExchangerDemo {
    public static void main(String[] args) {
        // 创建交换对象(信使)
        Exchanger<String> exchanger = new Exchanger<>();
        // 创建2给线程对象。
        new ThreadA(exchanger).start();
        new ThreadB(exchanger).start();
    }
}

class ThreadA extends Thread{
    private Exchanger<String> exchanger;
    public ThreadA(Exchanger<String> exchanger) {
        this.exchanger = exchanger;
    }
    @Override
    public void run() {
        try {
            // 礼物A
            System.out.println("线程A,做好了礼物A,等待线程B送来的礼物B.....");
            // 开始交换礼物。参数是送给其他线程的礼物!
            // System.out.println("线程A收到线程B的礼物:"+exchanger.exchange("礼物A"));
            // 如果等待了5s还没有交换它就去死(抛出异常)!
            System.out.println("线程A收到线程B的礼物:"+exchanger.exchange("礼物A", 5 , TimeUnit.SECONDS));
        } catch (Exception e) {
            System.out.println("线程A等待了5s,没有收到礼物,最终就执行结束了!");
        }
    }
}

class ThreadB extends Thread{
    private Exchanger<String> exchanger;
    public ThreadB(Exchanger<String> exchanger) {
        this.exchanger = exchanger;
    }
    @Override
    public void run() {
        try {
            // 礼物B
             System.out.println("线程B,做好了礼物B,等待线程A送来的礼物A.....");
            // 开始交换礼物。参数是送给其他线程的礼物!
             System.out.println("线程B收到线程A的礼物:"+exchanger.exchange("礼物B"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值