学习JUC和多线程

JUC

JMM

在这里插入图片描述

原子性,可见性与有序性

原子性

原子性 解释
概念 执行线程对共享资源发生写操作,其他线程只能看到结果,而中间过程看不到
例子 银行转账流程中,A账户减少了100元,那么B账户就会多100元,这两个动作是一个原子操作。我们不会看到A减少了100元,但是B余额保持不变的中间结果
实现 加锁;CAS;基本和引用类型中除了Long和Double的写操作都是原子类型

可见性

可见性 解释
概念 一个线程对于共享变量的更新,对于后续访问该变量的线程是否可见的问题。
例子和实现 请看volatile的模拟可见性代码

有序性

有序性 解释
概念 计算机在执行程序时,为了提高性能,存在编译器和处理器对指令重排的问题
例子 如下的Demo1、Demo2
实现 使用volatile

在这里插入图片描述

/*
    1 分配对象的内存空间(堆上)
    2 初始化对象
    3 设置instance指向刚分配的内存地址
    第二步和第三步可能会发生重排序,导致引用型变量指向了一个不为null但是也不完整的对象。(在多线程下的单例模式中,我们必须通过volatile来禁止指令重排序)
*/
Instance instance = new Instance()
public class ReSortDemo {
    int a;
    boolean flag = false;
    // 重排序demo1
    public void method1() {
        // 这里可能出现指令重排问题:
        // 线程一:编译器优化使得,flag先为true,然后线程二走method2进去,a=0+1=1,出现线程不安全的问题
        a = 1;
        flag = true;
    }

    public void method2() {
        if (flag) {
            a = a + 1;
            System.out.println("最终的a:" + a);
        }
    }
    // 重排序demo2:
    void resetSort() {
    int a = 1; // 语句1
    int b = 2; // 语句2
    int x = a + 1;// 语句3
    int y = a * a;// 语句4

    /* 指令重排:编译器优化、会将语句顺序打乱
       重排可能性:1234、1324、2134
       但是4是不能在1之前的,因为编译器优化会保证数据结果之间的依赖性
     */
}
}

乐观锁和悲观锁

乐观锁 悲观锁
概念 读数据时,其他线程不会修改;写数据时再判断是否被修改 读写数据时,其他线程都会修改
加锁 写时先读出当前版本号,然后加锁 每次读写都加锁
原理 CAS比较并交换 AQS抽象队列同步器

Synchronized

概念:synchronized是关键字,是一个独占悲观锁,也是可重入锁。

底层原理:

  • 进入时,执行monitorenter,将计数器+1,释放锁monitorexit时,计数器-1
  • 当一个线程判断到计数器为0时,则当前锁空闲,可以占用;反之,当前线程进入等待状态

作用范围:

  1. 作用成员变量和非静态方法,锁的是对象的实例=this对象
  2. 作用静态方法,锁的是Class实例,因为静态方法需要Class而不属于对象
  3. 作用代码块,锁的是所有代码块中的对象

Reentrant

概念:继承里Lock接口,是一个可重入独占锁,默认是非公平锁。

底层原理:通过AQS来实现锁的获取和释放

如何避免死锁:

  1. 晌应中断
  2. 可轮询锁
  3. 定时锁
public class InterruptDeadLock {
    private ReentrantLock lock1 = new ReentrantLock();
    private ReentrantLock lock2 = new ReentrantLock();

    public Thread lock1() {
        Thread t = new Thread(() -> {
            try {
                lock1.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock2.lockInterruptibly();
                System.out.println(Thread.currentThread().getName() + ",执行完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock1.isHeldByCurrentThread()) {
                    lock1.unlock();
                }
                if (lock2.isHeldByCurrentThread()) {
                    lock2.unlock();
                }
                System.out.println(Thread.currentThread().getName() + ",finally退出完毕");
            }
        });
        t.start();
        return t;
    }


    public Thread lock2() {
        Thread t = new Thread(() -> {
            try {
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock1.lockInterruptibly();
                System.out.println(Thread.currentThread().getName() + ",执行完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock1.isHeldByCurrentThread()) {
                    lock1.unlock();
                }
                if (lock2.isHeldByCurrentThread()) {
                    lock2.unlock();
                }
                System.out.println(Thread.currentThread().getName() + ",finally退出完毕");
            }
        });
        t.start();
        return t;
    }

    public static void main(String[] args) {
        long time = System.currentTimeMillis();
        InterruptDeadLock interruptDeadLock = new InterruptDeadLock();
        Thread t1 = interruptDeadLock.lock1();
        Thread t2 = interruptDeadLock.lock2();
        // 出现死锁,t2就开始释放锁
        while (true) {
            if (System.currentTimeMillis() - time >= 3000) {
                t2.interrupt();
            }
        }
    }

}
java.lang.InterruptedException

Thread-0,执行完毕
Thread-0,finally退出完毕
Thread-1,finally退出完毕

volatile

名称 原子性 可见性 有序性
是否保证 不保证,原因请看下面模拟的代码 保证 保证
底层原理 编译器优化指令重排,volatile不能保证 修改汇编Lock前缀指令,监听MESI缓存一致性协议 读写的内存屏障指令

模拟原子性:volatile不保证可见性,使用AtomicInteger就可以保证原子性

