大厂必备技术之JUC并发编程

学无止境 Java工程师的进阶之旅



一、什么是JUC

1.1、简介

  • JUC就是java.util.concurrent工具包的简称,这是一个处理线程的工具包,JDK1.5开始出现

1.2、线程和进程

  • 进程:计算机分配资源的基本单位,具有独立功能的程序,例如QQ运行起来就是一个进程
  • 线程:系统分配处理器时间资源的基本单元,或者说是进程之内独立执行的一个个单元执行流

1.3、线程的状态

  • NEW(新建)
  • RUNNABLE(准备就绪)
  • BLOCKED(阻塞)
  • WAITING(等待)
  • TIMED_WAITING(超时不等待)
  • TERMINATED(终结)

1.4、wait & sleep

  • wait:Object的方法,任何对象实例都能调用。如果当前线程占用锁执行wait会释放锁(即代码要在synchronized中)
  • sleep:Thread的静态方法,不会释放锁也不需要占用锁

1.5、并发与并行

  • 串行:按先后顺序执行
  • 并行:多个任务同时执行(边听歌边学习)
  • 并发:多个线程访问同一个资源,多个线程对一个点(抢票,秒杀)

1.6、管程

  • Monitor监视器(锁):是一种同步机制,保证同一时间只有一个线程访问被保护的数据或代码
    JVM同步基于管程对象,进入获取管程对象,退出释放管程对象

1.7、用户线程和守护线程

  • 用户线程:自定义线程
  • 守护线程:比如垃圾回收线程

主线程结束,用户线程还在运行,JVM存活
主线程结束,没有用户线程,JVM结束

1.8、线程不由java创建
在这里插入图片描述
由操作系统本地方法创建
在这里插入图片描述



二、Lock

2.1、 复习Synchronized

2.1.1、作用范围

类型作用范围作用对象
代码块{}调用这个方法的对象
方法整个方法调用这个方法的对象
静态方法整个静态方法该类的所有对象
括号括起来的部分该类所有对象

synchronized不属于方法定义的一部分,因此无法被继承,如果子类重写父类的同步方法,子类并不同步,需要再加synchronized关键字或者子类直接调用父类同步方法

2.1.2、多线程编程步骤
实现高内聚,低耦合

  • 创建资源类,创建属性和操作方法
  • 创建多线程调用资源类的方法

2.1.3、卖票例子

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

        Ticket ticket = new Ticket();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    ticket.sale();
                }
            }
        }, "A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    ticket.sale();
                }
            }
        }, "B").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    ticket.sale();
                }
            }
        }, "C").start();

    }
}

class Ticket {
    private int num = 1000;

    public synchronized void sale() {
        if (num > 0) {
            num--;
            System.out.println(Thread.currentThread().getName() + "卖出一张票:" + "余票" + num);
        }
    }
}

2.2、Lock使用与对比

  • Lock不是Java语言内置的,synchronized是Java的关键字,因此是内置特性
  • Lock是一个,通过这个类可以实现同步访问
  • Lock需要手动释放锁,synchronized代码块执行完后会字段释放锁

2.2.1、Lock接口

ReentrantLock可重入锁(后文会说,这里直接演示)

import java.util.concurrent.locks.ReentrantLock;

public class LSaleTicket {
    public static void main(String[] args) {
        LTicket lTicket = new LTicket();

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

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

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

}

class LTicket {
    private int num = 1000;
	
	// 创建可重入锁
    private final ReentrantLock lock = new ReentrantLock();

    public void sale() {
        lock.lock();
        try {
            if (num > 0) {
                num--;
                System.out.println(Thread.currentThread().getName() + "卖出一张票,余票" + num);
            }
        // 必须放在finally块中解锁,防止死锁
        } finally {
            lock.unlock();
        }

    }
}

2.2.2、对比

