多线程进阶-JUC并发编程(上篇)

一、JUC

1.JUC相关知识点汇总

在这里插入图片描述

2.什么是JUC

JUC:java.util.concurrent包名的简写,是关于并发编程的API。
与JUC相关的有三个包:java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks。

二、进程与线程

1、进程与线程

进程:程序的一次执行过程,是系统运行的基本单位,因此进程是动态的(很多次执行)。系统运行一个程序是一个进程创建、运行到消亡的过程。

在JAVA中,当我们启动Main函数其实就是启动了一个JVM的进程,而Main函数所在的线程就是这个进程中的一个线程,也称为主线程。

在Windows中,我们可以通过任务管理器看我们电脑运行着这那些进程。

Java默认有2个线程:main和 GC

线程:是一个比进程更小的执行单位。一个进程在其执行过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、本地方法栈、虚拟机栈。所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

对于Java而言,创建线程: Thread. Runnable. Callable,线程池

2.Java真的可以开启线程吗?

不行,看源码可知调用了native方法,底层为C++

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

3.并发和并行

并发(多线程操作同一个资源)
●CPU单核,模拟出来多条线程,快速交替

并行(多个人一起行走)
●CPU多核,多个线程可以同时执行;

查看cpu核数

public class Test1 {
    public static void main(String[] args){
        //获取cpu的核数
        //CPU密集型和IO密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

并发编程的本质:充分利用CPU的资源

4.线程状态

public enum State {
       	//新生
        NEW,
      	//运行
        RUNNABLE,
    	//阻塞
        BLOCKED,
       //等待,死等
        WAITING,
       //超时等待
        TIMED_WAITING,
		//终止
        TERMINATED;
    }

5.wait/sleep区别

(1)来自于不同的类
wait==》Object
sleep==》Thread

(2)锁的释放
wait 会释放锁
sleep ,休眠,不释放锁

(3)使用范围
wait:必须在同步代码块中
sleep: 可以在任何位置

三、Lock 锁(重点)

1.不加锁(同步监视器)

会出现线程争抢资源的情况,不安全

public class SaleTicketDemo_synchronized{
    public static void main(String[] args){
        //并发:多线程操作同一个资源类
        //创建资源类
        Ticket ticket = new Ticket();

        /*
            //,使用匿名内部类创建线程
        new Thread(new Runnable() {
            public void run() {
            }
        }).start()
         */
        ;

        //lambda创建线程   ->lambda表达式(参数)->{代码}
     	new Thread(()->{
            for (int i = 1;i<50;i++) ticket.sale();},"A").start();
        
        new Thread(()->{
            for (int i = 1;i<50;i++) ticket.sale();},"B").start();
        
        new Thread(()->{
            for (int i = 1;i<50;i++) ticket.sale();},"C").start();

    }
}

//线程是一个单独的资源类
class Ticket{
    //属性,方法
    private  int number = 50;

    public  void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"-->卖出了第"+(number--)+"张票,剩余"+number+"张票");
        }
    }
}

在这里插入图片描述

2.synchronized-自动锁

但会出现一个线程多次抢占资源使用

  public synchronized void sale(){
        if (number>0){
            System.out.println(Thread.currentThread().getName()+"-->卖出了第"+(number--)+"张票,剩余"+number+"张票");
        }
    }

在这里插入图片描述

3.Lock-手动锁

(1)加锁和解锁
在这里插入图片描述

(2)实现类
在这里插入图片描述
(3)公平锁

//公平锁:排队
Lock lock = new ReentrantLock(true);

部分代码

class Ticket2{
    //属性,方法
    private  int number = 50;

    Lock lock = new ReentrantLock(true);

    public void sale(){
        lock.lock();//加锁

        try {
            if (number>0){
                System.out.println(Thread.currentThread().getName()+"-->卖出了第"+(number--)+"张票,剩余"+number+"张票");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
        }
    }
}

截图
在这里插入图片描述

(4)非公平锁

//非公平锁:默认,可以插队
Lock lock = new ReentrantLock();

部分代码

class Ticket2{
    //属性,方法
    private  int number = 50;

    Lock lock = new ReentrantLock();