// 不保证原子性:使用javap -c反编译查看原因
private static void NotAutomaticByVolatile() {
    // 1.资源类
    MyResources myResources = new MyResources();
    // 2.模拟volatile不保证原子性
    for (int i = 0; i < 20; i++) {
        new Thread(() -> {
            for (int j = 0; j < 1000; j++) {
                myResources.add1();
            }
        }, String.valueOf(i)).start();
    }
    // 3.默认的2个线程是main和GC,所以让大于这个2个线程的等待
    while (Thread.activeCount() > 2) {
        Thread.yield();
    }
    // 4.volatile修饰的共享变量,不能保证原子性
    System.out.println(Thread.currentThread().getName() + ",data:" + myResources.data);
}

public class MyResources {
    volatile int data = 0;

    void add() {
        this.data = 1;
    }
    void add1() {
        this.data++;
    }
}

在这里插入图片描述

模拟可见性:volatile是一个轻量级的同步锁,保证可见性

private static void seeOkByVolatile() {
    MyResources myResources = new MyResources();

    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + ",进入啦");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();

        }
        myResources.add();
        System.out.println(Thread.currentThread().getName() + ",已经修改data:" + myResources.data);
    }, "线程A").start();

    while (myResources.data == 0) {
        /*    System.out.println(Thread.currentThread().getName() + ",while等待");*/
    }

    System.out.println(Thread.currentThread().getName() + ",模拟结束,data:" + myResources.data);
}

public class MyResources {
       volatile int data = 0;

    void add() {
        this.data = 1;
    }
}

volatile实现可见性的底层原理:

在这里插入图片描述

模拟有序性:单例设计模式

public class SingletonDemo {
    private static volatile SingletonDemo instance;

    private SingletonDemo() {
        System.out.println(Thread.currentThread().getName() + " 线程,创建实例了");
    }

    // 单线程下的单例设计模式是不安全的,会重复new 实例
    public static SingletonDemo getInstance() {
        if (instance == null) {
            instance = new SingletonDemo();
        }
        return instance;
    }

    // 共享变量未加volatile修饰的双端锁:由于指令重排序,还是有线程不安全的问题
    public static SingletonDemo getInstance1() {
        if (instance == null) {
            synchronized (SingletonDemo.class) {
                if (instance == null) {
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                SingletonDemo.getInstance1();
            }, String.valueOf(i)).start();
        }
    }
}

执行结果:无论多少次,就只能有一个线程创建实例

0 线程,创建实例了

volatile实现有效性底层原理:读写的内存屏障指令

在这里插入图片描述

原子性和CAS

AtomicInteger和CAS

AtomicInteger 解释
概念 AtomicInteger 内部使用 CAS 原子语义来处理加减等操作
CAS底层原理 自旋锁+Unsafe类(原子操作)
缺点 循环时间长,开销大;只能保证单个共享变量的原子操作;存在ABA问题

在这里插入图片描述

// 不保证原子性:使用javap -c反编译查看原因
private static void atomicByAtomicInteger() {
    // 1.资源类
    MyResources myResources = new MyResources();
    // 2.atomicInteger保证原子性
    for (int i = 0; i < 20; i++) {
        new Thread(() -> {
            for (int j = 0; j < 1000; j++) {
                myResources.add1();// i++等操作是不保证原子性
                myResources.addByAtomic();// 使用automicInteger保证原子性
            }
        }, String.valueOf(i)).start();
    }
    // 3.默认的2个线程是main和GC,所以让大于这个2个线程的等待
    while (Thread.activeCount() > 2) {
        Thread.yield();
    }
    System.out.println(Thread.currentThread().getName() + ",data:" + myResources.data);
    System.out.println(Thread.currentThread().getName() + ",atomicInteger:" + myResources.atomicInteger);
}

public class MyResources {
    volatile int data = 0;
    AtomicInteger atomicInteger = new AtomicInteger();

    void add() {
        this.data = 1;
    }

    void add1() {
        this.data++;
    }

    void addByAtomic() {
        this.atomicInteger.getAndIncrement();
    }
}

ABA问题

CAS会出现ABA问题:自旋操作只关心A和A的结果,中间B可能出现很多次,于是线程不安全

public class ABAQuestion {
    public static void main(String[] args) {
        AtomicReference<Integer> reference = new AtomicReference<>(1);

        new Thread(() -> {
            reference.compareAndSet(1, 2);
            reference.compareAndSet(2, 1);
        }, "线程1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            reference.compareAndSet(1, 1000);
            System.out.println(Thread.currentThread().getName() + ",update:" + reference.get());
        }, "线程2").start();
    }
}

解决ABA问题

解决ABA问题:使用AtomicStampedReference每次CAS指定版本号/时间戳

public class ABASolution {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100, 1);
        new Thread(() -> {
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 版本号1:" + stamp);
            //暂停1s等t2线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stampedReference.compareAndSet(100, 101, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + " 版本号2:" + stampedReference.getStamp());
            stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + " 版本号3:" + stampedReference.getStamp());
            System.out.println(Thread.currentThread().getName() + " 当前实际值:" + stampedReference.getReference());
        }, "t1").start();

        new Thread(() -> {
            int exceptStamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 期望版本号:" + exceptStamp);
            //暂停3s等t1线程
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // t1 线程执行完毕后,版本号 = 3 与 这里的 exceptStamp永不相同,所以执行会失败
            boolean result = stampedReference.compareAndSet(100, 200, exceptStamp, exceptStamp + 1);
            int nowStamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 修改值结果:" + result + " 当前版本号:" + nowStamp + " 期望版本号:" + exceptStamp);
            System.out.println(Thread.currentThread().getName() + " 当前实际值:" + stampedReference.getReference());
        }, "t2").start();
    }

}

执行结果:

t1 版本号1:1
t2 期望版本号:1
t1 版本号2:2
t1 版本号3:3
t1 当前实际值:100
t2 修改值结果:false 当前版本号:3 期望版本号:1
t2 当前实际值:100

单例设计模式

6种常见的单例模式写法

public class Singleton1 {

    private static Singleton1 instance;

    private Singleton1() {
    }
    // 方式1:懒汉式,线程不安全
    public static Singleton1 getInstance() {
        if (instance == null) {
            instance = new Singleton1();
        }
        return instance;
    }
}
public class Singleton2 {

    private static Singleton2 instance;

    private Singleton2() {
    }
    // 方式2:懒汉式,线程安全,加了synchronized
    public static synchronized Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}
public class Singleton3 {

    private static Singleton3 instance = new Singleton3();

    private Singleton3() {
    }

    // 方式2:饿汉式,线程安全,因为classloader机制避免了多线程的同步问题
    public static synchronized Singleton3 getInstance() {
        return instance;
    }
}
public class Singleton4 {
    // volatile防止代码指令重排
    private volatile static Singleton4 instance;

    private Singleton4() {
    }

    // 方式4:双端锁+volatile
    public static Singleton4 getInstance() {
        // 双端锁提高效率
        if (instance == null) {
            synchronized (Singleton4.class) {
                if (instance == null) {
                    instance = new Singleton4();
                }
            }
        }
        return instance;
    }
}
public class Singleton5 {
    private Singleton5() {
    }
    private static class SingletonHandle {
        private static final Singleton5 INSTANCE = new Singleton5();
    }
    // 方式5:静态内部类,比方式4更合理,因为内部类被装载了,才会使用;并且类加载机制保障了线程安全
    public static Singleton5 getInstance() {
        return SingletonHandle.INSTANCE;
    }
}
public enum Singleton6 {
    INSTANCE;
    // 方式6:枚举,更简洁,自动支持序列化机制,绝对防止多次实例化。
    public void whateverMethod() {

    }
}

集合不安全

并发修改异常:ConcurrentModificationException

// 普通的集合类都是线程不安全的:java.util.ConcurrentModificationException
private static void ListIsNotSafe() {
    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.toString());
        }, String.valueOf(i)).start();
    }
}

集合安全的方法

 集合安全:
    1.老API:Vector
    2.集合安全工具类:Collections.synchronizedList
    3.读写锁:new CopyOnWriteArrayList<>()
 private static void ListSafe() {
        List<String> list = new ArrayList<>();
        List<String> list1 = new Vector<>();
        List<String> list2 = Collections.synchronizedList(list);
        List<String> list3 = new CopyOnWriteArrayList<>();

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

CopyOnWrite

底层原理:读写分离,写时复制

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

HashSet底层

底层就是HashMap,但是add(e),是将key=e,value= PRESENT(map中的对象关联的虚拟值)

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
...
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

重入锁

Syn是可重入锁

class Phone {
    public synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + ",进入method1");
        method2();
    }

    private synchronized void method2() {
        System.out.println(Thread.currentThread().getName() + ",进入method2");

    }
}

public class SynIsRepeatableLock {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.method1();
        }, "线程1").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            phone.method1();
        }, "线程2").start();
    }
}

ReentrantLock是重入锁:(补充)加锁几次和解锁几次必须配对,如果配对都会生效

class Resources implements Runnable {

    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        method1();
    }

    // 补充:加锁几次和解锁几次必须配对,如果配对都会生效
    private void method1() {
        lock.lock();
        lock.lock();
        System.out.println(Thread.currentThread().getName() + ",进入method1");
        method2();
        lock.unlock();
        lock.unlock();
    }

    private void method2() {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + ",进入method2");
        lock.unlock();
    }
}

public class ReenIsRepeatableLock {
    public static void main(String[] args) {
        Resources resources = new Resources();
        Thread t1 = new Thread(resources);
        Thread t2 = new Thread(resources);
        t1.start();
        t2.start();
    }
}

自旋锁

自旋锁 解释
概念 正在获取锁的现在不会立刻阻塞,会循环等待其他线程释放锁的资源
优点 减少上下文线程切换的消耗
缺点 循环等待,消耗的是CPU资源

手写一个自旋锁:

class MyResource {

}

public class WhileLockDemo {

    AtomicReference<MyResource> atomicReference = new AtomicReference<>();
    MyResource myResource = new MyResource();

    private void method1() {
        System.out.println(Thread.currentThread().getName() + ",加锁啦");
        while (!atomicReference.compareAndSet(null, myResource)) {

        }

    }

    private void method2() {
        atomicReference.compareAndSet(myResource, null);
        System.out.println(Thread.currentThread().getName() + ",解锁啦");
    }

    public static void main(String[] args) {
        WhileLockDemo demo = new WhileLockDemo();
        new Thread(() -> {
            demo.method1();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            demo.method2();
        }, "线程1").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(() -> {
            demo.method1();
            demo.method2();
        }, "线程2").start();

    }

}

读写锁