  • Lock是一个接口。synchronized是Java关键字,synchronized是内置的语言实现
  • synchronized发生异常时会自动释放线程占有的锁,因此不会导致死锁。
  • Lock发生异常时,如果没有主动通过unLock()释放,会导致死锁,因此使用Lock需要在finally块中释放锁
  • Lock可以让等待锁的线程响应中断,而synchronized却不行,等待的线程会一直等待下去,不能响应中断
  • Lock可以判断有没有成功获取锁,synchronized无法办到
  • Lock可以提高多个线程进行读操作的效率(读写锁)
  • 当竞争不激烈时两者性能差不多,当竞争非常激烈时,Lock的性能远大于synchronized


三、线程间通信

3.1、概念

在多线程模式下进行工作,除了要考虑各个线程之间是否同步、如何竞争锁等问题,还要考虑这样一个问题:线程之间有的时候需要相互配合来共同完成一件事情。

把一个大的任务拆分成多个不同的任务线,每个任务线中都有更小的执行步骤。各个线程之间需要彼此配合:A 线程执行一步唤醒 B 线程,自己等待;B 线程执行一步,唤醒 A 线程,自己等待……

3.2、核心语法

1、Object 类的 wait() 方法

  • wait() 方法会导致当前线程进入等待状态
  • 必须是另外一个线程调用 notify() 或 notifyAll() 方法来唤醒
  • “for this object” 表示还是要使用同一个对象分别调用 wait()、notify()、notifyAll() 这些方法

2、Object 类的 notify() 方法

  • notify() 方法只唤醒一个线程
  • 处于等待状态的线程会被存放在对象监视器中的一个数组中
  • 如果在这个对象的监视器中维护的处于等待状态的线程是多个,那么 notify() 方法会随机唤醒一个
  • notfiy() 方法无法精确唤醒一个指定的线程,这个需求可以通过 Lock + Condition 方式实现(定制化通信)

3、Object 类的 notifyAll() 方法

  • 唤醒当前对象监视器上等待的所有线程。

3.3、案例演示

3.3.1、需求分析

  • 设定一个成员变量,作为两个线程都要操作的共享数据,设置初始化值为 0
  • A 线程执行 +1 操作
  • B 线程执行 -1 操作
  • A、B 两个线程交替执行

3.3.2、synchronized代码实现

public class STest {
    public static void main(String[] args) {
        Share share = new Share();

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

    }
}

class Share {

    private int num = 0;

    public synchronized void incr() throws InterruptedException {
        if (num > 0) {
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "::" + num);
        this.notifyAll();
    }

    public synchronized void decr() throws InterruptedException {
        if (num <= 0) {
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "::" + num);
        this.notifyAll();
    }

}

3.3.3、虚假唤醒

问题描述:

当上面的例子中,线程数量从两个增加到四个,计算结果就会出错:
thread-a 线程:1
thread-c 线程:2
thread-a 线程:3
thread-b 线程:2
thread-d 线程:1

问题分析:

1、使用 if 的情况(仅判断一次)

假设C线程判断num<=0后,进行等待(在哪里等就在哪里醒),被别的线程唤醒后不会再进行num判断,会导致数值不符合预期

在这里插入图片描述
2、使用 while 解决问题
在这里插入图片描述
3、小结:

要解决虚假唤醒问题,就需要对线程间通信时的判断条件使用 while 循环结构来执行,而不是 if 分支判断。

在这里插入图片描述

3.3.3、Lock代码实现

public class Demo02 {
    public static void main(String[] args) {
        Share share = new Share();


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

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

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

    }
}

class Share {

    private int num = 0;

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