    public void sale(){
        lock.lock();//加锁

        try {
            if (number>0){
                System.out.println(Thread.currentThread().getName()+"-->卖出了第"+(number--)+"张票,剩余"+number+"张票");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
        }
    }
}

截图
在这里插入图片描述
(5)Synchronized 和Lock区别
1、Synchronized 内置的Java关键字 ,Lock 是一个java类
2、Synchronized 无法判断获取锁的状态, Lock可以判断是否获取到了锁
3、Synchronized 会自动释放锁, lock必须要手动释放锁!如果不释放锁,死锁
4、Synchronized 线程1 (获得锁,阻塞)、线程2(等待,死等) ; Lock锁就不一定会等待下去(lock.tryLock();尝试获取锁);
5、Synchronized 可重入锁,不可以中断的,非公平; Lock ,可重入锁,可以判断锁,非公平(可以自己设置)
6、Synchronized 适合锁少量的代码同步问题, Lock适合锁大量的同步代码!

四、生产者消费者问题

1.synchronized、wait、notify传统生产者消费者问题

注意必须使用while循环进行临界值判断,若为if作为判断条件,只会判断一次,所以 如果是多个生产者和多个消费者,可能会出现虚假唤醒,即生产者notifyAll唤醒后,可能是另外一个正在wait阻塞的生产者抢到了锁,使得会继续生产,而不是去消费,唤醒是唤醒了多个,但只会有一个抢到了锁。但如果使while则会多次进行判断,使得一个生产者拿到锁,则另一个生产者会去等待。

public class Demo1 {
    public static void main(String[] args){
        //创建资源类
        Data data = new Data();

        //生产者线程1
        new Thread(()->{
            for (int i=0; i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"生产者1").start();

        //消费者线程1
        new Thread(()->{
            for (int i=0; i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"消费者1").start();

        //生产者线程2
        new Thread(()->{
            for (int i=0; i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"生产者2").start();

        //消费者线程2
        new Thread(()->{
            for (int i=0; i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"消费者2").start();

    }
}

//判断等待,业务,通知
class Data{
    private int number = 0;

    //生产资源+1
    public synchronized void  increment() throws InterruptedException {
        while (number != 0){ //number==0 则干活,执行num++
            //等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知消费者线程,我生产完了(+1)
        this.notifyAll();
    }

    //消费资源-1
    public synchronized void  decrement() throws InterruptedException {
        while (number == 0){//number==1 则干活,执行num--
            //等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知生产者线程,我消费完了(-1)
        this.notifyAll();
    }
}

截图
在这里插入图片描述

2.Lock、await、signal新版生产者消费者问题

Lock替换synchronized方法和语句的使用,Condition取代了对象监视器方法的使用

2.1一个生产者一个消费者


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

public class Demo2 {
    public static void main(String[] args){
        //创建资源类
        Data2 data = new Data2();

        //生产者线程
        new Thread(()->{
            for (int i=0; i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"生产者").start();

        //消费者线程
        new Thread(()->{
            for (int i=0; i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"消费者").start();


    }
}

//判断等待,业务,通知
class Data2{
    private int number = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();


    //生产资源+1
    public void  increment() throws InterruptedException {
        lock.lock();//加索
        try {
            while (number != 0){ //number==0 则干活,执行num++
                //等待
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知消费者线程,我生产完了(+1)
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//释放锁
        }
    }

    //消费资源-1
    public void  decrement() throws InterruptedException {
        lock.lock();//加锁
        try {
            while (number == 0){//number==1 则干活,执行num--
                //等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知生产者线程,我消费完了(-1)
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//释放锁
        }
    }
}

截图
在这里插入图片描述
2.2 condition实现精准通知唤醒

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

public class Demo {
    public static void main(String[] args){
        Data3 data = new Data3();
        new Thread(()->{
            for (int i=0 ; i<10 ; i++){
                data.printA();
            }
        },"A").start();

        new Thread(()->{
            for (int i=0 ; i<10 ; i++){
                data.printB();
            }
        },"B").start();

        new Thread(()->{
            for (int i=0 ; i<10 ; i++){
                data.printC();
            }
        },"C").start();

    }
}


class Data3{
    private Lock lock = new ReentrantLock();
    //需要三个condition监视器去精确通知唤醒
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1;

    public void printA(){
        lock.lock();//加锁
        try {
            while(number != 1 ){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+":第1个执行");
            //唤醒指定的人
            number = 2;
            condition2.signal();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();//加锁
        try {
            while(number != 2 ){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+":第2个执行");
            //唤醒指定的人
            number = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();//加锁
        try {
            while(number != 3 ){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+":第3个执行");
            //唤醒指定的人
            number = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

部分截图
在这里插入图片描述

五、八锁问题

1.一个对象,两个线程先打印,哪个方法先执行?
发短信-》打电话
2.sendMs延迟5秒,两个线程谁先执行?
发短信-》打电话
synchronized锁的对象是方法的调用者
一个对象,两个方法用的是同一把锁,谁先拿到谁执行

public class Demo1 {
    public static void main(String[] args){
        Phone phone = new Phone();
        
        new Thread(()->{
            phone.sendMs();
            try {
                TimeUnit.SECONDS.sleep(1);//休息1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{phone.call();},"B").start();
    }
}

class Phone{
    //synchronized锁的对象是方法的调用者
    //两个方法用的是同一把锁,谁先拿到谁执行
    public synchronized void sendMs(){
        try {
            TimeUnit.SECONDS.sleep(5);//休息5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

3.一个对象,增加一个普通方法后,先执行哪个方法 ?
听音乐-》发短信
listenMc没有锁,不是同步方法,不受锁的影响,会先执行

public class Demo2 {
    public static void main(String[] args){
        Phone2 phone = new Phone2();
        //
        new Thread(()->{
            phone.sendMs();
            try {
                TimeUnit.SECONDS.sleep(1);//休息1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{phone.listenMc();},"B").start();
    }
}
class Phone2{
    //synchronized锁的对象是方法的调用者
    public synchronized void sendMs(){
        try {
            TimeUnit.SECONDS.sleep(5);//休息5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
    //没有锁,不是同步方法,不受锁的影响
    public void listenMc(){
        System.out.println("听音乐");
    }
}

4.两个对象,两个同步方法,先执行哪一个?
两个对象,两把不同的锁,不存在资源的竞争,并且sendMs,存在延迟,所以先打电话后发短信

public class Demo3 {
    public static void main(String[] args){
        //两个对象,两把不同的锁,不存在资源的竞争,并且sendMs,存在延迟,所以先打电话后发短信
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();

        
        new Thread(()->{
            phone1.sendMs();
            try {
                TimeUnit.SECONDS.sleep(1);//休息1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{phone2.call();},"B").start();
    }
}

class Phone3{
    //synchronized锁的对象是方法的调用者
    public  synchronized void sendMs(){
        try {
            TimeUnit.SECONDS.sleep(5);//休息5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

5.增加两个静态方法,只有一个对象,谁先执行?
6.增加两个静态方法,两个对象,谁先执行?

static修饰为静态方法,类一加载的时候就只有一个Phone3.Class模板,所以不管有几个对象,只有一个类锁,所以先发短信后打电话

public class Demo4 {
    public static void main(String[] args){
        //两个对象,两把锁,但是只有一个Class类模板,static 锁的是Class
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        
        new Thread(()->{
            phone1.sendMs();
            try {
                TimeUnit.SECONDS.sleep(1);//休息1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{phone2.call();},"B").start();
    }
}

class Phone4{
    //synchronized锁的对象是方法的调用者
    //static修饰为静态方法,类一加载的时候就有了,锁的是Class,所以只有一个类锁
    public static synchronized void sendMs(){
        try {
            TimeUnit.SECONDS.sleep(5);//休息5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

7.一个对象,一个静态同步方法,一个普通的同步方法,谁先执行?
8.两个对象,一个静态同步方法,一个普通的同步方法,谁先执行?
一旦声明为static的同步方法,这个方法只依赖于类锁,一个static 锁的是Class,一个是对象锁,互不影响,且sendMs有延迟,所以先打电话后发短信,

public class Demo6 {
    public static void main(String[] args){
        //两个对象,两把锁,一个static 锁的是Class,一个是对象锁,互不影响,且sendMs有延迟,所以先打电话后发短信,
        Phone6 phone1 = new Phone6();
        Phone6 phone2 = new Phone6();

        new Thread(()->{
            phone1.sendMs();
            try {
                TimeUnit.SECONDS.sleep(1);//休息1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();

        new Thread(()->{phone2.call();},"B").start();
    }
}

class Phone6{
    //synchronized锁的对象是方法的调用者
    //static修饰为静态方法,类一加载的时候就有了,锁的是Class,所以只有一个类锁
    public static synchronized void sendMs(){
        try {
            TimeUnit.SECONDS.sleep(5);//休息5秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

总结:普通的同步方法为对象锁,static修饰的同步方法为类锁。

六、集合类不安全问题

1.单线程安全

public class ListTest {
    public static void main(String[] args){
        List list = new ArrayList();

        for (int i =1 ; i<=10 ; i++ ){
            list.add(UUID.randomUUID().toString().substring(0,8));
            System.out.println(list);
        }
    }
}

在这里插入图片描述

2.多线程不安全

public class ListTest {
    public static void main(String[] args){
        List list = new ArrayList();

        for (int i =1 ; i<=10 ; i++ ){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },"线程"+i).start();
        }
    }
}

在这里插入图片描述
执行失败:报出java.util.ConcurrentModificationException并发修改异常,线程不安全

3.多线程出现不安全的原因

线程安全:多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染

线程不安全:不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

List接口下面有两个实现,一个是ArrayList,另外一个是vector。
ArrayList类

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

vector类

 public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }

根据源码可知,因为Vector的自带的方法前加了synchronized 关键字,Vector本身是线程安全的,即单独调用它的函数也是线程安全的(但是集合类的复合操作无法保证其线程安全性详情参考),但是正因为有synchronized 的修饰,所以效率低,而ArrayList确是和它相反的,效率高,但是不安全。

一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成:

  1. 在 Items[Size] 的位置存放此元素;
  2. 增大 Size 的值。

在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;

而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (我们假设的是添加一个元素是要两个步骤,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。 其实元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

4.解决方案

1.Vector类

List<String> list = new Vector<>();

源码

 public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }

保证了add方法的线程安全。保证了数据的一致性,但由于加锁导致访问性能大大降低。

2.Collections工具类

用Collections工具类将线程不安全的ArrayList类转换为线程安全的集合类。

List<String> list = Collections.synchronizedList(new ArrayList<>());

3.CopyOnWriteArrayList类(写入复制,读写分离)
CopyOnWrite 写入时复制 ---- COW :计算机程序设计领域的一 种优化策略;
多个线程调用的时候,list, 读取的时候是固定的
在写入的时候避免覆盖,造成数据问题,写的时候会先把原来的数据复制一份,所以读写分离

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

为什么用CopyOnWriteArrayList,不用Vector?
因为Vector的add方法被synchronized修饰,所以性能低,而看CopyOnWrite源码可知用的是lock锁,效率高

 public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //写时复制
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            //写完后,再set回去
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

同理Set也可以用Collections工具类和CopyOnWriteArrayset来解决

//1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
    Set<String> set = new CopyOnWriteArraySet<>();

Hashset底层

public HashSet() {
        map = new HashMap<>();
    }
    //set的本质就是不重复的key
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
//PRESENT为常量,不变的值
private static final Object PRESENT = new Object();

由源码可知hashset底层就是创建了一个HashMap,set的本质就是不重复的key,PRESENT为常量,不变的值

map也存在ConcurrentModificationException,线程的不安全问题,
解决方法,可以用工具类Collections.synchronizedMap

Map<String,String> map = Collections.synchronizedMap(new HashMap<>());

另一钟解决方案ConcurrentHashMap

public class MapTest {
    public static void main(String[] args){
        //Map<String,String> map = new HashMap<>();
        //1.Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
        Map<String,String> map = new ConcurrentHashMap<>();


        for (int i =1 ; i<=20 ; i++ ){
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,4));
                System.out.println(map);
            },"线程"+i).start();
        }
    }
}

七、Callable

1.callable与runnable不同点

在这里插入图片描述

在这里插入图片描述

(1).可以有返回值,
(2).可以抛出异常
(3).方法不同 run()->call()
(4).支持泛型,泛型和返回值类型相同

2.Callable 创建线程

(1)创建实现类对象
(2)将实现类对象作为参数,创建FutureTask 适配器
(3)将适配器作为参数,创建Thread

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new MyThread());//适配类

        new Thread(futureTask,"A").start();

        //获取callable的返回结果
        Integer res = (Integer) futureTask.get();
        System.out.println(res);
    }
}

class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("call方法已经执行");
        return 2021;
    }
}

3.细节

(1)有缓存
两个Thread都调用call方法,但是只有一个输出结果,原因是有一个结果会被缓存,效率高

new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();//结果会被缓存,效率高

(2)结果可能会有等待,会阻塞
call()方法内可能包含耗时的操作,futureTask.get()方法可能产生阻塞,可以把它放到最后,或者使用异步通信来处理

八、常用辅助类

1、CountDownLatch-计数器

方法一:countDownLatch.countDown();//数量-1
方法二: countDownLatch.await();//等待计数器归0然后再执行后面的
每次有线程调用countDown()数量-1 ,假设计数器变为0 , countDownLatch.await()就会被唤醒,继续执行!

场景:假设有一个教师有10个人,只有10个人都离开教师才关门

//计数器
public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        //总数为10
        CountDownLatch countDownLatch = new CountDownLatch(10);

        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"回家了");
                countDownLatch.countDown();//数量-1
            },"线程"+i).start();
        }
        countDownLatch.await();//等待计数器归0然后再执行下面的操作
        System.out.println("教室内10个学生走完了,关门!!");
    }
}

2、CyclicBarrier-加法计数器

场景:集齐7颗龙珠召唤成龙

//加法计数器
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤成龙成功");
        });
        for (int i = 1; i <= 7; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName());
                try {
                    cyclicBarrier.await();//等待
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },"收集第"+i+"颗龙珠").start();
        }
    }
}

3、Semaphore-信号量

方法一: semaphore.acquire();//请求,获得,假设已经满了,就等待,等待被释放为止。
方法二: semaphore.release(); //释放 ,会将当前的信号释放+1,然后唤醒等待的线程

作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!

场景:6辆车抢3个停车位

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()+"正在停车");
                    TimeUnit.SECONDS.sleep(3);//停3秒
                    System.out.println(Thread.currentThread().getName()+"已经离开");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //释放
                    semaphore.release();
                }

            },"车辆"+i).start();
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值