class MyCache {
    //缓存应用的资源,必须使用volatile修饰
    private volatile Map<String, Object> map = new HashMap<>();
    //加入读写锁
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    /**
     * 模拟写锁上锁解锁
     */
    public void put(String key, Object value) {
        //写锁上锁
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t 正在写入,不可共享");
            map.put(key, value);
            //模拟网络拥堵
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t 写入完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //写锁解锁
            readWriteLock.writeLock().unlock();
        }
    }

    /**
     * 模拟读锁上锁解锁
     */
    public void get(String key) {
        //读锁上锁
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\t 正在读取,可共享");
            Object result = map.get(key);
            //模拟网络拥堵
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //读锁解锁
            readWriteLock.readLock().unlock();
        }
    }
}
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        //模拟读写锁:主要是学习map用volatile修饰
        MyCache cache = new MyCache();
        //写锁:独占
        for (int i = 1; i <= 5; i++) {
            final int tempInt = 1;
            new Thread(() -> {
                cache.put(tempInt + "", tempInt + "");
            }, String.valueOf(i)).start();
        }
        //读锁:可共享
        for (int i = 1; i <= 5; i++) {
            final int tempInt = 1;
            new Thread(() -> {
                cache.get(tempInt + "");
            }, String.valueOf(i)).start();
        }
    }
}

执行结果:

2     正在写入,不可共享
2     写入完成
1     正在写入,不可共享
1     写入完成
3     正在写入,不可共享
3     写入完成
4     正在写入,不可共享
4     写入完成
5     正在写入,不可共享
5     写入完成
1     正在读取,可共享
2     正在读取,可共享
3     正在读取,可共享
4     正在读取,可共享
5     正在读取,可共享
3     读取完成:1
2     读取完成:1
1     读取完成:1
5     读取完成:1
4     读取完成:1

Reentrant、Semaphore、CountDownLatch、CyclicBarrier区别

Reentrant Semaphore CountDownLatch CyclicBarrier
一次只允许一个线程访问某个资源 可以指定多个线程同时访问某个资源 一个或者多个线程,等待其他多个线程完成某件事情之后才能执行 多个线程互相等待,直到达到同一个同步点(屏障),再继续一起执行。

Semaphore

概念:一种基于技术的信号量,调用方法是acquire()、release()

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 模拟3个车位,同时只能有3个线程同时访问,形参=1就是syn
        // 1.同步器semaphore(信号量):指定X个线程可以获取资源
        Semaphore semaphore = new Semaphore(3);
        // 2.创建4个线程=有4个车位
        for (int i = 1; i <= 4; i++) {
            new Thread(() -> {
                try {
                    // 3.信号量开始计算
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "\t 抢到车位");
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(Thread.currentThread().getName() + "\t 停1s后离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 4.信号量释放计数
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}

执行结果:

1     抢到车位
3     抢到车位
2     抢到车位
2     停1s后离开车位
1     停1s后离开车位
3     停1s后离开车位
4     抢到车位
4     停1s后离开车位

CountDownLatch

概念:单线程等待,调用方法是await()

  • 使用枚举类给线程命名,学习countDownLatch和枚举类减少if判断作用
public enum CountryEnum {

    ONE(1, "齐"),
    TWE(2, "燕"),
    THREE(3, "楚"),
    FOUR(4, "赵"),
    FIVE(5, "魏"),
    SIX(6, "韩");

    private Integer retCode;
    private String retMessage;

    //Enum自带Setter,只用生产getter
    public Integer getRetCode() {
        return retCode;
    }

    public String getRetMessage() {
        return retMessage;
    }

    //构造器
    CountryEnum(Integer retCode, String retMessage) {
        this.retCode = retCode;
        this.retMessage = retMessage;
    }

    //获取枚举类中的值
    public static CountryEnum forEach_CountryEnum(Integer codeIndex) {
        // 枚举自带的元素数组,可用于遍历
        CountryEnum[] eles = CountryEnum.values();
        for (CountryEnum ele : eles) {
            if (codeIndex == ele.getRetCode()) {
                return ele;
            }
        }
        return null;
    }
}
public class CountDownLatchDemo {
    public static void main(String[] args) throws Exception {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "国,被灭了");
                countDownLatch.countDown();
                // 使用枚举类给线程命名,学习countDownLatch和枚举类减少if判断作用
            }, CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + ":秦国一统天下");
    }
}

执行结果:

齐国,被灭了
魏国,被灭了
赵国,被灭了
楚国,被灭了
燕国,被灭了
韩国,被灭了
main:秦国一统天下

CyclicBarrier

概念:循环屏障,多线程等待,调用方法是await()

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 同步器CyclicBarrier:指定屏障前等待线程数量,  到达屏障后执行的语句
        CyclicBarrier barrier = new CyclicBarrier(7, () -> {
            // 构造器第二个参数:Runnable接口
            System.out.println("龙珠齐,召唤神龙");
        });
        for (int i = 1; i <= 7; i++) {
            final int resources = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "\t 收集到第" + resources + "颗龙珠");
                //满足屏障初始化条件才能执行,否则等待
                try {
                    barrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }

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

执行结果:

1     收集到第1颗龙珠
3     收集到第3颗龙珠
4     收集到第4颗龙珠
2     收集到第2颗龙珠
5     收集到第5颗龙珠
6     收集到第6颗龙珠
7     收集到第7颗龙珠
龙珠齐,召唤神龙

AQS

概念:是1个抽象的队列同步器,通过维护 1个共资源状态( Volatile Int State )和一个先进先出( FIFO )的线程等待队列实现同步

底层原理:维护了一个共享资源的变量state

在这里插入图片描述

在这里插入图片描述

多线程

创建线程的方式

  1. extends Thread
  2. implements Runnable
  3. implements Callable<类型>
  4. 使用线程池创建
public class ThreadDemo1 extends Thread {
    // 1.继承Thread
    // 2.重写run
    @Override
    public void run() {
        System.out.println("threadDemo1  extends thread");
    }

    public static void main(String[] args) {
        // 3.调用start方法
        new ThreadDemo1().start();
    }
}
public class ThreadDemo2 implements Runnable {
    // 1.implements Runnable
    // 2.重写run
    @Override
    public void run() {
        System.out.println("ThreadDemo2 implements Runnable");
    }