    public void incr() throws InterruptedException {
        lock.lock();
        
        try {
            while (num > 0) {
                // 等待
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "::" + num);
            // 通知
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void decr() throws InterruptedException {
        lock.lock();

        try {
            while (num <= 0) {
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "::" + num);
            
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

3.4、定制化通信

1、可以通过可重入锁的多种情况来达成,每把钥匙都对应同一把重入锁

private Lock lock = new ReentrantLock();
//声明钥匙 A
private Condition conditionA = lock.newCondition();
//声明钥匙 B
private Condition conditionB = lock.newCondition();
//声明钥匙 C
private Condition conditionC = lock.newCondition();
conditionA.await();  //A等待
conditionA.signal(); //唤醒A

A 线程打印 5 次 A,B 线程打印 10 次 B,C 线程打印 15 次 C,按照此顺序循环 10 轮

2、代码演示

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

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.printA(i);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.printB(i);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.printC(i);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "CC").start();


    }
}

class Test {
    //通信对象:0--打印 A 1---打印 B 2----打印 C
    private int number = 0;
    //声明锁
    private Lock lock = new ReentrantLock();
    //声明钥匙 A
    private Condition conditionA = lock.newCondition();
    //声明钥匙 B
    private Condition conditionB = lock.newCondition();
    //声明钥匙 C
    private Condition conditionC = lock.newCondition();

    /**
     * A 打印 5 次
     */
    public void printA(int j) {
        try {
            lock.lock();
            while (number != 0) {
                conditionA.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 A,第" + j + " 轮开始");
            //输出 5 次 A
            for (int i = 0; i < 5; i++) {
                System.out.println("A");
            }
            //开始打印 B
            number = 1;
            //唤醒 B
            conditionB.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * B 打印 10 次
     */
    public void printB(int j) {
        try {
            lock.lock();
            while (number != 1) {
                conditionB.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 B,第" + j + " 轮开始");
            //输出 10 次 B
            for (int i = 0; i < 10; i++) {
                System.out.println("B");
            }
            //开始打印 C
            number = 2;
            //唤醒 C
            conditionC.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    /**
     * C 打印 15 次
     */
    public void printC(int j) {
        try {
            lock.lock();
            while (number != 2) {
                conditionC.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 C,第" + j + " 轮开始");
            //输出 15 次 C
            for (int i = 0; i < 15; i++) {
                System.out.println("C");
            }
            System.out.println("-----------------------------------------");
            //开始打印 A
            number = 0;
            //唤醒 A
            conditionA.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}


四、集合的线程安全

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

ArrayList 并发情况下会出现ConcurrentModificationException并发修改异常

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

        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));

                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

4.2、解决方案

4.2.1、Vector

1、解决

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

2、源码

// Vector类添加元素方法为同步方法
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
	public synchronized boolean add(E e) {
	   modCount++;
	   ensureCapacityHelper(elementCount + 1);
	   elementData[elementCount++] = e;
	   return true;
	}	
}

3、说明

不会抛异常,线程安全,但是这个类太古老

Vector 是矢量队列,它是 JDK1.0 版本添加的类。继承于 AbstractList,实现了 List, RandomAccess, Cloneable 这些接口。 Vector 继承了 AbstractList,实现了 List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。 Vector 实现了 RandmoAccess 接口,即提供了随机访问功能。RandmoAccess 是 java 中用来被 List 实现,为 List 提供快速访问功能的。在Vector 中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。 Vector 实现了 Cloneable 接口,即实现 clone()函数。它能被克隆

4.2.2、 Collections

1、解决

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

2、源码

public class Collections {
	public static <T> List<T> synchronizedList(List<T> list) {
		return (list instanceof RandomAccess ?
	            new SynchronizedRandomAccessList<>(list) :
	            new SynchronizedList<>(list));
	}
}

3、说明

不会抛异常,但是锁定范围大,性能低

4.2.3、CopyOnWriteArrayList(写时复制)

1、解决

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

2、写时复制

兼顾并发读和独立写写前先上可重入锁,写数据时复制一份旧数据再写,写完用新数据覆盖旧数据

在这里插入图片描述

  • 使用写时复制技术要向集合对象中写入数据时:先把整个集合数组复制一份
  • 将新数据写入复制得到的新集合数组
  • 再让指向集合数组的变量指向新复制的集合数组

3、源码

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
	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.3、解决HashSet线程不安全

1、直接使用CopyOnWriteArraySet代替

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

    }
}

2、源码:

所在类:java.util.concurrent.CopyOnWriteArraySet

public boolean add(E e) {
     return al.addIfAbsent(e);
}

所在类:java.util.concurrent.CopyOnWriteArrayList

private boolean addIfAbsent(E e, Object[] snapshot) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] current = getArray();
        int len = current.length;
        if (snapshot != current) {
            // Optimize for lost race to another addXXX operation
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                if (current[i] != snapshot[i] && eq(e, current[i]))
                    return false;
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        Object[] newElements = Arrays.copyOf(current, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

4.4、 解决HashMap线程不安全

1、直接使用ConcurrentHashMap解决

public class MySafe {
    public static void main(String[] args) {
        // 1、创建集合对象
        Map<String, String> map = new ConcurrentHashMap<>();

        // 2、创建多个线程执行读写操作
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++) {
                    String key = UUID.randomUUID().toString().replace("-", "").substring(0, 5);
                    String value = UUID.randomUUID().toString().replace("-", "").substring(0, 5);
                    map.put(key, value);
                    System.out.println("map = " + map);
                }

            }, "thread" + i).start();
        }

    }
}

2、说明

ConcurrentHashMap 底层使用的是『锁分段』技术。它的典型应用就是用来实现微服务的注册中心。

  • 微服务名称:作为 Map 的 key
  • 微服务对象:作为 Map 的 value
  • 使用 ConcurrentHashMap 实现并发读写
  • 发现服务:读操作
  • 注册服务:写操作


五、八锁现象

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

5.1、对象层

普通 synchronized 锁的对象是方法的调用者

1、两个同步方法,同一个对象演示

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

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

        TimeUnit.SECONDS.sleep(1);

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

class Phone {
    public synchronized void sendEmail() throws Exception {
        System.out.println("---sendEmail---");
    }

    public synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:同一把锁,所以先sendEmail

---sendEmail---
---sendSMS---

2、 两个同步方法,同一个对象,停 4 秒在短信方法内

class Phone {
    public synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(3);
        System.out.println("---sendEmail---");
    }

    public synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:同一把锁,依然是先sendEmail

---sendEmail---
---sendSMS---

3、两个对象,两个同步方法(注意睡眠时间)

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

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

        TimeUnit.SECONDS.sleep(1);

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

    }
}

class Phone {
    public synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("---sendEmail---");
    }

    public synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:不同锁,先sendSMS,不同对象在普通同步方法分配的是不同的锁

---sendSMS---
---sendEmail---

5.2、普通方法层

普通方法没有锁!不是同步方法,就不受锁的影响,正常执行

4、 一个对象,新增普通的 hello 方法

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

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

        TimeUnit.SECONDS.sleep(1);

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

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

    }
}

class Phone {
    public synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendEmail---");
    }

