JUC并发编程(基础入门一)——线程和进程、Lock、生产者消费者、8锁现象、集合不安全、callable、常用辅助类

1JUC并发编程

在这里插入图片描述
java.util工具包
业务普通的线程代码Thread,效率不高
Runnable 没有返回值、效率相对于Callable较低!
在这里插入图片描述

2线程和进程

线程、进程,如果不能使用一句话说出来,就是不扎实!

1进程

进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

java 默认有几个线程? 两个——main线程、GC线程

java真的可以开启线程? 开不了的,Java是没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。

2并发

并发:多个任务在同一个 CPU 核上,按细分的时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。**并发编程的本质:**充分利用CPU的资源!

3并行

并行:单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的“同时进行”。
CPU的获取方式:

public class Test {
    
    
    public static void main(String[] args) {
    
    
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

4线程的状态

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

5wait sleep区别

1、来自不同的类;wait => Object sleep => Thread
2、关于锁的释放;wait 会释放锁;sleep不会释放锁;
3、使用的范围是不同的;wait 必须在同步代码块中;sleep 可以在任何地方睡;
4、是否需要捕获异常;wait是不需要捕获异常;sleep必须要捕获异常;
5、用途不同:wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。

3Lock(重点)

2Lock

在这里插入图片描述
公平锁: 十分公平,必须先来后到;
非公平锁: 十分不公平,可以插队;(Lock默认为非公平锁)
在这里插入图片描述

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

public class Test {
    
    
    public static void main(String[] args) {
    
    
        final Ticket2 ticket = new Ticket2();

        new Thread(() -> {
    
    
            for (int i = 0; i < 40; i++) {
    
    
                ticket.sale();
            }
        }, "A").start();
        new Thread(() -> {
    
    
            for (int i = 0; i < 40; i++) {
    
    
                ticket.sale();
            }
        }, "B").start();
        new Thread(() -> {
    
    
            for (int i = 0; i < 40; i++) {
    
    
                ticket.sale();
            }
        }, "C").start();
    }
}
//lock三部曲
//1、    Lock lock=new ReentrantLock();
//2、    lock.lock() 加锁
//3、    finally=> 解锁:lock.unlock();
class Ticket2 {
    
    
    private int number = 30; //在堆中

    // 创建锁
    Lock lock = new ReentrantLock();
    //卖票的方式
    public  void sale() {
    
    
        lock.lock(); // 开启锁
        try {
    
    
            if (number > 0) {
    
    
                System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票剩余" + number + "张票");
            }
        }finally {
    
    
            lock.unlock(); // 关闭锁
        }

    }
}

3JUC中的 Lock 接口是什么?对比Synchronized它有什么优势?

1、Synchronized 内置的Java关键字,Lock是一个Java类
2、Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到锁**【优势1】**
3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁
4、Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间 【优势2】
5、Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;【优势3】
6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;

4生产者和消费者问题

1Synchronzied 版本

public class ConsumeAndProduct {
    
    
    public static void main(String[] args) {
    
    
        Data data = new Data();

        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();
    }
}

class Data {
    
    
    private int num = 0;

    // +1
    public synchronized void increment() throws InterruptedException {
    
    
        // 判断等待
        if (num != 0) {
    
    
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "=>" + num);
        // 通知其他线程 +1 执行完毕
        this.notifyAll();
    }

    // -1
    public synchronized void decrement() throws InterruptedException {
    
    
        // 判断等待
        if (num == 0) {
    
    
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "=>" + num);
        // 通知其他线程 -1 执行完毕
        this.notifyAll();
    }
}

A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0
A=>1
B=>0

存在问题(虚假唤醒)
问题,如果有四个线程,会出现虚假唤醒
在这里插入图片描述
在这里插入图片描述
解决方式 ,if 改为while即可,防止虚假唤醒 wait是会释放锁的!!!
结论:就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
这也就是为什么用while而不用if的原因了,因为线程被唤醒后,执行开始的地方是wait之后

2Lock版

在这里插入图片描述

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

public class LockCAP {
    
    
    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();
                }

            }
        }, "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();
    }
}

class Data2 {
    
    
    private int num = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    // +1
    public  void increment() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            // 判断等待
            while (num != 0) {
    
    
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            // 通知其他线程 +1 执行完毕
            condition.signalAll();
        }finally {
    
    
            lock.unlock();
        }

    }

    // -1
    public  void decrement() throws InterruptedException {
    
    
        lock.lock();
        try {
    
    
            // 判断等待
            while (num == 0) {
    
    
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "=>" + num);
            // 通知其他线程 +1 执行完毕
            condition.signalAll();
        }finally {
    
    
            lock.unlock();
        }

    }
}

在这里插入图片描述