    public static void main(String[] args) {
        // 3.调用run方法
        new ThreadDemo2().run();
    }
}
public class ThreadDemo3 implements Callable<String> {
    // 1.3 implements Callable<T>
    // 2.重写call方法
    @Override
    public String call() throws Exception {
        return "ThreadDemo3 implements Callable<String>";
    }

    public static void main(String[] args) {
        try {
            // 3.call()方法执行
            System.out.println(new ThreadDemo3().call());
        } catch (Exception e) {
            e.printStackTrace();

        }
    }
}
public class ThreadDemo3 implements Callable<String> {
    // 1.3 implements Callable<T>
    // 2.重写call方法
    @Override
    public String call() throws Exception {
        return "ThreadDemo3 implements Callable<String>";
    }

    public static void main(String[] args) {
        try {
            // 3.call()方法执行
            System.out.println(new ThreadDemo3().call());
        } catch (Exception e) {
            e.printStackTrace();

        }
    }
}
public class ThreadDemo4 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2, 2,
                1L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5));
        pool.execute(() -> {
            System.out.println("执行业务逻辑");
        });
        pool.shutdown();
    }
}

Callable和Runnable的区别

区别 Callable Runnable
返回值 Callable有返回值,调用Callable接口的线程结束后该返回值 无返回值
重写方法 call() run()
调用形式 Callable 在Thread中没有构造方法支持,所以使用FutureTask作为中间人传入,再作为参数传入Thread Thread中传入参数
class CallableThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("多个线程抢一个futureTask,只有有一个进入。Come in Callable");
        TimeUnit.SECONDS.sleep(2);
        return 100;
    }
}

class RunnableThread implements Runnable {

    @Override
    public void run() {
        System.out.println("Runnable,没有返回值");
    }
}
public class CallableDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1.Callable利用中间人FutureTask先存放资源类
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableThread());
        Thread thread1 = new Thread(futureTask, "线程一");
        Thread thread2 = new Thread(futureTask, "线程二");
        // 2.多个线程强一个futureTask,只运行一个Callable
        thread1.start();
        thread2.start();
        // 3.main线程模拟器其他线程的运算结果
        Integer otherRes = 100;
        // 4.FutureTask使用方法:使得CallableThread执行完再走,防止其余线程阻塞影响效率
        while (!futureTask.isDone()) {

        }
        // 5.获取CallableThread执行结果
        Integer res = futureTask.get();
        System.out.println(Thread.currentThread().getName() + "\t 所有线程的计算结果:" + (otherRes + res));
    }
}

执行结果:

多个线程抢一个futureTask,只有有一个进入。Come in Callable
main     所有线程的计算结果:200

线程状态

线程状态 描述
new 新建。属于一个已经创建的线程,但是还没有调用start方法启动的线程所处的状态。
runnable 就绪。有可能正在运行,或者正在等待CPU资源。总体上就是当我们创建线程并且启动之后,就属于Runnable状态。
blocked 阻塞。当线程准备进入synchronized同步块或同步方法的时候,申请一个监视器锁而进行的等待,会使线程进入BLOCKED状态
waiting/timed_waiting 等待。调用了Object.wait()或者Thread.join()或者LockSupport.park()。处于该状态下的线程在等待另一个线程 执行一些其余action来将其唤醒。
running 运行。处于运行状态的线程 主要 run 方法 中的逻辑代码
terminated 消亡。比较容易理解,那就是线程执行结束了,run方法执行结束表示线程处于消亡状态了。

在这里插入图片描述

线程的基本方法

6种常见方法

方法名 描述
sleep 线程休眠,是Thread类的静态方法,不会释放锁,使得线程进入TIMED-WAITING状态
wait 线程等待,是Object类的非静态方***释放占有的锁,使得线程进入WAITING状态,所以通常用于同步代码块
interrupt 线程中断,影响该线程内部的一个中断标识位,但并不会因为调用了 interrupt 方法而改变线程状态
yield 线程让步,使当前线程让出(释放) CPU 时间片, 与其他线程重新竞争
join 线程加入,等待其他线程终止,当前线程调用子或另一个线程join方法,当前线程阻塞等待join线程执行完毕
notify 线程唤醒,是Object类的非静态方法,唤醒等待的一个线程,如果全部线程都在等待,则随机唤醒

在这里插入图片描述

sleep和wait区别

区别 sleep wait
父亲不同 Thread类 Object类
含义不同 必须指定等待时间,结束后,恢复运行状态 可以不指定等待时间
是否释放锁不同 不会释放对象锁 会释放锁,notify唤醒后,才会重新获取对象锁

start和run区别

区别 start run
含义不同 启动线程,无需等待run方法执行完毕就可继续执行下面代码 指定运行线程的run方法,执行完run方法后,线程终止
状态不同 thread.start()线程处于就绪状态,没有运行 thread.run()线程处于运行状态