    public synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }

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

结果:先hello,不受锁的影响

---hello---
---sendEmail---
---sendSMS---

5.3、类层

不同实例对象的Class类模板只有一个,static静态的同步方法,锁的是Class

5、两个静态的同步方法,一个对象

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

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

        TimeUnit.SECONDS.sleep(1);

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

    }
}

class Phone {
    public static synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("---sendEmail---");
    }

    public static synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:同一把锁,先sendEmail

---sendEmail---
---sendSMS---

6、两个对象!增加两个静态的同步方法

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

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

        TimeUnit.SECONDS.sleep(1);

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

    }
}

class Phone {
    public static synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(2);
        System.out.println("---sendEmail---");
    }

    public static synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:同一把锁,锁的是类,依然是先sendEmail

---sendEmail---
---sendSMS---

5.4、混合层

静态同步方法锁的是Class类模板,普通同步方法锁的是实例化的对象,锁的对象不同

7、1个静态的同步方法,1个普通的同步方法 ,一个对象(注意睡眠时间)

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

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

        TimeUnit.SECONDS.sleep(1);

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

    }
}

class Phone {
    public synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendEmail---");
    }

    public static synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }
}

结果:不同锁,先sendSMS

---sendSMS---
---sendEmail---

8、1个静态的同步方法,1个普通的同步方法 ,两个对象(注意睡眠时间)

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

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

        TimeUnit.SECONDS.sleep(1);

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

    }
}

class Phone {
    public synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("---sendEmail---");
    }

    public static synchronized void sendSMS() throws Exception {
        System.out.println("---sendSMS---");
    }

}

