JUC基础

1、什么是JUC?

在Java中,线程部分是一个重点,本篇文章说的JUC也是关于线程的。JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包, JDK1.5开始出现的。

1.1、线程的状态

1.1.1、线程状态枚举类

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    //新建
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    //准备就绪
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    //阻塞
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    //不见不散
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    //过时不候
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    //终结
    TERMINATED;
}
  1. NEW:新建
  2. RUNNABLE:准备就绪
  3. BLOCKED:阻塞
  4. WAITING:不见不散
  5. TIMED_WAITING:过时不候
  6. TERMINATED:终结

操作系统中进程的五种状态:
![进程五种状态.jpg](https://img-blog.csdnimg.cn/img_convert/28ce611d5f37d35b69c9098d8ed229e3.jpeg#clientId=udfb93a42-ac06-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=308&id=ud4592eaf&margin=[object Object]&name=进程五种状态.jpg&originHeight=385&originWidth=878&originalType=binary&ratio=1&rotation=0&showTitle=true&size=28284&status=done&style=stroke&taskId=ub21a2be6-9183-4b45-a705-b3652754e66&title=进程的五种状态&width=702.4 “进程的五种状态”)

1.2、wait/sleep区别

  1. sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用;
  2. sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized 中)wait有一个特点:在哪里睡,就在哪里醒!;
  3. 它们都可以被 interrupted 方法中断。

1.3、管程

管程(monitor)是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同一时刻只被一个进程调用(由编译器实现)。但是这样并不能保证进程以设计的顺序执行。
JVM中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程(monitor)对象,管程(monitor)会随着java对象一同创建和销毁。
执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方法在执行时候会持有管程,其他线程无法再获取同一个管程。

1.4、用户线程和守护线程

  • 用户线程:平时用到的普通线程,自定义线程
  • 守护线程:运行在后台,是一种特殊的线程,比如垃圾回收
public class Main4
{
    public static void main(String[] args)
    {
        Thread aa = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon());
            while (true) {

            }
        }, "aa");
        aa.start();

        System.out.println(Thread.currentThread().getName() + " over");
    }
}

![image.png](https://img-blog.csdnimg.cn/img_convert/825b0974acbd19b1e978c3279960bdfe.png#clientId=uc5aaf87d-4a5e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=495&id=u15e70003&margin=[object Object]&name=image.png&originHeight=743&originWidth=1515&originalType=binary&ratio=1&rotation=0&showTitle=false&size=62119&status=done&style=stroke&taskId=u7691ddf7-6c1b-46b0-9026-dd807500863&title=&width=1009)

  • 当主线程结束后,用户线程还在运行,JVM存活

调用setDaemon()方法把用户线程变为守护线程:

public class Main4
{
    public static void main(String[] args)
    {
        Thread aa = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "::" + Thread.currentThread().isDaemon());
            while (true) {

            }
        }, "aa");
        //设置守护线程
        aa.setDaemon(true);
        aa.start();

        System.out.println(Thread.currentThread().getName() + " over");
    }
}

![image.png](https://img-blog.csdnimg.cn/img_convert/db952a31ba60980c80f3aa5001fe1618.png#clientId=uc5aaf87d-4a5e-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=498&id=ue7c82e8a&margin=[object Object]&name=image.png&originHeight=747&originWidth=1515&originalType=binary&ratio=1&rotation=0&showTitle=false&size=65424&status=done&style=stroke&taskId=uffe06a36-0a5e-484f-9686-04b2c110784&title=&width=1010)

  • 如果没有用户线程,都是守护线程,JVM结束

2、Lock接口

2.1、synchronized关键字

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
    虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此, synchronized关键字不能被继承。
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。

2.1.1、售票案例

![image.png](https://img-blog.csdnimg.cn/img_convert/4ad4e8e9def4c4648d55dba2148784f7.png#clientId=ud3020c11-2868-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=142&id=uda1422c7&margin=[object Object]&name=image.png&originHeight=177&originWidth=784&originalType=binary&ratio=1&rotation=0&showTitle=true&size=55908&status=done&style=stroke&taskId=uaadeef67-c3ec-42a9-9e79-ac230ad5c8f&title=多线程编程步骤&width=627.2 “多线程编程步骤”)
要求:3个售票员卖出30张票。
第一步:创建资源类,在资源类中创建属性和操作方法

//1、创建一个资源类,定义属性和方法
class Ticket{
    //票数
    private int number = 30;
    //操作方法:卖票
    public synchronized void sale(){
        //判断:是否有票
        if (number > 0){
            System.out.println(Thread.currentThread().getName() + " : 卖出: " + (number--) + " 剩下: " + number);
        }

    }
}

第二步:创建多个线程,调用资源类的操作方法

public class SaleTicket
{
    //2、创建多个线程,调用
    public static void main(String[] args)
    {
        //创建Ticket对象
        Ticket ticket = new Ticket();

        //创建三个线程
        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                //调用卖票的方法
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }
        },"AA").start();

        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                //调用卖票的方法
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }
        },"BB").start();

        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                //调用卖票的方法
                for (int i = 0; i < 40; i++) {
                    ticket.sale();
                }
            }
        },"CC").start();

    }

}

如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

  1. 获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
  2. 线程执行发生异常,此时 JVM 会让线程自动释放锁。

那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。

2.2、Lock接口

Lock锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。 Lock提供了比synchronized更多的功能。
Lock与的Synchronized区别:

  • Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。 Lock是一个类,通过这个类可以实现同步访问;
  • Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
  • Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  • 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;
  • Lock可以提高多个线程进行读操作的效率;
    在性能上来说,如果竞争资源不激烈,两者的性能是差不多的;而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。

使用Lock接口实现上述案例:

//1、创建一个资源类,定义属性和方法
class LTicket{
    //票数
    private int number = 30;

    //创建可重入锁
    private final ReentrantLock lock = new ReentrantLock();

    //操作方法:卖票
    public void sale(){
        //上锁
        lock.lock();

        try {
            //判断:是否有票
            if (number > 0){
                System.out.println(Thread.currentThread().getName() + " : 卖出: " + (number--) + " 剩下: " + number);
            }
        }
        finally {
            //解锁
            lock.unlock();
        }
    }
}
public class LSaleTicket
{
    //2、创建多个线程,调用资源类的操作方法
    public static void main(String[] args)
    {
        LTicket lTicket = new LTicket();

        //创建三个线程
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                lTicket.sale();
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                lTicket.sale();
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                lTicket.sale();
            }
        },"CC").start();
    }

}

这里有一个问题:
调用start()方法时,线程是否已经创建?
查看start()源码,发现调用了native的方法,也就是说调用start()方法时,不一定会创建线程。


3、线程间的通信