终止线程方式

  1. 正常运行结束

  2. 使用退出标志位退出线程

    public class StopThread1 extends Thread {
        // 1.volatile修饰的标志位
        private volatile boolean exit = false;
    
        @Override
        public void run() {
            // 2.while判断是否跳出
            while (!exit) {
                // 执行业务逻辑代码
                System.out.println("执行业务逻辑,使得exit=true");
            }
        }
    }
  3. 使用interrpter终止线程

    • 在调用sleep等方法后,抛出异常后,配合break跳出循环,才能结束run方法
    public class StopThread2 extends Thread {
        @Override
        public void run() {
            // 1.未阻塞判断中断标志位跳出
            while (!isInterrupted()) {
                try {
                    // 2.线程处于阻塞状态,调用interrupter方法后会抛出异常
                    Thread.sleep(5 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 3.抛出异常后,break才跳出循环,才能结束run方法
                    break;
                }
            }
        }
    }
  4. 使用stop方法终止线程

    • 直接调用Thread.stop()是很危险的,极度不安全

线程死锁

两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

死锁条件

死锁条件 概述 破坏死锁
互斥条件 该资源任意一个时刻只由一个线程占用 无法破坏,因为使用锁的本意就是想让它们互斥的(临界资源需要互斥访问)
请求和保持 一个线程 / 进程因请求资源而阻塞时,对已获得的资源保持不放 一次性申请所有的资源
不剥夺 线程 / 进程已获得的资源在末使用完之前不能被其他线程 / 进程强行剥夺,只有自己使用完毕后才释放资源 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源
循环等待 若干线程 / 进程之间形成一种头尾相接的循环等待资源关系 按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件(最常用)

在这里插入图片描述

手写死锁

写法1:

public class DeadLockDemo1 {
    // 资源1,让线程1获得
    private static Object resource1 = new Object();
    // 资源2,让线程2获得
    private static Object resource2 = new Object();

    public static void main(String[] args) {
        // 线程1
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread().getName() + ":get resource1");
                // 线程1等待1s,让线程2获得资源2
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread().getName() + ":get resource2");
                }
            }
        }, "线程 1").start();
        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread().getName() + ":get resource2");
                // 线程2等待1s,让线程1获得资源1
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":waiting get resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread().getName() + ":get resource1");
                }
            }
        }, "线程 2").start();
    }
}

执行结果:

线程 1:get resource1
线程 2:get resource2
线程 1:waiting get resource2
线程 2:waiting get resource1

如何避免上述的死锁:让线程2获得资源的锁的顺序和线程1一样,因为破坏了相互等待的条件,所以避免了死锁

写法2:

public class DeadThread implements Runnable{
    String lockA;
    String lockB;

    public DeadThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA) {
            System.out.println(Thread.currentThread().getName() + "\t 自己持有" + lockA + ",想持有" + lockB);
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName() + "\t 自己持有" + lockB + ",想持有" + lockA);
            }
        }
    }
}
public class DeadLockDemo2 {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";

        new Thread(new DeadThread(lockA, lockB), "A").start();
        //lockA和lockB互换
        new Thread(new DeadThread(lockB, lockA), "B").start();

    }
}
A     自己持有lockA,想持有lockB
B     自己持有lockB,想持有lockA
...

排查死锁

windows系统 解释
jps -l 输出当前包下进程包,对应的Java程序完全的包名,应用主类名下
jstack 死锁进程名 查看指定进程名的在JVM栈中的状态

在这里插入图片描述

在这里插入图片描述

阻塞队列

阻塞队列 解释
概念 队列空,线程再去拿就阻塞;队列满,线程再添加就阻塞
好处 出现阻塞队列后,程序员不用手动去阻塞队列和唤醒队列
阻塞队列分类 解释
ArrayBlockingQueue 数组结构组成的有界阻塞队列
LinkedBlockingQueue 由链表组成的有界阻塞队列(大小默认是21亿,不推荐默认使用)
LinkedBlockingDeque 由链表结构组成的双向阻塞队列
PriorityBlockingQueue 支持优先级排序的无界阻塞队列
DelayBlockingQueue 使用优先级队列实现的延迟无界队列
SynchronousQueue 不存储元素的阻塞队列=单个元素的队列
LinkedTransferQueue 由链表结构组成的无界阻塞队列
核心API 抛出异常 返回布尔 阻塞 超时
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)
检查 element() peek() 不可用 不可用

ArrayBlockingQueue

public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
        // 抛出异常组:add/remove/element:会抛出异常:IllegalStateException,一言不合就报异常不推荐使用
        blockingQueue.add("1");
        blockingQueue.add("2");
        blockingQueue.add("3");
        // blockingQueue.add("4"); 直接抛出异常
        blockingQueue.remove("1");
        blockingQueue.remove("2");
        blockingQueue.remove("3");
        System.out.println("--抛出异常组--");

        // 返回布尔型组:offer/poll/peek:失败返回布尔型
        blockingQueue.offer("11");
        blockingQueue.offer("12");
        blockingQueue.offer("13");
        System.out.println(blockingQueue.offer("14"));
        blockingQueue.poll();
        blockingQueue.poll();
        blockingQueue.poll();
        System.out.println("--返回布尔组--");

        // 等待组:put/take:满了就一直等待,等待是为了只要有数据出去立马添加
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
//        blockingQueue.put("d"); 这样会一直等待
//        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println("--等待组--");

        // 设置时间组:offer/poll设置失败等待时间
        System.out.println(blockingQueue.offer("a", 1L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("b", 1L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("c", 1L, TimeUnit.SECONDS));
        System.out.println("--设置时间组--");
    }
}

SynchronousQueue

public class SynchronsBlockingQueue {
    public static void main(String[] args) {
        // SynchronousQueue:一个线程生产一个,等待别的线程消费完才能进行下去
        SynchronousQueue<Integer> blockingQueue = new SynchronousQueue();

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + ",生产了1");
                blockingQueue.put(1);
                TimeUnit.SECONDS.sleep(1);
                //阻塞:SynchronousQueue使用put必须等待别的线程take后
                System.out.println(Thread.currentThread().getName() + ",生产了2");
                blockingQueue.put(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "线程1").start();