结果:不同锁,先sendSMS

---sendSMS---
---sendEmail---


六、多线程锁

6.1、公平锁与非公平锁

公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必唤醒所有线程,会减少唤起线程的数量。

  • 缺点:可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

创建可重入锁的时候可以设置公平或非公平锁(默认是非公平锁 )
在这里插入图片描述

6.2、可重入锁

  • synchronized:隐式,自动获取释放锁
  • Lock:显式,手动获取释放锁

6.2.1、synchronized演示可重入锁

1、拿到外层的锁就可以拿到内层的锁

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

        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() + "内层锁");
                    }
                }
            }
        }, "laptoy-t").start();
    }

}

// laptoy-t外层锁
// laptoy-t中层锁
// laptoy-t内层锁

2、递归锁

public class SyncLock {
    public static void main(String[] args) {
        new SyncLock().add();
    }

    synchronized void add() {
        add();
    }
}

// 栈溢出异常,证明可重入锁可以递归调用

6.2.2、Lock演示可重入锁

public class SyncLock {
    public static void main(String[] args) {
        
        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();
            }
        }, "laptoy-t").start();
    }
}

// laptoy-t外层锁
// laptoy-t内层锁

6.3、死锁

6.3.1、简介

两个或两个以上进程执行过程中,因为争夺资源而造成的一种互相等待的现象,如果没有外力干涉,他们无法再执行下去

在这里插入图片描述

6.3.2、产生原因

  • 系统资源不足
  • 进程运行推荐顺序不合适
  • 资源分配不当

6.3.3、快速演示死锁代码

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");
                synchronized (b) {
                    System.out.println(Thread.currentThread().getName() + "线程 获取锁b");
                }
            }
        }, "A").start();

        new Thread(() -> {
            synchronized (b) {
                System.out.println(Thread.currentThread().getName() + "线程 持有锁b,试图获取锁b");
                synchronized (a) {
                    System.out.println(Thread.currentThread().getName() + "线程 获取锁a");
                }
            }
        }, "B").start();
    }

}
// A线程 持有锁a,试图获取锁b
// B线程 持有锁b,试图获取锁b

6.3.4、验证死锁

执行jps查询正在执行的进程
在这里插入图片描述
执行jstack 13484追踪堆栈
在这里插入图片描述



七、Callable接口

7.1、简介

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

7.2、特点

  • 为了实现 Runnable,需要实现不返回任何内容的run()方法,而对于Callable,需要实现在完成时返回结果的call()方法。
  • call()方法可以引发异常,而 run()则不能。
  • 为实现 Callable 而必须重写 call 方法
  • 不能直接替换Runnable创建线程,因为 Thread 类的构造方法根本没有 Callable在这里插入图片描述

7.3、FutureTask

7.3.1、源码

继承FutureTask类,该类实现了Runnable接口,构造方法可以传入Callable实现类

public class FutureTask<V> implements RunnableFuture<V> {
	public FutureTask(Callable<V> callable) {
	    if (callable == null)
	        throw new NullPointerException();
	    this.callable = callable;
	    this.state = NEW;       // ensure visibility of callable 
	}
}

public interface RunnableFuture<V> extends Runnable, Future<V> {}

7.3.2、原理

在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成

  • 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态
  • 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
  • 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法
  • 一旦计算完成,就不能再重新开始或取消计算
  • get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常
  • get 只计算一次,因此 get 方法放到最后

7.3.3、使用

1、使用原生方式创建线程

public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());

        new Thread(futureTask, "A").start();  // A线程执行成功
        System.out.println(futureTask.get()); // 20
    }
}

class MyThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "线程执行成功");
        return 20;
    }
}

2、使用Lambda表达式

public class Demo02 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + "线程执行成功");
            return 20;
        });

        new Thread(futureTask, "A").start();	// A线程执行成功
        System.out.println(futureTask.get());	// 20
    }
}

3、isDone()方法验证未来任务只需要计算一次

