首页 > 技术文章 > 石一歌的多线程进阶笔记

faetbwac 2022-02-01 23:52 原文

Java多线程进阶JUC

JUC 其实就是 Java.Util.concurrent 包的缩写

文档地址

QQ截图20220126153458

回顾

线程的开启三种方式

  • Thread 单继承会有oop问题
  • Runnable 没有返回值、效率相比入 Callable 相对较低
  • Callable 推荐

线程与进程

  • 进程

    ​ 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

  • 线程

    ​ 线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

  • Java 本身无法开启线程,需要调动本地方法

并发编程

  • 并发

    • 单核 ,模拟出来多条线程
  • 并行

    • 多核 ,多个线程可以同时执行; 线程池
  • 查看电脑线程数

public class Test {                                          
   public static void main(String[] args) {                     
       // 获取电脑的线程数,不是cpu的核心数                         
       // CPU 密集型,IO密集型                                      
       System.out.println(Runtime.getRuntime().availableProcessors()); 
   }                                                            
}                                                            
  • 线程状态
public enum State {                                          
   // 新生                                                      
   NEW,                                                         
   // 运行                                                      
   RUNNABLE,                                                    
   // 阻塞                                                      
   BLOCKED,                                                     
   // 等待,死死地等                                            
   WAITING,                                                     
   // 超时等待                                                  
   TIMED_WAITING,                                               
   // 终止                                                      
   TERMINATED;                                                  
}                                                            
  • wait 与 sleep 的区别

    • 来源

      //Object.wait()                                              
      public final void wait() throws InterruptedException {       
      	wait(0);                                                     
      }                                                            
      //Thread.sleep()                                             
      //注:实际开发,会使用TimeUnit.DAYS.sleep(1L),底层仍为Thread.sleep() 
      public static native void sleep(long millis) throws InterruptedException; 
      
    • 锁的释放

      • wait() 会释放锁:wait 是进入线程等待池等待,出让系统资源,其他线程可以占用 CPU。

      • sleep() 不出让系统资源;

    • 捕获异常

      • 都需要捕获异常
    • 使用范围

      • wait() 需要在同步代码块、同步方法中使用
      • sleep() 可以在任何地方使用
    • 作用对象:

      • wait() 定义在 Object 类中,作用于对象本身
      • sleep() 定义在 Thread 类中,作用当前线程。
    • 方法属性:

      • wait() 是实例方法
      • sleep() 是静态方法 有static

  • synchronized

    • 同步方法
    • 同步代码块
  • Lock QQ截图20220126203952

    • 常用语句

      Lock l = ...; l.lock();                                      
      try { // access the resource protected by this lock }        
      finally { l.unlock(); }                             
      
    • ReentrantLock 可重入锁实现

      • 默认为非公平锁,可在构造函数选择QQ截图20220126205415
  • Synchronized 和 Lock 区别

     详细区别,参照文末链接 **Java并发编程:Lock**                                                              
    
  • Lock获取锁的其他方式
    • 尝试非阻塞的获取锁 tryLock():当前线程尝试获取锁,如果该时刻锁没有被其他线程获取到,就能成功获取并持有锁

    • 能被中断的获取锁 lockInterruptibly():获取到锁的线程能够响应中断,当获取到锁的线程被中断的时候,会抛出中断异常同时释放持有的锁

    • 超时的获取锁 tryLock(long time, TimeUnit unit):在指定的截止时间获取锁,如果没有获取到锁返回 false
      QQ截图20220126215245


生产者消费者问题

synchronized 版本

  • 单生产者单消费者
public class ProducerWithSynchronized {                      
   public static void main(String[] args) {                     
   	SynchronizedData data = new SynchronizedData();              
   	new Thread(() - {                                            
   		for (int i = 0; i < 10; i++) {                               
   			try {                                                        
   				data.increment();                                            
   			} catch (InterruptedException e) {                           
   				e.printStackTrace();                                         
   			}                                                            
   		}                                                            
   	},"ADD").start();                                            
   	new Thread(()-{                                              
   		for (int i = 0; i < 10; i++) {                               
   			try {                                                        
   				data.decrement();                                            
   			} catch (InterruptedException e) {                           
   				e.printStackTrace();                                         
   			}                                                            
   		}                                                            
   	},"MINUS").start();                                                                         
   }                                                            
}                                                            
//判断等待                                                   
//业务代码                                                   
//通知其他线程                                               
//数字:资源类                                               
public class SynchronizedData {                              
   //属性                                                       
   private int number = 0;
   public synchronized void increment() throws InterruptedException { 
       while (number != 0) {                                        
           //等待                                                       
           this.wait();                                                 
       }                                                            
       number++;                                                    
       System.out.println(Thread.currentThread().getName() + "--" + number); 
       //加完了通知其他线程                                         
       this.notifyAll();                                            
   }                                                            
                                                            
   public synchronized void decrement() throws InterruptedException { 
       while (number == 0) {                                        
           //等待                                                       
           this.wait();                                                 
       }                                                            
       number--;                                                    
       System.out.println(Thread.currentThread().getName() + "--" + number); 
       //减完了通知其他线程                                         
       this.notifyAll();                                            
   }                                                            
}                                                            
  • 多生产者多消费者
    • 出现虚假唤醒问题

      • 一旦线程被唤醒,并得到锁,就不会再判断if条件,而执行if语句块外的代码,所以建议,凡是先要做条件判断,再wait的地方,都使用while循环来做
    • 解决方法

      • if判断换为while,线程被再次唤醒后会继续判断条件
    • 注意

      • 单纯将notifyAll()换为notify(),不再唤醒等待的多个线程,而是随机唤醒单个线程,不会解决虚假唤醒问题。

Lock版本

通过 Lock 找到 condition 来配合控制对线程的唤醒 QQ截图20220126232320

public class ProducerWithLock {
   public static void main(String[] args) {
       LockData data = new LockData();
       new Thread(() -> {
           for (int i = 0; i < 10; i++) {
               try {
                   data.increment();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       },"A").start();
       new Thread(()->{
           for (int i = 0; i < 10; i++) {
               try {
                   data.decrement();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       },"B").start();
       new Thread(() -> {
           for (int i = 0; i < 10; i++) {
               try {
                   data.increment();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       },"C").start();
       new Thread(()->{
           for (int i = 0; i < 10; i++) {
               try {
                   data.decrement();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       },"D").start();
   }
}
public class LockData {
   private int number = 0;
   private Lock lock = new ReentrantLock();
   private Condition condition = lock.newCondition();

   public void increment() throws InterruptedException {
       lock.lock();
       try {
           while (number != 0) {
               //等待
               condition.await();
           }
           number++;
           System.out.println(Thread.currentThread().getName() + "-->" + number);
           //加完了通知其他线程
           condition.signalAll();
       } catch (InterruptedException e) {
           e.printStackTrace();
       } finally {
           lock.unlock();
       }
   }

   public void decrement() throws InterruptedException {
       lock.lock();
       try {
           while (number == 0) {
               //等待
               condition.await();
           }
           number--;
           System.out.println(Thread.currentThread().getName() + "-->" + number);
           //减完了通知其他线程
           condition.signalAll();
       } catch (InterruptedException e) {
           e.printStackTrace();
       } finally {
           lock.unlock();
       }
   }
}

Lock精准唤醒版本

这里我用的例子还是刚才的生产者消费者,方便展示问题。

刚才解决虚假唤醒的时候说过不能直接将notifyAll()换为notify(),也就是说我们必须使用全部唤醒。但是转到Lock时分离出来的Condition可以使用signal()来实现精准唤醒,不会有资源的浪费。

弹幕说notifyAll()和单个Condition的signalAll()也可以的。我只能说精准唤醒是为了提升性能,不是为了实现功能。

public class AwakeByCondition {
   public static void main(String[] args) {
       AwakeInOrderByCondition data = new AwakeInOrderByCondition();
       new Thread(() -> {
           for (int i = 0; i < 10; i++) {
               try {
                   data.increment();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       },"A").start();
       new Thread(()->{
           for (int i = 0; i < 10; i++) {
               try {
                   data.decrement();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       },"B").start();
       new Thread(() -> {
           for (int i = 0; i < 10; i++) {
               try {
                   data.increment();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       },"C").start();
       new Thread(()->{
           for (int i = 0; i < 10; i++) {
               try {
                   data.decrement();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       },"D").start();
   }
}
public class AwakeInOrderByCondition {
   private int number = 0;
   private Lock lock = new ReentrantLock();
   private Condition condition1 = lock.newCondition();
   private Condition condition2 = lock.newCondition();

   public void increment() throws InterruptedException {
       lock.lock();
       try {
           while (number != 0) {
               //等待
               condition1.await();
           }
           number++;
           System.out.println(Thread.currentThread().getName() + "-->" + number);
           //加完了通知其他线程
           condition2.signal();
       } catch (InterruptedException e) {
           e.printStackTrace();
       } finally {
           lock.unlock();
       }
   }

   public void decrement() throws InterruptedException {
       lock.lock();
       try {
           while (number == 0) {
               //等待
               condition2.await();
           }
           number--;
           System.out.println(Thread.currentThread().getName() + "-->" + number);
           //减完了通知其他线程
           condition1.signal();
       } catch (InterruptedException e) {
           e.printStackTrace();
       } finally {
           lock.unlock();
       }
   }
}

8 个代码加锁问题

通常在两个线程启动中加入主线程休眠语句,以保证先发线程抢到锁。

TimeUnit.SECONDS.sleep(1);                                   

单实例双同步方法

public class Lock1 {
   /**
    * 标准情况下 是先sendEmail() 还是先callPhone()?
    * 答案:sendEmail
    * 解释:被 synchronized 修饰的方式,锁的对象是方法的调用者
    * 所以说这里两个方法调用的对象是同一个,先调用的先执行!
    */
   public static void main(String[] args) {
       Phone1 phone = new Phone1();
       //锁的存在
       new Thread(() -> {
           phone.sendSms();
       }, "A").start();
       new Thread(() -> {
           phone.call();
       }, "B").start();
   }
}

class Phone1 {
   public synchronized void sendSms() {
       System.out.println("发短信");
   }

   public synchronized void call() {
       System.out.println("打电话");
   }
}

资源类休眠

public class Lock2 {

   /**
    * sendEmail()休眠三秒后  是先执行sendEmail() 还是 callPhone()
    * 答案: sendEmail
    * 解释:被 synchronized 修饰的方式,锁的对象是方法的调用者
    * 所以说这里两个方法调用的对象是同一个,先调用的先执行!
    */
   public static void main(String[] args) {
       Phone2 phone = new Phone2();
       //锁的存在
       new Thread(() -> {
           phone.sendSms();
       }, "A").start();
       new Thread(() -> {
           phone.call();
       }, "B").start();
   }
}

class Phone2 {
   public synchronized void sendSms() {
       try {
           TimeUnit.SECONDS.sleep(3);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("发短信");
   }

   public synchronized void call() {
       System.out.println("打电话");
   }
}

单实例普通方法混同步方法

public class Lock3 {
   /**
    * 被synchronized 修饰的方式和普通方法 先执行sendEmail() 还是 callPhone()
    * 答案: callPhone
    * 解释:新增加的这个方法没有 synchronized 修饰,不是同步方法,不受锁的影响!
    */
   public static void main(String[] args) {
       Phone3 phone = new Phone3();
       //锁的存在
       new Thread(() -> {
           phone.sendSms();
       }, "A").start();
       new Thread(() -> {
           phone.call();
       }, "B").start();
   }
}

class Phone3 {
   public synchronized void sendSms() {
       try {
           TimeUnit.SECONDS.sleep(3);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("发短信");
   }

   public void call() {
       System.out.println("打电话");
   }
}

双实例双同步方法

public class Lock4 {
   /**
    * 被synchronized 修饰的不同方法 先执行sendEmail() 还是callPhone()?
    * 答案:callPhone
    * 解释:被synchronized 修饰的不同方法 锁的对象是调用者
    * 这里锁的是两个不同的调用者,所有互不影响
    */
   public static void main(String[] args) {
       Phone4 phoneA = new Phone4();
       Phone4 phoneB = new Phone4();
       //锁的存在
       new Thread(() -> {
           phoneA.sendSms();
       }, "A").start();
       new Thread(() -> {
           phoneB.call();
       }, "B").start();
   }
}

class Phone4 {
   public synchronized void sendSms() {
       try {
           TimeUnit.SECONDS.sleep(4);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("发短信");
   }

   public synchronized void call() {
       System.out.println("打电话");
   }
}

单实例双静态方法

public class Lock5 {
   /**
    * 两个静态同步方法 都被synchronized 修饰 是先sendEmail() 还是callPhone()?
    * 答案:sendEmail
    * 解释:只要方法被 static 修饰,锁的对象就是 Class模板对象,这个则全局唯一!
    */
   public static void main(String[] args) {
       Phone5 phone = new Phone5();
       //锁的存在
       new Thread(() -> {
           phone.sendSms();
       }, "A").start();
       new Thread(() -> {
           phone.call();
       }, "B").start();
   }
}

class Phone5 {
   public static synchronized void sendSms() {
       try {
           TimeUnit.SECONDS.sleep(5);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("发短信");
   }

   public static synchronized void call() {
       System.out.println("打电话");
   }
}

单实例静态方法混同步方法

public class Lock6 {
   /**
    * 被synchronized 修饰的普通方法和静态方法  是先sendEmail() 还是 callPhone()?
    * 答案:callPhone
    * 解释:只要被static修饰锁的是class模板, 而synchronized 锁的是调用的对象
    * 这里是两个锁互不影响,按时间先后执行
    */
   public static void main(String[] args) {
       Phone6 phone = new Phone6();
       //锁的存在
       new Thread(() -> {
           Phone6.sendSms();
       }, "A").start();
       new Thread(() -> {
           phone.call();
       }, "B").start();
   }
}

class Phone6 {
   public static synchronized void sendSms() {
       try {
           TimeUnit.SECONDS.sleep(6);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("发短信");
   }

   public synchronized void call() {
       System.out.println("打电话");
   }
}

双实例双静态方法

public class Lock7 {
   /**
    * 两个静态同步方法 都被synchronized 修饰 是先sendEmail() 还是callPhone()?
    * 答案:sendEmail
    * 解释:只要方法被 static 修饰,锁的对象就是 Class模板对象,这个则全局唯一!
    */
   public static void main(String[] args) {
       Phone7 phoneA = new Phone7();
       Phone7 phoneB = new Phone7();
       //锁的存在
       new Thread(() -> {
           phoneA.sendSms();
       }, "A").start();
       new Thread(() -> {
           phoneB.call();
       }, "B").start();
   }
}

class Phone7 {
   public static synchronized void sendSms() {
       try {
           TimeUnit.SECONDS.sleep(7);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("发短信");
   }

   public static synchronized void call() {
       System.out.println("打电话");
   }
}

双实例静态方法混同步方法

public class Lock8 {
   /**
    * 一个被static+synchronized 修饰的方法和普通的synchronized方法,先执行sendEmail()还是callPhone()?
    * 答案:callPhone()
    * 解释: 只要被static 修饰的锁的就是整个class模板
    * 这里一个锁的是class模板 一个锁的是调用者
    * 所以锁的是两个对象 互不影响
    */
   public static void main(String[] args) {
       Phone8 phoneA = new Phone8();
       Phone8 phoneB = new Phone8();
       //锁的存在
       new Thread(() -> {
           phoneA.sendSms();
       }, "A").start();
       new Thread(() -> {
           phoneB.call();
       }, "B").start();
   }
}

class Phone8 {
   public static synchronized void sendSms() {
       try {
           TimeUnit.SECONDS.sleep(8);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       System.out.println("发短信");
   }

   public synchronized void call() {
       System.out.println("打电话");
   }
}

总结

  • 同步方法锁对象,可以被多实例绕开
  • 静态同步方法锁类,无法被多实例绕开
  • 两者混合,不是相同锁
  • 不建议使用通过类实例访问静态成员,应该直接使用类访问静态成员

不安全的集合类

List

public class UnSafeList {
   public static void main(String[] args) {
       List<String> list = new ArrayList<>();
       for (int i = 0; i < 10; i++) {
           new Thread(() -> {
               list.add(UUID.randomUUID().toString().substring(0, 5));
               System.out.println(list+"("+Thread.currentThread().getName()+")");
           }, String.valueOf(i)).start();
       }
   }
}                                                       
  • java.util.ConcurrentModificationException异常

    不同线程同时操作了同一 list 索引元素抛出的异常。 QQ截图20220127144928

  • 解决方法

    • 集合自带的线程安全的list

      List<String list = new Vector<(); 
      
    • Collections工具类强行上锁

      List<String list =Collections.synchronizedList(new ArrayList<());
      
    • JUC包下的读写数组CopyOnWriteArrayList,读写分离

      List<String list = new CopyOnWriteArrayList<();     
      
  • CopyOnWriteArrayList

    • 介绍
      • CopyOnWriteArrayList,写数组的拷贝,支持高效率并发且是线程安全的, 读操作无锁的 ArrayList。所有可变操作都是通过对底层数组进行一次新的复制来实现。
      • CopyOnWriteArrayList,适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在扩容的概念,每次写操作都要复制一个副本,在副本的基础上修改后改变 Array 引用。CopyOnWriteArrayList 中写操作需要大面积复制数组,所以性能差。
      • CopyOnWriteArrayList,慎用 ,因为谁也没法保证 CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次 add/set 都要重新复制数组,代价高昂。
    • 缺点
      • 由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致 young gc 或者 full gc
        • young gc :年轻代(Young Generation):对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的 GC 机制清理掉(IBM 的研究表明,98% 的对象都是很快消亡的),这个 GC 机制被称为 Minor GC 或叫 Young GC
        • 年老代(Old Generation):对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次 Young GC 后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代大,能存放更多的对象,在年老代上发生的 GC 次数也比年轻代少。当年老代内存不足时,将执行 Major GC,也叫 Full GC
      • 不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个 set 操作后,读取到数据可能还是旧的, 虽然 CopyOnWriteArrayList 能做到最终一致性, 但是还是没法满足实时性要求;
    • 总结
      • CopyOnWriteArrayList 这是一个 ArrayList 的线程安全的变体,其原理大概可以通俗的理解为: 初始化的时候只有一个容器,很长一段时间,这个容器数据、数量等没有发生变化的时候,大家 (多个线程),都是读取(假设这段时间里只发生读取的操作) 同一个容器中的数据,所以这样大家读到的数据都是唯一、一致、安全的,但是后来有人往里面增加了一个数据,这个时候 CopyOnWriteArrayList 底层实现添加的原理是先 copy 出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据。

Set

public class UnSafeSet {
   public static void main(String[] args) {
       Set<Object> set = new HashSet<>();
       for (int i = 0; i < 10; i++) {
           new Thread(() -> {
               set.add(UUID.randomUUID().toString().substring(0, 5));
               System.out.println(set+"("+Thread.currentThread().getName()+")");
           }, String.valueOf(i)).start();
       }
   }
}                                                         
  • 解决方法

    • 用collections工具类强行上锁

      Set<Object set = Collections.synchronizedSet(new HashSet<());  
      
    • JUC包下的读写SetCopyOnWriteArraySet,底层为CopyOnWriteArrayList

      Set<String set  = new CopyOnWriteArraySet<();
      

Map

public class UnSafeMap {
   public static void main(String[] args) {
       // 默认等价于什么? new HashMap<>(16,0.75);
       Map<String, Object> map = new HashMap<>();
       for (int i = 0; i < 10; i++) {
           new Thread(() -> {
               map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
               System.out.println(map+"("+Thread.currentThread().getName()+")");
           }, String.valueOf(i)).start();
       }
   }
}                                                         
  • 解决方法

    • Hashtable

      Hashtable<Object, Object map = new Hashtable<();  
      
    • JUC包下线程安全的ConcurrentHashMap

      Map<String, Object map = new ConcurrentHashMap<(); 
      

Callable详解

比较推荐的一种线程创建方式。

多线程中提到的两种创建线程的方法。 一种是通过创建 Thread 类,另一种是通过使用 Runnable 创建线程。但是,Runnable 缺少的一项功能是,当线程终止时(即 run()完成时),我们无法使线程返回结果。为了支持此功能,Java 中提供了 Callable 接口。

  • 为了实现Runnable,需要实现不返回任何内容的run()方法,而对于Callable,需要实现在完成时返回结果的call()方法。请注意,不能使用Callable创建线程,只能使用Runnable创建线程。

  • 另一个区别是call()方法可以引发异常,而run()则不能。

  • 为实现Callable而必须重写call方法。

  • 线程池启动(前面介绍过)
public class CallableTest implements Callable<Boolean {      
   @Override                                                    
   public Boolean call() throws Exception {                     
   	System.out.println(Thread.currentThread().getName() + "线程方法被调用"); 
   	return true;                                                 
   }                                                            
   public static void main(String[] args) {                     
       CallableTest callable = new CallableTest();                  
       //创建执行服务                                               
       ExecutorService service = Executors.newFixedThreadPool(1);   
       //提交执行                                                   
       Future<Boolean result = service.submit(callable);            
       //获取结果                                                   
       boolean isTrue = result.get();                               
       //关闭服务                                                   
       service.shutdownNow();                                       
   }                                                            
}                                                            
  • FutureTask启动

    本质是借助FutureTask,包装Callable接口的实现类,然后传递给Thread线程执行

  • Futrue接口

    ​ 当call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用Future对象。将Future视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦Callable返回)。因此,Future基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写5种方法,但是由于下面的示例使用了库中的具体实现,因此这里仅列出了重要的方法。

    • public boolean cancel(boolean mayInterrupt):用于停止任务。如果尚未启动,它将停止任务。如果已启动,则仅在mayInterrupt为true时才会中断任务。

    • public Object get()抛出InterruptedException,ExecutionException:用于获取任务的结果。如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。

    • public boolean isDone():如果任务完成,则返回true,否则返回false

    ​ 可以看到Callable和Future做两件事:Callable与Runnable类似,因为它封装了要在另一个线程上运行的任务;而Future用于存储从另一个线程获得的结果。实际上,future也可以与Runnable一起使用。
    ​ 要创建线程,需要Runnable。为了获得结果,需要future。

    ​ Java库具有具体的FutureTask类型,该类型实现Runnable和Future,并方便地将两种功能组合在一起。
    可以通过为其构造函数提供Callable来创建FutureTask。然后,将FutureTask对象提供给Thread的构造函数以创建Thread对象。因此,间接地使用Callable创建线程。

  • 关系图 QQ截图20220129162308

  • 实现

public class CallableTest implements Callable<Boolean {      
   @Override                                                    
   public Boolean call() throws Exception {                     
       System.out.println(Thread.currentThread().getName() + "线程方法被调用"); 
       return true;                                                 
   }                                                            
public class CallableTest {
   public static void main(String[] args) throws ExecutionException, InterruptedException {
       //直接包装 
       new Thread(new FutureTask(new CallableTest())).start();
       //lambda表达式
       new Thread(new FutureTask<Boolean>(()->{
           System.out.println(Thread.currentThread().getName() + "线程方法被调用");
           return true;
       })).start();
   }
}                                                            

JUC常用辅助类

CountDownLatch

见名知意:倒计时锁存器,阻塞主线程

public class CountDownLatchDemo {
   public static void main(String[] args) throws InterruptedException {
       CountDownLatch downLatch = new CountDownLatch(6);
       for (int i = 0; i < 6; i++) {
           new Thread(() -> {
               System.out.println(Thread.currentThread().getName() + " Go out");
               downLatch.countDown();//数量减一
           }, String.valueOf(i)
           ).start();
       }
       downLatch.await();//等待
       System.out.println("Finish");
   }
}

CyclicBarrier

见名知意:循环障碍,加法计时器,阻塞所有线程

public class CyclicBarrierDemo {
   public static void main(String[] args) {
       CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
           System.out.println("召唤神龙成功");
       });
       for (int i = 1; i <= 7; i++) {
           /**
            * 匿名内部类访问局部变量需要加final,lambda同理
            */
           final int temp = i;
           new Thread(() -> {
               System.out.println(Thread.currentThread().getName() + "收集到第" + temp + "个龙珠");
               try {
                   cyclicBarrier.await();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               } catch (BrokenBarrierException e) {
                   e.printStackTrace();
               }
           }).start();
       }
   }
}

Semaphore

见名知意:信号量,可近似看作资源池。

public class SemaphoreDemo {
   public static void main(String[] args) {
       Semaphore semaphore = new Semaphore(3);
       for (int i = 1; i <= 6; i++) {
           new Thread(() -> {
               try {
                   semaphore.acquire(); // 得到资源
                   System.out.println(Thread.currentThread().getName() + "抢到车位

推荐阅读