Condition的优势 : 精准的通知和唤醒的线程!如果我们要指定通知的下一个进行顺序怎么办呢? 我们可以使用Condition来指定通知进程
在这里插入图片描述

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

/**
 * Description:
 * A 执行完 调用B
 * B 执行完 调用C
 * C 执行完 调用A
 **/

public class ConditionDemo {
    
    
    public static void main(String[] args) {
    
    
        Data3 data3 = new Data3();

        new Thread(() -> {
    
    
            for (int i = 0; i < 10; i++) {
    
    
                data3.printA();
            }
        },"A").start();
        new Thread(() -> {
    
    
            for (int i = 0; i < 10; i++) {
    
    
                data3.printB();
            }
        },"B").start();
        new Thread(() -> {
    
    
            for (int i = 0; i < 10; i++) {
    
    
                data3.printC();
            }
        },"C").start();
    }

}
class Data3 {
    
    
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1; // 1A 2B 3C

    public void printA() {
    
    
        lock.lock();
        try {
    
    
            // 业务代码 判断 -> 执行 -> 通知
            // 阻塞条件
            while (num != 1) {
    
    
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "==> AAAA" );
            num = 2;
            condition2.signal();
        }catch (Exception e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            lock.unlock();
        }
    }
    public void printB() {
    
    
        lock.lock();
        try {
    
    
            // 业务代码 判断 -> 执行 -> 通知
            while (num != 2) {
    
    
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "==> BBBB" );
            num = 3;
            condition3.signal();
        }catch (Exception e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            lock.unlock();
        }
    }
    public void printC() {
    
    
        lock.lock();
        try {
    
    
            // 业务代码 判断 -> 执行 -> 通知
            while (num != 3) {
    
    
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "==> CCCC" );
            num = 1;
            condition1.signal();
        }catch (Exception e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            lock.unlock();
        }
    }
}
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
A==> AAAA
B==> BBBB
C==> CCCC
。。。。。。

5 8锁现象

如何判断锁的是谁!锁到底锁的是谁?
锁会锁住:对象、Class
深刻理解我们的锁

问题1
两个同步方法,先执行发短信还是打电话

import java.util.concurrent.TimeUnit;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Phone phone = new Phone();

        new Thread(() -> {
    
     phone.sendMs(); }).start();
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        new Thread(() -> {
    
     phone.call(); }).start();
    }
}

class Phone {
    
    
    public synchronized void sendMs() {
    
    
        System.out.println("发短信");
    }
    public synchronized void call() {
    
    
        System.out.println("打电话");
    }
}

输出结果为
发短信
打电话

问题2:
我们再来看:我们让发短信 延迟4s

import java.util.concurrent.TimeUnit;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Phone phone = new Phone();

        new Thread(() -> {
    
     phone.sendMs(); }).start();
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        new Thread(() -> {
    
     phone.call(); }).start();
    }
}

class Phone {
    
    
    public synchronized void sendMs() {
    
    
        try {
    
    
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call() {
    
    
        System.out.println("打电话");
    }
}

现在结果是什么呢?
结果:还是先发短信,然后再打电话!
why?
原因:并不是顺序执行,而是synchronized 锁住的对象是方法的调用!对于两个方法用的是同一个锁,谁先拿到谁先执行,另外一个等待

问题三
加一个普通方法

import java.util.concurrent.TimeUnit;

public class Test {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Phone phone = new Phone();

        new Thread(() -> {
    
    
            try {
    
    
                phone.sendMs();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
    
     phone.hello(); }).start();
    }
}

class Phone {
    
    
    public synchronized void sendMs() throws InterruptedException {
    
    
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");
    }
    public synchronized void call() {
    
    
        System.out.println("打电话");
    }
    public void hello() {
    
    
        System.out.println("hello");
    }
}

输出结果为
hello
发短信
原因:hello是一个普通方法,不受synchronized锁的影响,不用等待锁的释放

问题四
如果我们使用的是两个对象,一个调用发短信,一个调用打电话,那么整个顺序是怎么样的呢?

import java.util.concurrent.TimeUnit;

public class Test {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
    
    
            try {
    
    
                phone1.sendMs();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
    
     phone2.call(); }).start();
    }
}

class Phone {
    
    
    public synchronized void sendMs() throws InterruptedException {
    
    
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");
    }
    public synchronized void call() {
    
    
        System.out.println("打电话");
    }
    public void hello() {
    
    
        System.out.println("hello");
    }
}

输出结果
打电话
发短信
原因:两个对象两把锁,不会出现等待的情况,发短信睡了4s,所以先执行打电话

问题五、六
如果我们把synchronized的方法加上static变成静态方法!那么顺序又是怎么样的呢?
(1)我们先来使用一个对象调用两个方法!