public class Demo03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
            System.out.println(Thread.currentThread().getName() + "执行成功");
            return 20;
        });

        new Thread(futureTask, "主线程").start();

        while (!futureTask.isDone()) {
            System.out.println("wait...计算中");
        }

        System.out.println("-----未来任务计算成功-----");

        System.out.println("第一次获取未来任务的结果:" + futureTask.get());
        System.out.println("第二次获取未来任务的结果:" + futureTask.get());

        System.out.println(Thread.currentThread().getName() + "执行结束");
    }
}

在这里插入图片描述



八、辅助类

8.1、简介

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

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

8.2、减少计数CountDownLatch

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

  • CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞
  • 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
  • 当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行

场景:6个同学离开教室再锁门

public class Demo01 {
    public static void main(String[] args) throws Exception {
        // 定义一个数值为6的计数器
        CountDownLatch countDownLatch = new CountDownLatch(6);
        // 创建6个同学
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "号同学离开了教室");
                // 计数减1
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }
        // 计数器未清0前等待
        countDownLatch.await();

        System.out.println("全部离开了,现在的计数器为" + countDownLatch.getCount());
        System.out.println("锁门");
    }
}

8.3、循环栅栏 CyclicBarrier

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

场景: 集齐 7 颗龙珠就可以召唤神龙

public class Demo01 {
    public static void main(String[] args) throws Exception {
        CyclicBarrier c = new CyclicBarrier(7, () -> {
            System.out.println("集齐七颗龙珠召唤神龙成功");
        });

        for (int i = 1; i <= 7; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + " 星龙收集成功");
                    // 等待达到目标数
                    c.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }

    }
}

看一下源码,构造方法传入的数量赋值给count
在这里插入图片描述
每次调用await其实是调用dowait()
在这里插入图片描述
每次调用count-1,直到index=0时就放行
在这里插入图片描述

8.4、 信号灯 Semaphore

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

场景:六辆车三个车位

public class Demo01 {
    public static void main(String[] args) throws Exception {
        // 设置三个车位
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {

                try {
                    // 抢占
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " 抢到了车位");

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

        }
    }
}


九、读写锁

9.1、回顾锁

回顾悲观锁和乐观锁的概念

  • 悲观锁:单独每个人完成事情的时候,执行上锁解锁。解决并发中的问题,不支持并发操作,只能一个一个操作,效率低
  • 乐观锁:每执行一件事情,都会比较数据版本号,谁先提交,谁先提交版本号

新概念:

  • 表锁:整个表操作,不会发生死锁
  • 行锁:每个表中的单独一行进行加锁,会发生死锁
  • 读锁:共享锁(可以有多个人读),会发生死锁
  • 写锁:独占锁(只能有一个人写),会发生死锁

9.2、读写锁

读写锁:一个资源可以被多个读线程访问,也可以被一个写线程访问,但不能同时存在读写线程,读写互斥,读读共享

读写锁ReentrantReadWriteLock
1、 读锁为ReentrantReadWriteLock.ReadLockreadLock()方法
2、 写锁为ReentrantReadWriteLock.WriteLockwriteLock()方法

创建读写锁对象private ReadWriteLock rwLock = new ReentrantReadWriteLock();
1、写锁 加锁rwLock.writeLock().lock();
2、写锁 解锁rwLock.writeLock().unlock();
3、读锁 加锁rwLock.readLock().lock();
4、读锁 解锁rwLock.readLock().unlock();

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

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

        // 写锁线程
        for (int i = 1; i <= 20; i++) {
            int num = i;
            new Thread(() -> {
                myCache.put(num + "", num + "");
            }, String.valueOf(i)).start();
        }

        // 读锁线程
        for (int i = 1; i <= 20; i++) {
            int num = i;
            new Thread(() -> {
                myCache.get(num + "");
            }, String.valueOf(i)).start();
        }
    }
}


class MyCache {

    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private volatile Map<String, Object> map = new HashMap<>();