        new Thread(() -> {
            try {
                //消费::SynchronousQueue使用put必须等待别的线程take消费
                System.out.println(Thread.currentThread().getName() + ",消费了" + blockingQueue.take());
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + ",消费了" + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "线程2").start();
    }
}

执行结果:

线程1,生产了1
线程2,消费了1
线程1,生产了2
线程2,消费了2

Synchronized和ReenTrantLock区别

相同点:

  • 都是可重入锁,默认都是非公平锁
  • 都保证了可见性和互斥性
不同点 syn lock
含义 关键字,隐式获取锁和释放锁 Api层面,显示获锁和释放锁
使用 不需要手动释放 需要手动释放,否则死锁
中断 不可中断,除非抛出异常或者正常运行完毕 可响应式中断,try/trylock(time)/lockInterruptibly
公平 只能是非公平锁 默认非公平,传参为true表示公平
底层 同步阻塞,悲观策略 同步非阻塞,乐观并发策略

练习题:(Lock能精确唤醒) AA打印5次,BB打印10次,CC打印15次,紧接着AA打印5次,...重复10论

class MyRenLockResources {
    // 1=AA,2=BB,3=CC
    private int threadName = 1;
    private Lock lock = new ReentrantLock();
    private Condition a = lock.newCondition();
    private Condition b = lock.newCondition();
    private Condition c = lock.newCondition();


    public void print5() {
        lock.lock();
        try {
            //1 判断 防止虚假唤醒,使用while
            while (threadName != 1) {
                //不等于1 就不是AA干活,AA等待
                a.await();
            }
            //2 干活 模拟打印5次
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //3 通知别的线程
            threadName = 2;
            b.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print10() {
        lock.lock();
        try {
            //1 判断 防止虚假唤醒,使用while
            while (threadName != 2) {
                //不等于1 就不是AA干活,AA等待
                b.await();
            }
            //2 干活 模拟打印5次
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //3 通知别的线程
            threadName = 3;
            c.signal();

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

    public void print15() {
        lock.lock();
        try {
            //1 判断 防止虚假唤醒,使用while
            while (threadName != 3) {
                //不等于1 就不是AA干活,AA等待
                c.await();
            }
            //2 干活 模拟打印5次
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //3 通知别的线程
            threadName = 1;
            a.signal();

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

}
public class SyncAndReentrantLockDemo {
    public static void main(String[] args) {
        MyRenLockResources myResources = new MyRenLockResources();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                myResources.print5();
            }
        }, "AA").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                myResources.print10();
            }
        }, "BB").start();

        new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                myResources.print15();
            }
        }, "CC").start();
    }
}

线程池参数

使用ThreadPoolExecutor自建线程池(推荐使用)/Executors工具类(不推荐使用)

// 线程池7大参数
public ThreadPoolExecutor(
    // 核心线程数
    int corePoolSize,
    // 最大线程数
    int maximumPoolSize,
    // 空闲线程存活时间
    long keepAliveTime,
    // 线程存活时间单位
    TimeUnit unit,
    // 阻塞队列
    BlockingQueue<Runnable> workQueue,
    // 线程工厂
    ThreadFactory threadFactory,
    // 饱和拒绝策略
    RejectedExecutionHandler handler
)
线程池参数 解释
corePoolSize 线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。
maximumPoolSize 线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize;
keepAliveTime 线程池维护线程所允许的空闲时间。当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
unit keepAliveTime的单位
workQueue 用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列
threadFactory 它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。
handler 线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略
workQueue阻塞队列 解释
ArrayBlockingQueue 基于数组结构的有界阻塞队列,按FIFO排序任务
LinkedBlockingQuene 基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene
SynchronousQuene 一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene
PriorityBlockingQuene 具有优先级的无界阻塞队列
handler拒绝策略 解释
AbortPolicy 直接抛出异常,默认策略
CallerRunsPolicy 用调用者所在的线程(如以下的main线程)来执行任务
DiscardOldestPolicy 丢弃阻塞队列中靠最前的任务,并执行当前任务
DiscardPolicy 直接丢弃任务

线程池核心组件

在这里插入图片描述

创建线程池

方式一:Executors

ExecutorService,其中定义了线程池的具体行为,常见的四种方法如上图红线处

public class ExecutorsDemo {
    public static void main(String[] args) {
        // 创建线程池方式1:Executors,里面封装了很多,但都不推荐使用
        // newFixedThreadPool:核心和最大线程数相同,等待队列是LinkedBlockingQueue,等待时间0
        ExecutorService fixedPool = Executors.newFixedThreadPool(5);
        // newCachedThreadPool:核心为0,等待队列是SynchronousQueue,最大线程数是最大整数,等待时间60s
        ExecutorService cachedPool = Executors.newCachedThreadPool();
        // newSingleThreadExecutor:核心和最大为0,等待队列是LinkedBlockingQueue,等待时间0
        ExecutorService singletonPool = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 10; i++) {
            fixedPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + ",办理业务");
            });
        }
        fixedPool.shutdown();
    }
}

5种常用的线程池,但是阿里巴巴开发手册都不推荐使用

在这里插入图片描述

在这里插入图片描述