操作系统中线程间的通信有哪几种方式?

  1. 管道,是一种半双工方式,数据单方向流动,而且只能在有亲缘关系的线程之间使用。他是基于内核的,可以把它当作是内核缓冲区,在内核和用户空间交换数据需要进行四次的数据拷贝,因为他是把用户空间的数据拷贝到内核,然后从内核拷贝到内存,然后从内存再拷贝到内核,最后拷贝到用户空间,至于为什么要拷贝到内核,是因为数据最重都是在内存中执行的。接收的时候要按照次序,按照先进先出的原则,在管道中的数据只能被读取1次,读出之后缓冲区就不存在了。它只能承载没有格式的字节流 。
  2. 命名管道,和管道差不多,但是它支持没有亲缘关系的线程之间通信。其他的类似。命名管道的名字对应磁盘索引截点,所有进程都可以对他进行访问。
  3. 消息队列,是消息的链表,是一系列保存在内核中的消息列表,同样交换数据也是需要4次拷贝的,它的优势是可以对每个消息指定特定的消息类型,接收的时候不需要按照队列的次序接收。
  4. 共享存储,就是映射一段可以被其他进程所访问的内存,由一个进程创建,多个进程都是可以访问的。用它传递数据只需要拷贝三次,用户空间->内存->用户空间。
  5. 信号量,用于控制多个进程对于共享资源的访问,可以用之线程同步。
  6. 套接字Socket,Socket套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
  7. 信号,是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生了。

![image.png](https://img-blog.csdnimg.cn/img_convert/7560d996b24460326c61db572d994f68.png#clientId=ud3020c11-2868-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=218&id=u8293af06&margin=[object Object]&name=image.png&originHeight=248&originWidth=634&originalType=binary&ratio=1&rotation=0&showTitle=true&size=75388&status=done&style=stroke&taskId=u3a701b27-5a0c-47de-bc87-d9825d01b76&title=多线程的编程步骤(中部)&width=558.2000122070312 “多线程的编程步骤(中部)”)
案例:两个线程对number进行+1和-1

3.1、synchronized实现案例

//1、创建一个资源类,定义属性和方法
class Share{
    //初始值
    private int number = 0;
    //+1的方法
    public synchronized void incr() throws InterruptedException
    {
        //2、判断 干活 通知
        if (number != 0){ //判断:如果number的值是否是0,如果不是0,等待
            this.wait();
        }
        //干活:如果number的值是0,就+1
        number++;

        System.out.println(Thread.currentThread().getName() + "::" + number);

        //通知:其他线程
        this.notify();
    }
    //-1的方法
    public synchronized void decr() throws InterruptedException
    {
        //2、判断 干活 通知
        if (number != 1){ //判断:如果number的值是否是1,如果不是1,等待
            this.wait();
        }
        //干活:如果number的值是1,就-1
        number--;

        System.out.println(Thread.currentThread().getName() + "::" + number);

        //通知:其他线程
        this.notify();
    }
}
public class ThreadDemo1
{
    //3、创建多个线程,调用资源类的操作方法
    public static void main(String[] args)
    {
        Share share = new Share();
        //创建线程
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    share.incr();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    share.decr();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();
    }
}

3.1.2、虚假唤醒问题

我们将线程的个数增加到4个,AA和CC线程执行+1操作;BB和CC线程执行-1操作。

public class ThreadDemo1
{
    //3、创建多个线程,调用资源类的操作方法
    public static void main(String[] args)
    {
        Share share = new Share();
        //创建线程
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    share.incr();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    share.decr();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    share.incr();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    share.decr();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"DD").start();
    }
}