import java.util.concurrent.TimeUnit;

public class Test {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Phone phone = new Phone();

        new Thread(() -> {
    
    
            try {
    
    
                phone.sendMs();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
    
     phone.call(); }).start();
    }
}

class Phone {
    
    
    public static synchronized void sendMs() throws InterruptedException {
    
    
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");
    }
    public static synchronized void call() {
    
    
        System.out.println("打电话");
    }
}

答案是:先发短信,后打电话
(2)如果我们使用两个对象调用两个方法!

import java.util.concurrent.TimeUnit;

public class Test {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
    
    
            try {
    
    
                phone1.sendMs();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
    
     phone2.call(); }).start();
    }
}

class Phone {
    
    
    public static synchronized void sendMs() throws InterruptedException {
    
    
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");
    }
    public static synchronized void call() {
    
    
        System.out.println("打电话");
    }
}

答案是:还是先发短信,后打电话
原因是什么呢? 为什么加了static就始终前面一个对象先执行呢!为什么后面会等待呢?
原因是:对于static静态方法来说,对于整个类Class来说只有一份,对于不同的对象使用的是同一份方法,相当于这个方法是属于这个类的,如果静态static方法使用synchronized锁定,那么这个synchronized锁会锁住整个对象!不管多少个对象,对于静态的锁都只有一把锁,谁先拿到这个锁就先执行,其他的进程都需要等待!

问题七
如果我们使用一个静态同步方法、一个同步方法、一个对象调用顺序是什么?

import java.util.concurrent.TimeUnit;

public class Test {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Phone phone = new Phone();
        new Thread(() -> {
    
    
            try {
    
    
                phone.sendMs();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
    
     phone.call(); }).start();
    }
}

class Phone {
    
    
    public static synchronized void sendMs() throws InterruptedException {
    
    
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");
    }
    public synchronized void call() {
    
    
        System.out.println("打电话");
    }
}

输出结果
打电话
发短信
原因:因为一个锁的是Class类的模板,一个锁的是对象的调用者。所以不存在等待,直接运行。

问题八
如果我们使用一个静态同步方法、一个同步方法、两个对象调用顺序是什么?

import java.util.concurrent.TimeUnit;

public class Test {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();

        new Thread(() -> {
    
    
            try {
    
    
                phone1.sendMs();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
    
     phone2.call(); }).start();
    }
}

class Phone {
    
    
    public static synchronized void sendMs() throws InterruptedException {
    
    
        TimeUnit.SECONDS.sleep(4);
        System.out.println("发短信");
    }
    public  void call() {
    
    
        System.out.println("打电话");
    }

}

输出结果
打电话
发短信
原因:两把锁锁的不是同一个东西

小结
new 出来的 this 是具体的一个对象
static Class 是唯一的一个模板

6集合不安全

1List 不安全

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

//java.util.ConcurrentModificationException 并发修改异常!
public class Test {
    
    
    public static void main(String[] args) {
    
    

        List<Object> arrayList = new ArrayList<>();

        for(int i=1;i<=10;i++){
    
    
            new Thread(()->{
    
    
                arrayList.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(arrayList);
            },String.valueOf(i)).start();
        }
    }
}

会导致 java.util.ConcurrentModificationException 并发修改异常!
ArrayList 在并发情况下是不安全的
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        /**
         * 解决方案
         * 1. List<String> list = new Vector<>();
         * 2. List<String> list = Collections.synchronizedList(new ArrayList<>());
         * 3. List<String> list = new CopyOnWriteArrayList<>();
         */
        List<String> list = new CopyOnWriteArrayList<>();


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

CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略

核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源直到某个调用者视图修改资源内容时系统才会真正复制一份专用副本(private copy)给该调用者而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

读的时候不需要加锁如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。

CopyOnWriteArrayList 的设计思想
1 读写分离,读和写分开
2 最终一致性
3 使用另外开辟空间的思路,来解决并发冲突

CopyOnWriteArrayList比Vector厉害在哪里?

Vector底层是使用synchronized关键字来实现的:效率特别低下。
在这里插入图片描述
CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!
在这里插入图片描述

2set 不安全

Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;
解决方案还是两种:
使用Collections工具类的synchronized包装的Set类
使用CopyOnWriteArraySet 写入复制的JUC解决方案