    // 写数据
    public void put(String key, Object value) {
        // 写锁 加锁
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 正在写操作" + key);
            // 模拟写操作花时
            TimeUnit.MICROSECONDS.sleep(300);
            
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + " 写完了" + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 写锁 解锁
            readWriteLock.writeLock().unlock();
        }
    }

    // 读数据
    public Object get(String key) {
        Object result = null;
        // 读锁 加锁
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 正在读操作" + key);
            TimeUnit.MICROSECONDS.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + " 读完了" + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 读锁 解锁
            readWriteLock.readLock().unlock();
        }
        return result;
    }

}

9.3、锁的局限性

  1. 无锁:多线程抢夺资源
  2. synchronized和ReentrantLock:都是独占,每次只可以一个操作,不能共享
  3. ReentrantReadWriteLock:读读可以共享,提升性能,但是不能多人写
    缺点:造成死锁(一直读,不能写),读进程不能写,写进程可以读。
  4. 通过(写锁释放前可以读这一特性)进行锁降级,可以提高效率

9.4、写锁的锁降级

1、目的: 写锁降级为读锁(一般等级写锁高于读锁)(利用写锁时可以读这一特性)

2、正常情况是获取写锁,释放写锁,获取读锁,释放读锁

3、降级流程:获取写锁->获取读锁->释放写锁->释放读锁

其实就是在写锁释放前进行获取读锁,可以提高线程效率

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("laptoy");
        
        //2 获取读锁
        readLock.lock();
        System.out.println("---read---");
        
        //3 释放写锁
        writeLock.unlock();

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


十、阻塞队列 BlockingQueue

10.1、简介

阻塞队列是共享队列(多线程操作),一端输入,一端输出
所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起
在这里插入图片描述
执行流程:

  • 当队列是空的,从队列中获取元素的操作将会被阻塞
  • 当队列是满的,从队列中添加元素的操作将会被阻塞
  • 试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
  • 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增

举个例子:

生产者消费者问题:

通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然

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

10.2、种类

第一第二比较常用

1、ArrayBlockingQueue数组有界阻塞队列

生产者和消费者共用同一个锁,无法并行

2、 LinkedBlockingQueue链表有界阻塞队列),默认值为integer.MAX_VALUE

生产者和消费者采用了独立的锁,可以并行

3、PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现

Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。

4、DelayQueue

类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。

5、SynchronousQueue

一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。

6、LinkedBlockingDeque

使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。

7、LinkedTransferQueue

它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。

10.3、方法

方法类型抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e,time,unit)
移除remove()poll()take()poll(time,unit)
检查element()peek()不可用不可用
  • 抛出异常
    当队列满再add()会抛出IllegalStateException:Queue full
    当队列空再remove()会抛NoSuchElementException
  • 特殊值
    插入成功true,失败false
    移除成功返回出队列的元素,失败返回null
  • 阻塞
    队列满继续put会阻塞直到put成功或响应中断退出
    队列空继续take会阻塞直到take成功
  • 超时退出
    阻塞队列满时,队列阻塞生产者一定时间,超时后生产者线程退出

10.4、代码演示

1、抛出异常演示

public class BlockingQueueDemo {
    public static void main(String[] args) {
        ArrayBlockingQueue<Object> 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.add("d")); // 抛异常
      
        System.out.println(blockingQueue.element());// a,检查出即将出队列的元素

        System.out.println(blockingQueue.remove()); // a
        System.out.println(blockingQueue.remove()); // b
        System.out.println(blockingQueue.remove()); // c
        
      //System.out.println(blockingQueue.remove()); //抛异常
    }
}

2、特殊值演示

public class BlockingQueueDemo {
    public static void main(String[] args) {
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);

        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("d")); // false

        System.out.println(blockingQueue.peek());     // a,检查出即将出队列的元素

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

3、阻塞演示

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);

        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        // blockingQueue.put("d"); //阻塞

        System.out.println(blockingQueue.take()); // a
        System.out.println(blockingQueue.take()); // b
        System.out.println(blockingQueue.take()); // c
        // System.out.println(blockingQueue.take()); // 阻塞

    }
}