执行结果:
![image.png](https://img-blog.csdnimg.cn/img_convert/0b2b09996b590a06fd217223ebe77eac.png#clientId=ud3020c11-2868-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=537&id=u0688e1b3&margin=[object Object]&name=image.png&originHeight=710&originWidth=116&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6439&status=done&style=stroke&taskId=ufef30859-a15a-48b5-917a-6429c62255b&title=&width=87.80000305175781)
出现了问题:我们希望的是只有0和1的值,但是这里边不符合预期。那么问题出现在哪里?在wait()方法:
![image.png](https://img-blog.csdnimg.cn/img_convert/00e838f1af6213b7026e0b342029672c.png#clientId=ud3020c11-2868-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=397&id=ud11328b0&margin=[object Object]&name=image.png&originHeight=496&originWidth=1496&originalType=binary&ratio=1&rotation=0&showTitle=false&size=36891&status=done&style=stroke&taskId=uee24e4a8-dd54-4627-ab93-02f336e48db&title=&width=1196.8)
具体分析:
wait有一个特点:在哪里睡,就在哪里醒!这就是导致虚假唤醒的原因!!!
解决方法:判断放在while语句块中

class Share{
    //初始值
    private int number = 0;
    //+1的方法
    public synchronized void incr() throws InterruptedException
    {
        //2、判断 干活 通知
        while (number != 0){ //判断:如果number的值是否是0,如果不是0,等待
            this.wait();
        }
        //干活:如果number的值是0,就+1
        number++;

        System.out.println(Thread.currentThread().getName() + "::" + number);

        //通知:其他线程
        this.notify();
    }
    //-1的方法
    public synchronized void decr() throws InterruptedException
    {
        //2、判断 干活 通知
        while (number != 1){ //判断:如果number的值是否是1,如果不是1,等待
            this.wait();
        }
        //干活:如果number的值是1,就-1
        number--;

        System.out.println(Thread.currentThread().getName() + "::" + number);

        //通知:其他线程
        this.notify();
    }
}

最终的多线程编程步骤:
![image.png](https://img-blog.csdnimg.cn/img_convert/8e61e9abf8296583ccd089a1f96bc38e.png#clientId=ud3020c11-2868-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=282&id=u44989d72&margin=[object Object]&name=image.png&originHeight=330&originWidth=706&originalType=binary&ratio=1&rotation=0&showTitle=true&size=83835&status=done&style=stroke&taskId=u656435ed-1701-47d7-ba76-a6c5fb5b26a&title=最终的多线程编程步骤&width=603.7999877929688 “最终的多线程编程步骤”)

3.2、Lock接口实现案例

//1、创建一个资源类,定义属性和方法
class Share{
    //初始值
    private int number = 0;
    //创建Lock
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    //+1的方法
    public void incr() throws InterruptedException
    {
        //上锁
        lock.lock();

        try {
            //判断
            while (number != 0){
                condition.await();
            }
            //干活
            number++;
            System.out.println(Thread.currentThread().getName() + "::" + number);
            //通知
            condition.signalAll();
        }
        finally {
            //解锁
            lock.unlock();
        }
    }
    //-1的方法
    public synchronized void decr() throws InterruptedException
    {
        //上锁
        lock.lock();

        try {
            //判断
            while (number != 1){
                condition.await();
            }
            //干活
            number--;
            System.out.println(Thread.currentThread().getName() + "::" + number);
            //通知
            condition.signalAll();
        }
        finally {
            //解锁
            lock.unlock();
        }
    }
}
public class ThreadDemo2
{
    public static void main(String[] args)
    {
        Share share = new Share();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    share.incr();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    share.decr();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    share.incr();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    share.decr();
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"DD").start();
    }
}

3.3、线程间的定制化通信

要求:

  • AA打印5次,BB打印10次,CC打印15次
  • AA打印15次,BB打印10次,CC打印15次
  • 进行10轮

分析:
![image.png](https://img-blog.csdnimg.cn/img_convert/43a51600953dd08bd2284980bb0bd8c7.png#clientId=uf2a59efe-98ec-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=374&id=u618aeb13&margin=[object Object]&name=image.png&originHeight=468&originWidth=768&originalType=binary&ratio=1&rotation=0&showTitle=false&size=73817&status=done&style=stroke&taskId=ubd851829-052e-4433-8876-a5f240ac5c3&title=&width=614.4)

//1、创建资源类,定义属性和操作方法
class ShareResource{
    //定义标志位
    private int flag = 1; //1AA 2BB 3CC
    //创建Lock锁
    private Lock lock = new ReentrantLock();

    //创建三个condition
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    //打印5次,参数第几轮
    public void print5(int loop) throws InterruptedException
    {
        //上锁
        lock.lock();

        try {
            //判断
            while (flag != 1){
                c1.await();
            }
            //干活
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName()+"::"+i+" :轮数:"+loop);
            }
            //通知
            flag = 2; //修改标志位为2
            c2.signal(); //通知BB线程
            }
        finally {
            //解锁
            lock.unlock();
        }
    }

    //打印10次,参数第几轮
    public void print10(int loop) throws InterruptedException
    {
        //上锁
        lock.lock();

        try {
            //判断
            while (flag != 2){
                c2.await();
            }
            //干活
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName()+"::"+i+" :轮数:"+loop);
            }
            //通知
            flag = 3; //修改标志位为3
            c3.signal(); //通知CC线程
        }
        finally {
            //解锁
            lock.unlock();
        }
    }

    //打印15次,参数第几轮
    public void print15(int loop) throws InterruptedException
    {
        //上锁
        lock.lock();

        try {
            //判断
            while (flag != 3){
                c3.await();
            }
            //干活
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName()+"::"+i+" :轮数:"+loop);
            }
            //通知
            flag = 1; //修改标志位为1
            c1.signal(); //通知AA线程
        }
        finally {
            //解锁
            lock.unlock();
        }
    }

}
public class ThreadDemo3
{
    public static void main(String[] args)
    {
        ShareResource shareResource = new ShareResource();
        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    shareResource.print5(i);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    shareResource.print10(i);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

        new Thread(()->{
            for (int i = 1; i <= 10; i++) {
                try {
                    shareResource.print15(i);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();
    }
}

4、集合的线程安全

4.1、ArrayList集合线程不安全演示

查看ArrayList中add()方法的源码:

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

可以看到add()方法并没有加上synchronized关键字,所以这里边便存在着线程不安全的问题!
现在演示一种情况:使用多个线程,向list集合中添加元素,并从集合中获取元素:

//List线程不安全问题
public class ThreadDemo4
{
    public static void main(String[] args)
    {
        //创建ArrayList集合
        ArrayList<String> list = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                //向集合中添加元素
                list.add(UUID.randomUUID().toString().substring(0,8));
                //从集合中获取元素
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

输出的结果:

Exception in thread "2" Exception in thread "0" Exception in thread "4" Exception in thread "1" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at Lock.ThreadDemo4.lambda$main$0(ThreadDemo4.java:25)
	at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at Lock.ThreadDemo4.lambda$main$0(ThreadDemo4.java:25)
	at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at Lock.ThreadDemo4.lambda$main$0(ThreadDemo4.java:25)
	at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at Lock.ThreadDemo4.lambda$main$0(ThreadDemo4.java:25)
	at java.lang.Thread.run(Thread.java:748)
[null, null, 3b81634e, 278eb15f, 0687f3ba]
[null, null, 3b81634e, 278eb15f, 0687f3ba, d10c7698, 884dfd9e, 8e76f7e3, 3ee8aab6]
[null, null, 3b81634e, 278eb15f, 0687f3ba, d10c7698, 884dfd9e, 8e76f7e3]
[null, null, 3b81634e, 278eb15f, 0687f3ba, d10c7698, 884dfd9e]
[null, null, 3b81634e, 278eb15f, 0687f3ba, d10c7698]
[null, null, 3b81634e, 278eb15f, 0687f3ba, d10c7698, 884dfd9e, 8e76f7e3, 3ee8aab6, cfc4fe66]

出现了一个ConcurrentModificationException异常,称为并发修改异常。

4.1.1、解决方案:Vector

public class ThreadDemo4
{
    public static void main(String[] args)
    {
        //解决方法:Vector
        Vector<String> list = new Vector<>();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                //向集合中添加元素
                list.add(UUID.randomUUID().toString().substring(0,8));
                //从集合中获取元素
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

查看Vector的源码:

/**
 * Appends the specified element to the end of this Vector.
 *
 * @param e element to be appended to this Vector
 * @return {@code true} (as specified by {@link Collection#add})
 * @since 1.2
 */
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

发现添加了synchronized关键字。

4.1.2、解决方案:Collections

public class ThreadDemo4
{
    public static void main(String[] args)
    {
        //解决方法:Collections
        List<Object> list = Collections.synchronizedList(new ArrayList<>());

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                //向集合中添加元素
                list.add(UUID.randomUUID().toString().substring(0,8));
                //从集合中获取元素
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

4.1.3、解决方案:CopyOnWriteArrayList

public class ThreadDemo4
{
    public static void main(String[] args)
    {
        //解决方法:CopyOnWriteArrayList
        List<Object> list = new CopyOnWriteArrayList<>();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                //向集合中添加元素
                list.add(UUID.randomUUID().toString().substring(0,8));
                //从集合中获取元素
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

写时复制技术:每次写的时候先复制,然后往里写,再合并!
![image.png](https://img-blog.csdnimg.cn/img_convert/afc2adb97fdfc48294bab6899061cafb.png#clientId=uf2a59efe-98ec-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=268&id=u4f177ccc&margin=[object Object]&name=image.png&originHeight=335&originWidth=878&originalType=binary&ratio=1&rotation=0&showTitle=false&size=93903&status=done&style=stroke&taskId=ub23a28cd-06d9-44d6-93ce-365adffc936&title=&width=702.4)
查看源码:

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
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;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

4.2、HashSet和HashMap线程不安全问题及解决方案

public class ThreadDemo4
{
    public static void main(String[] args)
    {
        Set<String> set = new HashSet<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                //向结合中添加元素
                set.add(UUID.randomUUID().toString().substring(0,8));
                //从集合中获取内容
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

同样出现了并发修改问题。
使用CopyOnWriteArraySet解决:

public class ThreadDemo4
{
    public static void main(String[] args)
    {
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                //向结合中添加元素
                set.add(UUID.randomUUID().toString().substring(0,8));
                //从集合中获取内容
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}
public class ThreadDemo4
{
    public static void main(String[] args)
    {
        HashMap<String, String> map = new HashMap<>();
        for (int i = 0; i < 30; i++) {
            String key = String.valueOf(i);
            new Thread(()->{
                //向结合中添加元素
                map.put(key,UUID.randomUUID().toString().substring(0,8));
                //从集合中获取内容
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

解决方法:ConcurrentHashMap

public class ThreadDemo4
{
    public static void main(String[] args)
    {
        Map<String, String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++) {
            String key = String.valueOf(i);
            new Thread(()->{
                //向结合中添加元素
                map.put(key,UUID.randomUUID().toString().substring(0,8));
                //从集合中获取内容
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

5、多线程锁

5.1、锁的八个问题演示

定义一个手机Phone类:

public class Phone
{
    public synchronized void sendSMS() throws InterruptedException
    {
        //停留4秒
        //TimeUnit.SECONDS.sleep(4);
        System.out.println("------------sendSMS");
    }

    public synchronized void sendEmail(){
        System.out.println("------------sendEmail");
    }

    public void getHello(){
        System.out.println("------------getHello");
    }
}

在main()方法中依次访问SMS和Email:

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

        new Thread(()->{
            try{
                phone.sendSMS();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"AA").start();

        Thread.sleep(100);

        new Thread(()->{
            try{
                //phone.sendEmail();
                //phone.getHello();
                phone1.sendEmail();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"BB").start();

    }
}
  1. 标准访问,先打印短信还是邮件?
    ------------sendSMS
    ------------sendEmail
  2. 在短信方法中停4秒,先打印短信还是邮件?
    ------------sendSMS
    ------------sendEmail
  3. 新增普通方法hello,先打印短信还是hello?
    ------------getHello
    ------------sendSMS
  4. 现有两部手机,先打印短信还是邮件?
    ------------sendEmail
    ------------sendSMS
  5. 两个静态同步方法,1部手机,先打印短信还是邮件
    ------------sendSMS
    ------------sendEmail
  6. 两个静态同步方法,2部手机,先打印短信还是邮件
    ------------sendSMS
    ------------sendEmail
  7. 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
    ------------sendEmail
    ------------sendSMS
  8. 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
    ------------sendEmail
    ------------sendSMS

结论:

  • 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法,其它的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法。锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法加个普通方法后发现和同步锁无关。
  • 换成两个对象后,不是同一把锁了,情况立刻变化。

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。
具体表现为以下 3 种形式:

  1. 对于普通同步方法,锁是当前实例对象;
  2. 对于静态同步方法,锁是当前类的Class对象;
  3. 对于同步方法块,锁是Synchonized括号里配置的对象。

5.2、公平锁和不公平锁

在上面的例子中new一个ReentrantLock锁时,我们使用的是无参构造,查看源码:

/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

无参构造直接就是非公平锁,可能出现线程“饿死”的情况。还可以使用有参构造,传入一个参数。

5.3、可重入锁

也叫“递归锁”,在得到最外层的锁后,可以无障碍地进入所有内层锁。
![image.png](https://img-blog.csdnimg.cn/img_convert/43cfdc1a88bae56b55a85a7116483dfb.png#clientId=ua9a4ca34-ce9b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=332&id=u082eb811&margin=[object Object]&name=image.png&originHeight=415&originWidth=675&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30694&status=done&style=stroke&taskId=ud3de98d3-7599-4381-bcaa-8ac92dcc213&title=&width=540)
演示可重入锁的特点:

package SynchronizedDemo;

public class SyncLockDemo
{
    public synchronized void add(){
        add();
    }

    public static void main(String[] args)
    {
        SyncLockDemo syncLockDemo = new SyncLockDemo();
        //此处会抛出栈溢出的异常
        syncLockDemo.add();

        Object o = new Object();
        new Thread(()->{
            synchronized (o){
                System.out.println(Thread.currentThread().getName() + " 外层");

                synchronized (o){
                    System.out.println(Thread.currentThread().getName() + " 中层");

                    synchronized (o){
                        System.out.println(Thread.currentThread().getName() + " 内层");
                    }
                }
            }
        },"t1").start();
    }
}

如果不是“可重入”,那么就不可能访问到“中层”和“内层”;同理,也不可能可以递归调用add()方法。

package SynchronizedDemo;

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

public class SyncLockDemo
{

    public static void main(String[] args)
    {
        //lock演示可重入锁
        Lock lock = new ReentrantLock();
        //创建线程
        new Thread(()->{
            try{
                //上锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+"外层");

                try{
                    //上锁
                    lock.lock();
                    System.out.println(Thread.currentThread().getName()+"内层");
                }finally {
                    lock.unlock();
                }
            }finally {
                lock.unlock();
            }
        },"t1").start();
    }
}

程序可以正常执行。
演示另一种情况,如果我把内层不解锁呢?

package SynchronizedDemo;

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

public class SyncLockDemo
{
    public synchronized void add(){
        add();
    }

    public static void main(String[] args)
    {
        //lock演示可重入锁
        Lock lock = new ReentrantLock();
        //创建线程
        new Thread(()->{
            try{
                //上锁
                lock.lock();
                System.out.println(Thread.currentThread().getName()+"外层");

                try{
                    //上锁
                    lock.lock();
                    System.out.println(Thread.currentThread().getName()+"内层");
                }finally {
                    //lock.unlock();
                }
            }finally {
                lock.unlock();
            }
        },"t1").start();

        new Thread(()->{
            lock.lock();
            System.out.println("aaaaa");
            lock.unlock();
        },"aa").start();
    }
}

![image.png](https://img-blog.csdnimg.cn/img_convert/351aacd6ebc6747c3192d8b15275c1d8.png#clientId=ua9a4ca34-ce9b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=211&id=u09c4b509&margin=[object Object]&name=image.png&originHeight=264&originWidth=844&originalType=binary&ratio=1&rotation=0&showTitle=false&size=17176&status=done&style=stroke&taskId=u21b50cf8-ddcd-4d19-bb91-50e76cc68dc&title=&width=675.2)
如果没有释放锁,对自己没有影响。但是别的线程则无法获得锁,别的线程无法继续执行下去!

5.4、死锁

![image.png](https://img-blog.csdnimg.cn/img_convert/2a4490e3beee252061a7fcef71aaaa5b.png#clientId=ua9a4ca34-ce9b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=614&id=u444b0c56&margin=[object Object]&name=image.png&originHeight=767&originWidth=1506&originalType=binary&ratio=1&rotation=0&showTitle=false&size=242775&status=done&style=stroke&taskId=u78cc7c08-35e3-4b0f-b306-bfbfcc2e78b&title=&width=1204.8)

package SynchronizedDemo;

import java.util.concurrent.TimeUnit;

public class DeadLock
{
    //创建两个对象
    static Object a = new Object();
    static Object b = new Object();

    public static void main(String[] args)
    {
        new Thread(()->{
            synchronized (a){
                System.out.println(Thread.currentThread().getName()+" 持有锁a,试图获取锁b");
                try {
                    TimeUnit.SECONDS.sleep(1);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (b){
                    System.out.println(Thread.currentThread().getName()+" 获取锁b");
                }
            }
        },"A").start();

        new Thread(()->{
            synchronized (b){
                System.out.println(Thread.currentThread().getName()+" 持有锁b,试图获取锁a");
                try {
                    TimeUnit.SECONDS.sleep(1);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (a){
                    System.out.println(Thread.currentThread().getName()+" 获取锁a");
                }
            }
        },"B").start();
    }
}


6、Callable接口

![image.png](https://img-blog.csdnimg.cn/img_convert/ac29e10c9190a6ffc60110cda4727ded.png#clientId=ua9a4ca34-ce9b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=290&id=ue82a61a3&margin=[object Object]&name=image.png&originHeight=363&originWidth=590&originalType=binary&ratio=1&rotation=0&showTitle=false&size=71160&status=done&style=stroke&taskId=u7a442185-3641-420c-817f-7f72f06f988&title=&width=472)
创建线程的多种方式:

  1. 继承Thread类;
  2. 实现Runnable接口;
  3. 实现Callable接口;
  4. 线程池。

Runnable接口和Callable接口的区别:

  1. 是否有返回值;Callable接口有返回值,Runnable接口没有返回值;
  2. 是否抛出异常;Callable可以抛出异常,Runnable没有异常;
  3. 实现方法名称不同,一个run()方法,一个是call()方法。

6.1、Callable接口创建线程的方式

那么如何使用Callable接口创建一个线程呢?

//Callable接口创建一个线程,报错
//new Thread(new MyThred2(),"BB").start();

![image.png](https://img-blog.csdnimg.cn/img_convert/b088a5bd57667244fd7d3848db313de7.png#clientId=ua9a4ca34-ce9b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=110&id=u1ad1697c&margin=[object Object]&name=image.png&originHeight=138&originWidth=533&originalType=binary&ratio=1&rotation=0&showTitle=false&size=41330&status=done&style=stroke&taskId=ub1c005ac-a209-4362-8190-0f8595e4121&title=&width=426.4)
当call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用Future对象。
将Future视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦Callable 返回)。 Future基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写5种方法,这里列出了重要的方法,如下:

  • public boolean cancel(boolean mayInterrupt): 用于停止任务;
  • public Object get()抛出 InterruptedException, ExecutionException:用于获取任务的结果;
  • public boolean isDone(): 如果任务完成,则返回 true,否则返回 false。

Java库具有具体的FutureTask类型,该类型实现Runnable和Future,并方便地将两种功能组合在一起。可以通过为其构造函数提供Callable来创建FutureTask。然后,将FutureTask对象提供给Thread的构造函数以创建Thread对象。因此,间接地使用Callable创建线程。
核心重点:
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成:
• 当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态;
• 一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果;
• 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞get方法;
• 一旦计算完成,就不能再重新开始或取消计算;
• get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常;
• get只计算一次,因此get方法放到最后。

package callable;

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

//比较两个接口
//实现Runnable接口
class MyThred1 implements Runnable{

    @Override
    public void run()
    {

    }
}
//实现Callable接口
class MyThread2 implements Callable{

    @Override
    public Integer call() throws Exception
    {
        System.out.println(Thread.currentThread().getName() +" come in callable");
        return 200;
    }
}

public class Demo1
{
    public static void main(String[] args) throws ExecutionException, InterruptedException
    {
        //Runnable接口创建一个线程
        new Thread(new MyThred1(),"AA").start();

        //Callable接口创建一个线程,报错
        //new Thread(new MyThred2(),"BB").start();

        //FutureTask
        FutureTask<Integer> futureTask1 = new FutureTask<>(new MyThread2());

        FutureTask<Integer> futureTask2 = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() +" come in callable");
            return 1024;
        });

        //创建线程
        new Thread(futureTask2,"lucy").start();
        new Thread(futureTask1,"mary").start();

        while (!futureTask2.isDone()){
            System.out.println("wait.....");
        }

        //调用FutureTask的get方法
        System.out.println(futureTask2.get());

        System.out.println(futureTask2.get());

        System.out.println(Thread.currentThread().getName() +" over");
    }
}


7、JUC辅助类

JUC中提供了三种常用的辅助类,通过这些辅助类可以很好的解决线程数量过多时Lock锁的频繁操作。这三种辅助类为:

  • CountDownLatch:减少计数
  • CyclicBarrier:循环栅栏
  • Semaphore:信号灯

7.1、减少计数 CountDownLatch

CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减1的操作,使用await()方法等待计数器不大于0,然后继续执行await方法之后的语句。

  • CountDownLatch主要有两个方法,当一个或多个线程调用await()方法时,这些线程会阻塞
  • 其它线程调用countDown方法会将计数器减1(调用countDown()方法的线程不会阻塞)
  • 当计数器的值变为0时,因await()方法阻塞的线程会被唤醒,继续执行
public class CountDownLatchDemo
{
    //6个同学陆续离开教室之后,班长才可以锁门
    public static void main(String[] args)
    {
        //6个同学陆续离开教室
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + " 号同学离开了教室");
            },String.valueOf(i)).start();
        }

        System.out.println(Thread.currentThread().getName() + " 班长锁门走人了");
    }
}
main 班长锁门走人了
4 号同学离开了教室
3 号同学离开了教室
1 号同学离开了教室
2 号同学离开了教室
6 号同学离开了教室
5 号同学离开了教室

使用CountDownLatch方法:

public class CountDownLatchDemo
{
    //6个同学陆续离开教室之后,班长才可以锁门
    public static void main(String[] args) throws InterruptedException
    {

        //1、创建CountDownLatch对象,设置初始值
        CountDownLatch countDownLatch = new CountDownLatch(6);

        //6个同学陆续离开教室
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + " 号同学离开了教室");

                //2、计数 -1
                countDownLatch.countDown();

            },String.valueOf(i)).start();
        }

        //3、等待(阻塞)
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + " 班长锁门走人了");
    }
}
1 号同学离开了教室
6 号同学离开了教室
5 号同学离开了教室
3 号同学离开了教室
4 号同学离开了教室
2 号同学离开了教室
main 班长锁门走人了

7.2、循环栅栏 CyclicBarrier

CyclicBarrier看英文单词可以看出大概就是循环阻塞的意思,在使用中CyclicBarrier的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。可以将CyclicBarrier理解为加1操作。

public class CyclicBarrierDemo
{
    //创建固定值
    private static final int NUMBER = 7;
    public static void main(String[] args)
    {
        //创建CyclicBarrier
        CyclicBarrier cyclicBarrier =
                new CyclicBarrier(NUMBER,()->{
                    System.out.println("集齐7颗龙珠就可以召唤神龙");
                });

        //集齐7颗龙珠的过程
        for (int i = 1; i <= 7; i++) {
            new Thread(()->{
                try{
                    System.out.println(Thread.currentThread().getName() + " 星龙珠收集到了");
                    //等待
                    cyclicBarrier.await();
                }catch (Exception e){
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}
1 星龙珠收集到了
5 星龙珠收集到了
6 星龙珠收集到了
3 星龙珠收集到了
2 星龙珠收集到了
7 星龙珠收集到了
4 星龙珠收集到了
集齐7颗龙珠就可以召唤神龙

7.3、信号灯Semaphore

Semaphore的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。使用acquire方法获得许可证,release方法释放许可。

//抢车位, 6部汽车 3个停车位
public class SemaphoreDemo
{
    public static void main(String[] args)
    {
        //创建Semaphore,设置许可证
        Semaphore semaphore = new Semaphore(3);

        //模拟6辆汽车
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                //抢占
                try {
                    semaphore.acquire();

                    System.out.println(Thread.currentThread().getName() + " 抢到了车位");

                    //设置随机的停车时间
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));

                    System.out.println(Thread.currentThread().getName() + " ---------离开了车位");
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

8、读写锁

悲观锁和乐观锁:
![image.png](https://img-blog.csdnimg.cn/img_convert/b524c63eb75f4f6f2cdf975f4bfe6b7c.png#clientId=u96bb6df4-a7b4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=514&id=ue57751e6&margin=[object Object]&name=image.png&originHeight=694&originWidth=1050&originalType=binary&ratio=1&rotation=0&showTitle=false&size=288627&status=done&style=shadow&taskId=uf455449e-1820-4819-a9bf-4f080f7b824&title=&width=778)
表锁和行锁以及读锁(共享锁)和写锁(独占锁):
![image.png](https://img-blog.csdnimg.cn/img_convert/ea1ff5ba0657ed75cb25b816f8ebf42f.png#clientId=u96bb6df4-a7b4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=511&id=u19aa8641&margin=[object Object]&name=image.png&originHeight=730&originWidth=1381&originalType=binary&ratio=1&rotation=0&showTitle=false&size=70126&status=done&style=shadow&taskId=ub51a48eb-1f57-4ce8-852b-7d2b56448b2&title=&width=965.800048828125)
现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
针对这种场景,JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁。

  1. 线程进入读锁的前提条件:
    没有其他线程的写锁
    没有写请求,或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)
  2. 线程进入写锁的前提条件:
    没有其他线程的读锁
    没有其他线程的写锁

而读写锁有以下三个重要的特性:

  1. 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平;
  2. 重进入:读锁和写锁都支持线程重进入;
  3. 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
class MyCache{
    //创建map集合
    private Map<String,Object> map = new HashMap<>();

    //向map中放数据
    public void put(String key, Object value){
        System.out.println(Thread.currentThread().getName() + " 正在写操作" + key);

        //暂停一会
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }

        //放数据
        map.put(key,value);
        System.out.println(Thread.currentThread().getName() + " 写完了" + key);
    }

    //从map中取数据
    public Object get(String key){
        System.out.println(Thread.currentThread().getName() + " 正在读取操作" + key);

        //暂停一会
        try {
            TimeUnit.MILLISECONDS.sleep(300);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }

        Object result = map.get(key);
        System.out.println(Thread.currentThread().getName() + " 取完了" + key);
        return result;

    }
}
public class ReadWriteLockDemo
{
    public static void main(String[] args)
    {
        MyCache myCache = new MyCache();

        //创建线程放数据
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.put(num+"",num+"");
            },String.valueOf(i)).start();
        }

        //创建线程取数据
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}
1 正在写操作1
5 正在写操作5
4 正在写操作4
3 正在写操作3
2 正在写操作2
1 正在读取操作1
2 正在读取操作2
3 正在读取操作3
4 正在读取操作4
5 正在读取操作5
3 写完了3
1 写完了1
3 取完了3
5 写完了5
1 取完了1
5 取完了5
4 写完了4
2 写完了2
4 取完了4
2 取完了2

正常情况下,写完了才可以读。

package readwrite;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

//资源类
class MyCache{
    //创建map集合
    private Map<String,Object> map = new HashMap<>();

    //创建读写锁的对象
    private ReadWriteLock rwlock = new ReentrantReadWriteLock();

    //向map中放数据
    public void put(String key, Object value){
        //添加写锁
        rwlock.writeLock().lock();

        //暂停一会
        try {
            System.out.println(Thread.currentThread().getName() + " 正在写操作" + key);

            TimeUnit.MILLISECONDS.sleep(300);

            //放数据
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + " 写完了" + key);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //释放写锁
            rwlock.writeLock().unlock();
        }



    }

    //从map中取数据
    public Object get(String key){

        Object result = null;
        //添加读锁
        rwlock.readLock().lock();

        //暂停一会
        try {
            System.out.println(Thread.currentThread().getName() + " 正在读取操作" + key);

            TimeUnit.MILLISECONDS.sleep(300);

            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + " 取完了" + key);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            rwlock.readLock().unlock();
        }

        return result;

    }
}
public class ReadWriteLockDemo
{
    public static void main(String[] args)
    {
        MyCache myCache = new MyCache();

        //创建线程放数据
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.put(num+"",num+"");
            },String.valueOf(i)).start();
        }

        //创建线程取数据
        for (int i = 1; i <= 5; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}

1 正在写操作1
1 写完了1
2 正在写操作2
2 写完了2
3 正在写操作3
3 写完了3
4 正在写操作4
4 写完了4
4 写完了4
5 正在写操作5
5 写完了5
1 正在读取操作1
2 正在读取操作2
3 正在读取操作3
5 正在读取操作5
4 正在读取操作4
4 取完了4
3 取完了3
1 取完了1
2 取完了2
5 取完了5

8.1、读写锁的演变

![image.png](https://img-blog.csdnimg.cn/img_convert/6a43a6dfa3f5c60e1f455e80b99e59cc.png#clientId=u3db532d9-9537-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=486&id=u0c32a06d&margin=[object Object]&name=image.png&originHeight=608&originWidth=1590&originalType=binary&ratio=1&rotation=0&showTitle=true&size=97791&status=done&style=shadow&taskId=u90209d23-d53f-44e7-88c4-dd4e2ff6f54&title=锁的演变历程&width=1272 “锁的演变历程”)

8.2、读写锁的降级

![image.png](https://img-blog.csdnimg.cn/img_convert/b6c6f5383a37a008c3e95093f900a738.png#clientId=u3db532d9-9537-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=578&id=u2f9e864d&margin=[object Object]&name=image.png&originHeight=723&originWidth=1221&originalType=binary&ratio=1&rotation=0&showTitle=false&size=49984&status=done&style=shadow&taskId=u3dd38437-11d6-46bf-9c5a-99593383d87&title=&width=976.8)

public class Demo1
{
    public static void main(String[] args)
    {
        //可重入读写锁的对象
        ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock readLock = rwlock.readLock(); //读锁
        ReentrantReadWriteLock.WriteLock writeLock = rwlock.writeLock(); //写锁

        //锁降级
        //1 获取到写锁
        writeLock.lock();
        System.out.println("atguigu");

        //2 获取到读锁
        readLock.lock();
        System.out.println("----read");

        //3 释放写锁
        writeLock.unlock();
        //4 释放读锁
        readLock.unlock();
    }
}

9、阻塞队列

9.1、BlockingQueue简介

阻塞队列,顾名思义,首先它是一个队列,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;
![image.png](https://img-blog.csdnimg.cn/img_convert/a0c5cb851767886eb9735e6017923c0d.png#clientId=u3db532d9-9537-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=383&id=u301043ad&margin=[object Object]&name=image.png&originHeight=479&originWidth=1043&originalType=binary&ratio=1&rotation=0&showTitle=false&size=211609&status=done&style=shadow&taskId=ua125f907-3b77-4b21-b4e5-40728401f9c&title=&width=834.4)
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起。
为什么需要BlockingQueue?
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。
在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者” 和“消费者” 模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。

  • 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列
  • 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒

9.2、常见的BlockingQueue

  1. ArrayBlockingQueue(常用)
    由数组结构组成的有界阻塞队列
  2. LinkedBlockingQueue(常用)
    由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列
  3. DelayQueue
    使用优先级队列实现的延迟无界阻塞队列
  4. PriorityBlockingQueue
    支持优先级排序的无界阻塞队列
  5. SynchronousQueue
    不存储元素的阻塞队列,也即单个元素的队列
  6. LinkedTransferQueue
    由链表组成的无界阻塞队列
  7. LinkedBlockingDeque
    由链表组成的双向阻塞队列

9.3、BlockingQueue核心方法

![image.png](https://img-blog.csdnimg.cn/img_convert/070eba5de3a94cd65a2deb5a47b79c9e.png#clientId=u3db532d9-9537-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=426&id=u1cbc0f87&margin=[object Object]&name=image.png&originHeight=533&originWidth=1052&originalType=binary&ratio=1&rotation=0&showTitle=false&size=248402&status=done&style=shadow&taskId=u3b16b9c7-fea5-4aa4-ae00-cf0c2504675&title=&width=841.6)

public class BlockingQueueDemo
{
    public static void main(String[] args) throws InterruptedException
    {
        //创建阻塞队列
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue(3);

        //第一组
        System.out.println(blockingQueue.add("a")); //true
        System.out.println(blockingQueue.add("b")); //true
        System.out.println(blockingQueue.add("c")); //true
        System.out.println(blockingQueue.element()); //a

        /*
        Exception in thread "main" java.lang.IllegalStateException: Queue full
         */
        //System.out.println(blockingQueue.add("w"));

        System.out.println(blockingQueue.remove()); //a
        System.out.println(blockingQueue.remove()); //b
        System.out.println(blockingQueue.remove()); //c
        /*
        Exception in thread "main" java.util.NoSuchElementException
         */
        //System.out.println(blockingQueue.remove());

        //第二组
        System.out.println(blockingQueue.offer("a")); //true
        System.out.println(blockingQueue.offer("b")); //true
        System.out.println(blockingQueue.offer("c")); //true
        System.out.println(blockingQueue.offer("ww")); //false

        System.out.println(blockingQueue.poll()); //a
        System.out.println(blockingQueue.poll()); //b
        System.out.println(blockingQueue.poll()); //c
        System.out.println(blockingQueue.poll()); //null

        //第三组
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        //队列没有空间会一直阻塞
        //blockingQueue.put("w");

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        //队列没有空间会一直阻塞
        //System.out.println(blockingQueue.take());

        //第四组
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        //会阻塞,超过3秒,会结束
        System.out.println(blockingQueue.offer("w", 3L, TimeUnit.SECONDS));

    }
}

10、线程池

线程池的优势: 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
线程池的特点:

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的销耗;
  • 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行;
  • 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控;
  • Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类。

![image.png](https://img-blog.csdnimg.cn/img_convert/6e5c04a67cbef579e9b8678fbe6011d0.png#clientId=u3db532d9-9537-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=506&id=u04cac084&margin=[object Object]&name=image.png&originHeight=633&originWidth=1032&originalType=binary&ratio=1&rotation=0&showTitle=false&size=82590&status=done&style=shadow&taskId=ub78be328-36c7-41e6-bd90-c3c56ec3d8c&title=&width=825.6)

10.1、线程池的种类与创建

10.1.1、newCachedThreadPool(常用,线程池根据需求创建线程,可扩容)

作用:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
特点

  1. 线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
  2. 线程池中的线程可进行缓存重复利用和回收(回收默认时间为1分钟)
  3. 当线程池中,没有可用线程,会重新创建一个线程

场景:适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较短,任务多的场景。

10.1.2、newFixedThreadPool(常用,一池N线程)

作用:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
特点

  • 线程池中的线程处于一定的量,可以很好的控制线程的并发量
  • 线程可以重复被使用,在显示关闭之前,都将一直存在
  • 超出一定量的线程被提交时候需在队列中等待

场景:适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严格限制的场景。

10.1.3、newSingleThreadExecutor(常用,一个任务一个任务执行,一池一线程)

作用:创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
特点

  • 线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行

场景:适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个线程的场景

public class ThreadPoolDemo1
{
    public static void main(String[] args)
    {
        //演示三种常见的线程池分类
        //1 一池N线程
        //ExecutorService threadPool1 = Executors.newFixedThreadPool(5); //5个窗口

        //2 一池一线程
        //ExecutorService threadPool1 = Executors.newSingleThreadExecutor(); //1个窗口
        
        //3 一池可扩容线程
        ExecutorService threadPool1 = Executors.newCachedThreadPool(); //可扩容

        //10个顾客请求
        try{
            for (int i = 1; i <= 10; i++) {
                //执行
                threadPool1.execute(()->{
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool1.shutdown();
        }


    }
}

打开源码查看这三个方法:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

可以发现都是使用的new ThreadPoolExecutor()方法。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

一共有7个参数。

10.2、ThreadPoolExecutor构造方法的七个参数

![image.png](https://img-blog.csdnimg.cn/img_convert/5cb908aa372cec02a5fad3c22f8f54d9.png#clientId=u3db532d9-9537-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=339&id=u44dde3f9&margin=[object Object]&name=image.png&originHeight=424&originWidth=636&originalType=binary&ratio=1&rotation=0&showTitle=false&size=41555&status=done&style=shadow&taskId=u3a3f7a23-8c93-45c0-b820-c41724bbccd&title=&width=508.8)

10.3、工作流程和拒绝策略

![image.png](https://img-blog.csdnimg.cn/img_convert/c19588f7d9f491f385a5675b3d5861ca.png#clientId=u3db532d9-9537-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=358&id=u92906647&margin=[object Object]&name=image.png&originHeight=448&originWidth=668&originalType=binary&ratio=1&rotation=0&showTitle=true&size=164761&status=done&style=shadow&taskId=u1e58c43c-3f38-4bd1-85d3-e4c2cb93728&title=线程池工作流程&width=534.4 “线程池工作流程”)

  1. 在创建了线程池后,线程池中的线程数为零
  2. 当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
    2.1 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
    2.2 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入阻塞队列;
    2.3 如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
    2.4 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行
  4. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
    4.1 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
    4.2 所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。

![image.png](https://img-blog.csdnimg.cn/img_convert/4cf69b3c9ce3585d3fc6c9ef8a38f1bf.png#clientId=u3db532d9-9537-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=207&id=uc1bfa373&margin=[object Object]&name=image.png&originHeight=259&originWidth=862&originalType=binary&ratio=1&rotation=0&showTitle=true&size=66418&status=done&style=shadow&taskId=u8b9a289f-66bf-41a3-89f2-ac9379f9979&title=拒绝策略&width=689.6 “拒绝策略”)

  • CallerRunsPolicy:当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
  • AbortPolicy:丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行
  • DiscardPolicy:直接丢弃,其他啥都没有
  • DiscardOldestPolicy:当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列workQueue中最老的一个任务,并将新任务加入

10.4、自定义线程池

public class ThreadPoolDemo2
{
    public static void main(String[] args)
    {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                5,
                2L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        //10个顾客请求
        try {
            for (int i = 1; i <= 10; i++) {
                //执行
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        }
        catch (
                Exception e) {
            e.printStackTrace();
        }
        finally {
            threadPool.shutdown();
        }
    }
}

11、Fork/Join分支合并框架

Fork/Join它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。Fork/Join框架要完成两件事情:

  • Fork:把一个复杂任务进行分拆,大事化小
  • Join:把分拆任务的结果进行合并

![image.png](https://img-blog.csdnimg.cn/img_convert/9338acc886ee27da8583274b0d05f682.png#clientId=u3db532d9-9537-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=516&id=u746e3cf7&margin=[object Object]&name=image.png&originHeight=645&originWidth=851&originalType=binary&ratio=1&rotation=0&showTitle=false&size=82734&status=done&style=shadow&taskId=u1d9f2d3c-d2e5-451d-8a11-46d856ed01c&title=&width=680.8)
在Java的Fork/Join框架中,使用两个类完成上述操作:

  • ForkJoinTask:我们要使用Fork/Join框架,首先需要创建一个ForkJoin任务。该类提供了在任务中执行fork和join的机制。通常情况下我们不需要直接集成 ForkJoinTask类,只需要继承它的子类,Fork/Join框架提供了两个子类:
    1、RecursiveAction:用于没有返回结果的任务
    2、RecursiveTask:用于有返回结果的任务
  • ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行
  • RecursiveTask:继承后可以实现递归(自己调自己)调用的任务
class MyTask extends RecursiveTask<Integer>
{

    //拆分差值不能超过10
    private static final Integer VALUE = 10;
    private int begin; //拆分开始值
    private int end; //拆分结束值
    private int result; //返回结果值

    //创建有参数构造
    public MyTask(int begin, int end){
        this.begin = begin;
        this.end = end;
    }

    //拆分和合并的过程
    @Override
    protected Integer compute()
    {
        //判断相加的两个值是否大于10
        if (end-begin <= 10){
            //相加操作
            for (int i = begin; i <= end; i++) {
                result += i;
            }

        } else {
            //进一步拆分
            //获取数据的中间值
            int middle = (begin+end)/2;

            //拆分左边部分
            MyTask task01 = new MyTask(begin, middle);
            //拆分右边部分
            MyTask task02 = new MyTask(middle+1, end);

            task01.fork();
            task02.fork();

            //合并结果
            result = task01.join() + task02.join();
        }

        return result;
    }
}
public class ForkJoinDemo1
{
    public static void main(String[] args) throws ExecutionException, InterruptedException
    {
        //创建MyTask对象
        MyTask myTask = new MyTask(0, 100);

        //创建分支合并池对象
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask);

        //获取最终合并之后结果
        Integer result = forkJoinTask.get();
        System.out.println(result);

        //关闭池对象
        forkJoinPool.shutdown();
    }
}

11.1、Fork/Join 框架的实现原理

ForkJoinPool由ForkJoinTask数组和ForkJoinWorkerThread数组组成,ForkJoinTask数组负责将存放以及将程序提交给ForkJoinPool,而ForkJoinWorkerThread负责执行这些任务。
![image.png](https://img-blog.csdnimg.cn/img_convert/0d99153d88260ec6f40f71d8ee259851.png#clientId=u3db532d9-9537-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=536&id=u3f07653c&margin=[object Object]&name=image.png&originHeight=670&originWidth=754&originalType=binary&ratio=1&rotation=0&showTitle=true&size=74935&status=done&style=shadow&taskId=u127ec2d7-e14e-4b30-a6e0-90f51bf9f07&title=fork方法&width=603.2 “fork方法”)
![image.png](https://img-blog.csdnimg.cn/img_convert/b21a10b616ce0dc67019cf4bf9e3d0fb.png#clientId=u3db532d9-9537-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=469&id=uc41c4149&margin=[object Object]&name=image.png&originHeight=586&originWidth=996&originalType=binary&ratio=1&rotation=0&showTitle=false&size=70096&status=done&style=shadow&taskId=u13603af3-61ec-407f-a306-c078c285fc9&title=&width=796.8)


12、异步回调

public class CompletableFutureDemo
{
    public static void main(String[] args) throws ExecutionException, InterruptedException
    {
        //异步调用没有返回值的
        CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(()->{
            System.out.println(Thread.currentThread().getName() + " completableFuture1");
        });
        completableFuture1.get();

        //异步调用有返回值的
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName() + " completableFuture2");
            ///模拟异常
            int i = 10/0;
            return 1024;
        });
        completableFuture2.whenComplete((t,u)->{
            System.out.println("----t="+t); //----t=1024  方法返回值
            //----u=java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero  异常
            System.out.println("----u="+u);
        }).get();
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值