public class SetTest {
    
    
    public static void main(String[] args) {
    
    
        /**
         * 1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
         * 2. Set<String> set = new CopyOnWriteArraySet<>();
         */
//        Set<String> set = new HashSet<>();
        Set<String> set = new CopyOnWriteArraySet<>();

        for (int i = 1; i <= 30; i++) {
    
    
            new Thread(() -> {
    
    
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

HashSet底层是什么?
hashSet底层就是一个HashMap
在这里插入图片描述

3Map不安全

HashMap的相信介绍我们在集合章节有解释!!!!
默认加载因子是0.75,默认的初始容量是16 **(这里是有问题)**见我们的java面试题专题中详细解答
在这里插入图片描述
同样的HashMap基础类也存在并发修改异常!

public class MapTest {
    
    
    public static void main(String[] args) {
    
    
        //map 是这样用的吗?  不是,工作中不使用这个
        //默认等价什么? new HashMap<>(16,0.75);
        /**
         * 解决方案
         * 1. Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
         *  Map<String, String> map = new ConcurrentHashMap<>();
         */
        Map<String, String> map = new ConcurrentHashMap<>();
        //加载因子、初始化容量
        for (int i = 1; i < 100; i++) {
    
    
            new Thread(()->{
    
    
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

什么是ConcurrentHashMap?
ConcurrentHashMap是Java中的一个线程安全且高效的HashMap实现。平时涉及高并发如果要用map结构,那第一时间想到的就是它。相对于hashmap来说,ConcurrentHashMap就是线程安全的map,其中利用了锁分段的思想提高了并发度。

那么它到底是如何实现线程安全的?
JDK 1.6版本关键要素:
segment继承了ReentrantLock充当锁的角色,为每一个segment提供了线程安全的保障;
segment维护了哈希散列表的若干个桶,每个桶由HashEntry构成的链表。
JDK1.8后,ConcurrentHashMap抛弃了原有的Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。

7Callable

说一下 runnable 和 callable 有什么区别?
相同点
1 都是接口
2 都可以编写多线程程序
3 都采用Thread.start()启动线程
主要区别
1 Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
2 Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理Callable 接口 call 方法允许抛出异常,可以获取异常信息

注:Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        for (int i = 1; i < 10; i++) {
    
    
            MyThread1 myThread1 = new MyThread1();

            FutureTask<Integer> futureTask = new FutureTask<>(myThread1);
            // 放入Thread中使用,结果会被缓存
            new Thread(futureTask,String.valueOf(i)).start();
            // 这个get方法可能会被阻塞,如果在call方法中是一个耗时的方法,所以一般情况我们会把这个放在最后,或者使用异步通信
            int a = futureTask.get();
            System.out.println("返回值:" + a);
        }

    }

}
class MyThread1 implements Callable<Integer> {
    
    

    @Override
    public Integer call() throws Exception {
    
    
        System.out.println("call()");
        return 1024;
    }
}

8常用的辅助类

1CountDownLatch 减法计数器

import java.util.concurrent.CountDownLatch;

public class Test {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        // 总数是6 必须要执行任务的时候,再使用!
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
    
    
            new Thread(() -> {
    
    
                System.out.println(Thread.currentThread().getName() + "==> Go Out");
                countDownLatch.countDown(); // 每个线程都数量 -1
            },String.valueOf(i)).start();
        }
        countDownLatch.await(); // 等待计数器归零 然后向下执行
        System.out.println("close door");
    }
}

主要方法:
countDown 减一操作;
await 等待计数器归零
await 等待计数器归零,就唤醒,再继续向下运行

2CyclicBarrier 加法计数器

在这里插入图片描述

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Test {
    
    
    public static void main(String[] args) {
    
    
        // 主线程
        //public CyclicBarrier(int parties, Runnable barrierAction)
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {
    
    
            System.out.println("召唤神龙");
        });

        for (int i = 1; i <= 7; i++) {
    
    
            // 子线程
            final int finalI = i;
            new Thread(() -> {
    
    
                System.out.println(Thread.currentThread().getName() + "收集了第" + finalI + "颗龙珠");
                try {
    
    
                    cyclicBarrier.await(); // 加法计数 等待
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
    
    
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

3Semaphore (信号量)限流

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class Test {
    
    
    public static void main(String[] args) {
    
    

        // 线程数量,停车位,限流
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i <= 6; i++) {
    
    
            new Thread(() -> {
    
    
                // acquire() 得到
                try {
    
    
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                }catch (Exception e) {
    
    
                    e.printStackTrace();
                }finally {
    
    
                    semaphore.release(); // release() 释放
                }
            }).start();
        }
    }
}

Thread-0抢到车位
Thread-1抢到车位
Thread-2抢到车位
Thread-1离开车位
Thread-0离开车位
Thread-3抢到车位
Thread-4抢到车位
Thread-2离开车位
Thread-5抢到车位
Thread-4离开车位
Thread-5离开车位
Thread-3离开车位
Thread-6抢到车位
Thread-6离开车位

原理:
semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!
semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!

猜你喜欢

转载自blog.csdn.net/zs18753479279/article/details/114148763