学习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时,则当前锁空闲,可以占用;反之,当前线程进入等待状态
作用范围:
- 作用成员变量和非静态方法,锁的是对象的实例=this对象
- 作用静态方法,锁的是Class实例,因为静态方法需要Class而不属于对象
- 作用代码块,锁的是所有代码块中的对象
Reentrant
概念:继承里Lock接口,是一个可重入独占锁,默认是非公平锁。
底层原理:通过AQS来实现锁的获取和释放
如何避免死锁:
- 晌应中断
- 可轮询锁
- 定时锁
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
多线程
创建线程的方式
- extends Thread
- implements Runnable
- implements Callable<类型>
- 使用线程池创建
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()线程处于运行状态 |
终止线程方式
正常运行结束
使用退出标志位退出线程
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"); } } }
使用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; } } } }
使用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 所有线程已经终止
线程池执行原理
- 创建一个线程池,在还没有任务提交的时候,默认线程池里面是没有线程的。当然,你也可以调用prestartCoreThread方法,来预先创建一个核心线程。
- 线程池里还没有线程或者线程池里存活的线程数小于核心线程数corePoolSize时,这时对于一个新提交的任务,线程池会创建一个线程去处理提交的任务。当线程池里面存活的线程数小于等于核心线程数corePoolSize时,线程池里面的线程会一直存活着,就算空闲时间超过了keepAliveTime,线程也不会被销毁,而是一直阻塞在那里一直等待任务队列的任务来执行。
- 当线程池里面存活的线程数已经等于corePoolSize了,这是对于一个新提交的任务,会被放进任务队列workQueue排队等待执行。而之前创建的线程并不会被销毁,而是不断的去拿阻塞队列里面的任务,当任务队列为空时,线程会阻塞,直到有任务被放进任务队列,线程拿到任务后继续执行,执行完了过后会继续去拿任务。这也是为什么线程池队列要是用阻塞队列。
- 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列也满了,这里假设maximumPoolSize>corePoolSize(如果等于的话,就直接拒绝了),这时如果再来新的任务,线程池就会继续创建新的线程来处理新的任务,直到线程数达到maximumPoolSize,就不会再创建了。这些新创建的线程执行完了当前任务过后,在任务队列里面还有任务的时候也不会销毁,而是去任务队列拿任务出来执行。在当前线程数大于corePoolSize过后,线程执行完当前任务,会有一个判断当前线程是否需要销毁的逻辑:如果能从任务队列中拿到任务,那么继续执行,如果拿任务时阻塞(说明队列中没有任务),那超过keepAliveTime时间就直接返回null并且销毁当前线程,直到线程池里面的线程数等于corePoolSize之后才不会进行线程销毁。
- 如果当前的线程数达到了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 () 方法,清除数据 |