方式二:new ThreadPoolExecutor

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 1 推荐使用使用自建的线程池,学习7大参数
        ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
                2, 5, 1L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                // AbortPolicy:直接抛出异常
                // CallerRunsPolicy:用调用者所在的线程(本类中的main)来执行任务
                // DiscardPolicy:丢弃进来的任务
                // DiscardOldestPolicy:丢弃之前的第一个任务
                new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 1; i <= 20; i++) {
            int resource = i;
            // 2.执行线程池中的线程
            myThreadPool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + "\t 线程进入,\t 获得资源: " + resource);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        // 3.终止线程池
        myThreadPool.shutdown();
        // 4.while判断线程池是否终止
        while (!myThreadPool.isTerminated()) {

        }
        System.out.println("所有线程已经终止");
    }
}
pool-1-thread-1     线程进入,     获得资源: 1
pool-1-thread-4     线程进入,     获得资源: 9
pool-1-thread-3     线程进入,     获得资源: 8
main     线程进入,     获得资源: 11
pool-1-thread-2     线程进入,     获得资源: 2
pool-1-thread-5     线程进入,     获得资源: 10
main     线程进入,     获得资源: 12
pool-1-thread-2     线程进入,     获得资源: 3
pool-1-thread-3     线程进入,     获得资源: 4
pool-1-thread-1     线程进入,     获得资源: 5
pool-1-thread-4     线程进入,     获得资源: 7
pool-1-thread-5     线程进入,     获得资源: 6
main     线程进入,     获得资源: 18
pool-1-thread-2     线程进入,     获得资源: 13
pool-1-thread-4     线程进入,     获得资源: 14
pool-1-thread-1     线程进入,     获得资源: 16
pool-1-thread-3     线程进入,     获得资源: 15
pool-1-thread-5     线程进入,     获得资源: 17
pool-1-thread-2     线程进入,     获得资源: 19
pool-1-thread-1     线程进入,     获得资源: 20
所有线程已经终止

线程池执行原理

  1. 创建一个线程池,在还没有任务提交的时候,默认线程池里面是没有线程的。当然,你也可以调用prestartCoreThread方法,来预先创建一个核心线程。
  2. 线程池里还没有线程或者线程池里存活的线程数小于核心线程数corePoolSize时,这时对于一个新提交的任务,线程池会创建一个线程去处理提交的任务。当线程池里面存活的线程数小于等于核心线程数corePoolSize时,线程池里面的线程会一直存活着,就算空闲时间超过了keepAliveTime,线程也不会被销毁,而是一直阻塞在那里一直等待任务队列的任务来执行。
  3. 当线程池里面存活的线程数已经等于corePoolSize了,这是对于一个新提交的任务,会被放进任务队列workQueue排队等待执行。而之前创建的线程并不会被销毁,而是不断的去拿阻塞队列里面的任务,当任务队列为空时,线程会阻塞,直到有任务被放进任务队列,线程拿到任务后继续执行,执行完了过后会继续去拿任务。这也是为什么线程池队列要是用阻塞队列。
  4. 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列也满了,这里假设maximumPoolSize>corePoolSize(如果等于的话,就直接拒绝了),这时如果再来新的任务,线程池就会继续创建新的线程来处理新的任务,直到线程数达到maximumPoolSize,就不会再创建了。这些新创建的线程执行完了当前任务过后,在任务队列里面还有任务的时候也不会销毁,而是去任务队列拿任务出来执行。在当前线程数大于corePoolSize过后,线程执行完当前任务,会有一个判断当前线程是否需要销毁的逻辑:如果能从任务队列中拿到任务,那么继续执行,如果拿任务时阻塞(说明队列中没有任务),那超过keepAliveTime时间就直接返回null并且销毁当前线程,直到线程池里面的线程数等于corePoolSize之后才不会进行线程销毁。
  5. 如果当前的线程数达到了maximumPoolSize,并且任务队列也满了,这种情况下还有新的任务过来,那就直接采用拒绝的处理器进行处理。默认的处理器逻辑是抛出一个RejectedExecutionException异常。你也就可以指定其他的处理器,或者自定义一个拒绝处理器来实现拒绝逻辑的处理(比如讲这些任务存储起来)。JDK提供了四种拒绝策略处理类:AbortPolicy(抛出一个异常,默认的),DiscardPolicy(直接丢弃任务),DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池),CallerRunsPolicy(交给线程池调用所在的线程进行处理)。

在这里插入图片描述

ThreadLocal

ThreadLocal 解释
概念 提供每个线程存储自身专属的局部变量值
实现原理 调用 ThreadLocal 的 set () 方法时,实际上就是往 ThreadLocalMap 设置值,key 是 ThreadLocal 对象,值是传递进来的对象;调用 ThreadLocal 的 get () 方法时,实际上就是往 ThreadLocalMap 获取值,key 是 ThreadLocal 对象 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。因为这个原理,所以 ThreadLocal 能够实现 “数据隔离”,获取当前线程的局部变量值,不受其他线程影响。
风险 ThreadLocal 被 ThreadLocalMap 中的 entry 的 key 弱引用,如果 ThreadLocal 没有被强引用, 那么 GC 时 Entry 的 key 就会被回收,但是对应的 value 却不会回收。就会造成内存泄漏
解决办法 每次使用完 ThreadLocal,都调用它的 remove () 方法,清除数据
全部评论

相关推荐

点赞 评论 收藏
分享
10-28 11:04
已编辑
美团_后端实习生(实习员工)
一个2人:我说几个点吧,你的实习经历写的让人觉得毫无含金量,你没有挖掘你需求里的 亮点, 让人觉得你不仅打杂还摆烂。然后你的简历太长了🤣你这个实习经历看完,估计没几个人愿意接着看下去, sdk, 索引这种东西单拎出来说太顶真了兄弟,好好优化下简历吧
点赞 评论 收藏
分享
点赞 1 评论
分享
牛客网
牛客企业服务