4、超时演示

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.offer("a", 3L, TimeUnit.SECONDS)); // true
        System.out.println(blockingQueue.offer("b", 3L, TimeUnit.SECONDS)); // true
        System.out.println(blockingQueue.offer("c", 3L, TimeUnit.SECONDS)); // true
        System.out.println(blockingQueue.offer("d", 3L, TimeUnit.SECONDS)); // false,阻塞三秒后返回false

        System.out.println(blockingQueue.poll(3L, TimeUnit.SECONDS)); // a
        System.out.println(blockingQueue.poll(3L, TimeUnit.SECONDS)); // b
        System.out.println(blockingQueue.poll(3L, TimeUnit.SECONDS)); // c
        System.out.println(blockingQueue.poll(3L, TimeUnit.SECONDS)); // null,阻塞三秒后返回null
    }
}


十一、线程池

11.1、简介

1、概述

一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

2、线程池的优势

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

3、特点

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

4、继承图
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors,ExecutorService,ThreadPoolExecutor 这几个类
在这里插入图片描述

11.2、功能线程池

Executors帮我们封装好的线程池,拿来即用

  • Executors.newFixedThreadPool(5):一池5线程(定长线程池)
  • Executors.newCachedThreadPool():自动分配线程数量(可缓存线程池)
  • Executors.newSingleThreadExecutor(): 一池一线程(单线程化线程池)
public class Demo01 {
    public static void main(String[] args) {
        // 一池5线程
        ExecutorService pool1 = Executors.newFixedThreadPool(5);
        // 自动分配多少线程
        ExecutorService pool2 = Executors.newCachedThreadPool();
        // 一池一线程
        ExecutorService pool3 = Executors.newSingleThreadExecutor();

        try {
            for (int i = 0; i <= 10; i++) {
                pool1.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 执行成功");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool1.shutdown();
        }
    }
}

// 以一池5线程演示结果
// pool-1-thread-2 执行成功
// pool-1-thread-4 执行成功
// pool-1-thread-1 执行成功
// pool-1-thread-3 执行成功
// pool-1-thread-1 执行成功
// pool-1-thread-4 执行成功
// pool-1-thread-2 执行成功
// pool-1-thread-5 执行成功
// pool-1-thread-4 执行成功
// pool-1-thread-1 执行成功
// pool-1-thread-3 执行成功

底层都是new ThreadPoolExecutor

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

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

在这里插入图片描述

11.3、ThreadPoolExecutor的七个参数

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
参数说明
int corePoolSize核心线程数
int maximumPoolSize最大线程数
long keepAliveTime空闲线程存活时间
TimeUnit unit存活的时间单位
BlockingQueue workQueue存放提交但未执行任务的队列
ThreadFactory threadFactory创建线程的工厂类
RejectedExecutionHandler handler等待队列满后的拒绝策略

11.4、执行流程

创建线程池时不会创建线程,执行run()才会进行execute()创建
在这里插入图片描述
假设最大线程数为5,核心线程为2,阻塞队列为3,现在进来9个线程

  • 进来2个线程,调用线程池核心线程执行 (1 2
  • 继续来三个线程(超过核心线程数),这三个就进入阻塞队列 (3 4 5
  • 继续来三个线程(超过阻塞队列但未超过最大线程数),这三个线程会直接优先创建6 7 8
  • 继续来线程(超过最大线程数),会通过拒绝策略进行拒绝 (9 ...

在这里插入图片描述

11.5、拒绝策略(handler)

  • AbortPolicy(默认):丢弃任务,并抛出拒绝执行 RejectedExecutionException
  • CallerRunsPolicy:不会丢弃任务,也不会抛出异常,将任务返还给调用者执行
  • DiscardPolicy:直接丢弃
  • DiscardOldestPolicy:抛弃阻塞队列等待时间最久的任务,把当前任务加入队列中尝试再次提交当前任务

11.6、线程工厂

线程工厂指定创建线程的方式,需要实现 ThreadFactory 接口,并实现 newThread(Runnable r) 方法。该参数可以不用指定,Executors 框架已经为我们实现了一个默认的线程工厂:

/**
 * The default thread factory.
 */
private static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
 
    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }
 
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}


  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Laptoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值