Java 并发编程
请谈谈你对 volatile的理解
volatile是java虚拟机提供的轻量级的同步机制
-
保证可见性
-
禁止指令重排
-
不保证原子性
JMM(JAVA 内存模型) 请谈谈对JMM的理解
基本概念
-
JMM是一种抽象的概念,并不真实存在,它描述的是一种规范或规则,通过这组规范定义了程序中的访问方式。
-
JMM 同步规定
-
线程解锁前,必须把共享变量的值刷回到主内存
-
线程加锁前,必须把主内存的最新值拷贝到自己的工作内存中
-
加锁和解锁是同一把锁
-
-
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存,工作内存是每个线程的私有区域,java内存模型规定所有变量都存储在主内存中,主内存是共享区域,所有线程都可以访问,但线程对变量的操作(读写改等),都必须在自己的工作内存中进行。
-
首先将变量从主内存中拷贝到自己的工作内存然后对变量进行操作,操作完成后将变量写回到主内存中,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量拷贝副本,因为工作内存是每个线程的私有区域,因此,不同的线程间无法访问对方的工作内存,线程间的通信(传值),必须通过主内存来完成。
-
内存模型图
-
三大特性
-
可见性
package com.haha; import java.util.concurrent.TimeUnit; class Data { //int a = 0; /* * Thread-0 hello... * Thread-0 ++后。。。 * 不加volatile的结果,线程一直阻塞。 */ volatile int a = 0; void addOne() { this.a += 1; } } public class VolatileDemo { public static void main(String[] args) { Data data = new Data(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t hello..."); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } data.addOne(); System.out.println(Thread.currentThread().getName() + "\t ++后。。。"); }).start(); while (data.a == 0) { // looping } System.out.println(Thread.currentThread().getName() + " job is done..."); } } /* * 加volatile的结果: * Thread-0 hello... * Thread-0 ++后。。。 * main job is done... */
如果不加volatile关键字,则主线程会进入死循环,加valatile则主线程可以结束,说明加了volatile关键字的变量,当有一个线程修改了值,会马上被另一个线程感知到,当前值作废,重新从主内存中获取值。对其他线程可见,这就是可见性。
-
原子性
package com.haha;
class Data1 { volatile int a = 0;
void addOne() { this.a += 1; }
}
public class VolatileDemo2 {
public static void main(String[] args) { test(); } private static void test() { Data1 data = new Data1(); for (int i = 0; i < 20; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { data.addOne(); } }).start(); } // 默认有main线程和gc线程 while (Thread.activeCount() > 2) { Thread.yield(); } System.out.println(data.a); }
}
```
package com.haha; import java.util.concurrent.atomic.AtomicInteger; //解决volatile原子性问题 // AtomicInteger 原子类操作 JUC class Data1 { volatile int a = 0; AtomicInteger atomicInteger = new AtomicInteger(); void addAtomic() { atomicInteger.getAndIncrement(); } void addPlus() { a++; } void addOne() { this.a += 1; } } public class VolatileDemo2 { public static void main(String[] args) { test(); } private static void test() { Data1 data = new Data1(); for (int i = 0; i < 20; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { data.addOne(); data.addAtomic(); } }).start(); } // 默认有main线程和gc线程 while (Thread.activeCount() > 2) { Thread.yield(); } System.out.println(data.a); System.out.println(data.atomicInteger); } }
期望结果是20000,但结果达不到20000;
-
有序性
-
计算机在执行程序时,为了提高性能,编译器处理器常常会对指令做重排,一般分为以下3中
-
编译器优化重排
-
指令并行重排
-
内存系统的重排
-
-
单线程环境里确保程序最终执行的结果和代码执行结果一致
-
处理器在进行重排序时必须考虑指令之间的数据依赖性
-
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证用的变量能否一致性是无法确定的,结果无法预测。
-
代码Demo:
package com.haha;
public class ReSortSeqDemo { int a = 0; boolean flag = false;
public void method01() { a = 1; // flag =true; flag = true; } public void method02() { if (flag) { a = a + 3; System.out.println("a = " + a); } }
} //解决办法:用volatile来修饰变量,实现禁止指令重排,从而避免多线程环境下程序出现乱序执行的现象
-
禁止指令重排
volatile实现禁止指令重排序的优化,从而避免了多线程环境中程序出现乱序的现象
先了解一个概念,内存屏障 (Memory Barrier) 又称内存栅栏,是一个CPU指令,它的作用有两个:
-
保证特定操作的执行顺序
-
保证某些变量的内存可见性(利用该特性实现 volatile的内存可见性)
由于编译器和处理器都能执行指令重排优化,如果指令间插入一条Memory Barrier 则会告诉编译器和CPU,不管什么指令都不能使这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后执行重排序优化。内存屏障另一个作用是强制刷出各种CPU缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。
下面是保守策略下,volatile写插入内存屏障后生成的指令序列示意图:
下面是保守策略下,volatile读插入内存屏障后生成的指令序列示意图
线程安全性保证
-
工作内存与主内存同步延迟现象导致可见性问题
-
可以使用syncronzied或volatile关键字解决,它们可以使用一个线程修改后的变量立即对其他线程可见
-
-
对于指令重排导致可见性问题和有序性问题
-
可以利用volatile关键字解决,因为volatile的另一个作用就是禁止指令重排(有序性)优化
-
你在哪些地方用到过 volatile
单例
-
多线程环境下可能存在的安全问题
package com.haha; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SingletonDemo01 { private static SingletonDemo01 instance = null; private SingletonDemo01() { System.out.println(Thread.currentThread().getName() + "\t 构造方法"); } // +synchronized public static SingletonDemo01 getInstance() { if (instance == null) { instance = new SingletonDemo01(); } return instance; } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.execute(() -> SingletonDemo01.getInstance()); } executorService.shutdown(); } } /* 结果: pool-1-thread-2 构造方法 pool-1-thread-8 构造方法 pool-1-thread-9 构造方法 pool-1-thread-6 构造方法 pool-1-thread-4 构造方法 pool-1-thread-3 构造方法 pool-1-thread-5 构造方法 pool-1-thread-1 构造方法 */
单例模式下,构造器中的内容会多次输出
-
双重锁单例
package com.haha; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SingletonDemo2 { private static SingletonDemo2 instance = null; private SingletonDemo2() { System.out.println(Thread.currentThread().getName() + "\t 构造方法"); } // +synchronized代码块 前面+判断 = 双端检锁机制 public static SingletonDemo2 getInstance() { if (instance == null) { synchronized (SingletonDemo2.class) { if (instance == null) { instance = new SingletonDemo2(); } } } return instance; } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.execute(() -> SingletonDemo2.getInstance()); } executorService.shutdown(); } } // pool-1-thread-1 构造方法
-
-
如果没有加volatile 就不一定是线程安全的,原因是指令重排的存在,加入volatile可以禁止指令重排。
-
原因是在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能还没有完成初始化。
instance=new SingletonDemo2() 可以分三步完成: memory = allocate(); // 分配对象空间 instance(memory); // 初始化对象 instance = memory; //设置instance指向刚分配的内存地址,此时instance!=null
-
步骤2 和步骤3 不存在依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种优化时允许的。
-
发生重排
memory = allocate(); // 1.分配对象空间 instance = memory; // 3.设置instance指向刚分配的内存地址,此时instance != null,但对象还没有初始化完成 instance(memory); // 2.初始化对象
-
所以不加 volatile 返回的实例不为空,但可能是未初始化的实例
package com.haha; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SingletonDemo3 { // 禁止指令重排 private static volatile SingletonDemo3 instance = null; private SingletonDemo3() { System.out.println(Thread.currentThread().getName() + "\t 构造方法"); } // +synchronized代码块 前面+判断 = 双端检锁机制 + 禁止指令重排 public static SingletonDemo3 getInstance() { if (instance == null) { synchronized (SingletonDemo3.class) { if (instance == null) { instance = new SingletonDemo3(); } } } return instance; } public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { executorService.execute(() -> SingletonDemo3.getInstance()); } executorService.shutdown(); } } // pool-1-thread-1 构造方法
-
CAS 你知道么?
compartAndSet
package com.haha; import java.util.concurrent.atomic.AtomicInteger; public class CASDemo { public static void main(String[] args) { // 获取真实值,并替换为相应的值 AtomicInteger atomicInteger = new AtomicInteger(1020); boolean b1 = atomicInteger.compareAndSet(1020, 1024); // 期望值,替换值 System.out.println(b1);// ture boolean b2 = atomicInteger.compareAndSet(1020, 1024); System.out.println(b2);// false atomicInteger.getAndIncrement(); } }
CAS 底层原理? 谈谈 UnSafe 的理解?
getAndIncrement()'
public final boolean compareAndSet(int expect,int update){ return unsafe.compareAndSwapInt(this,valueOffset,expect,update) } public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
引出一个问题:UnSafe 类是什么?
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { //获取下面 value 的地址偏移量 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; //..... }
-
UnSafe 是CAS的核心类,由于Java方法无法直接访问底层系统,而需要通过本地方法(native)方法来访问,UnSafe类相当于一个后门,基于该类可以直接操作特定的内存数据。UnSafe类存在于 sun.misc包中(rt.jar),其内部方法操作可以向C指针一样直接操作内存,因为Java中CAS操作执行依赖于UnSafe类。
-
变量ValueOffset,表示该变量值在内存中的偏移量,因为UnSafe就是根据内存偏移量来获取数据的。
-
变量value 用volatile来修饰,保证了多线程的内存可见性。
CAS 是什么?
-
CAS 的全称Compare-And-Swap,它是一条CPU 并发源语。
-
它的功能是判断内存某一位置的值是否为预期值,如果是则更改这个值,这个过程是原子的。
-
CAS 并发原体现在JAVA语言中就是 sun.misc.Unsafe 类中的各个方法。调用 UnSafe类中的CAS 方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖硬件的功能,通过它实现了原子操作。由于CAS是一种系统源语,源语属于操作系统用于范畴,是由若干条指令组成,用于完成某一个功能的过程,并且源语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条原子指令,不会造成所谓的数据不一致的问题。
-
分析一下 getAndAddInt( )这个方法
-
CAS(CompareAndSwap)
-
比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存的值一致为止。
-
-
CAS 应用
-
CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B,当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做,再次循环,直到成功为止。
-
// unsafe.getAndAddInt public final int getAndAddInt(Object obj, long valueOffset, long expected, int val) { int temp; do { temp = this.getIntVolatile(obj, valueOffset); // 获取快照值 } while (!this.compareAndSwap(obj, valueOffset, temp, temp + val)); // 如果此时 temp 没有被修改,就能退出循环,否则重新获取 return temp; }
CAS 的缺点?
-
循环时间长开销大
-
如果CAS失败,就会一直尝试,如果CAS长时间一直不成功,可能给CPU带来很大的开销(比如线程数很多,每次比较都是失败,就会一直循环),所以希望是线程数比较小的场景。
-
-
只能保证一个共享变量的原子操作
-
对于多个共享变量操作时,循环CAS就无法保证原子性。
-
-
引出 ABA 问题
原子类 AtomicInteger 的 ABA问题谈一谈?原子更新引用知道么?
-
原子引用
package com.haha; import java.util.concurrent.atomic.AtomicReference; class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "User [name=" + name + ", age=" + age + "]"; } } public class AtomicReferenceDemo { public static void main(String[] args) { User hi = new User("hi", 23); User hello = new User("hello", 18); AtomicReference<User> atomicReference = new AtomicReference<>(); atomicReference.set(hi); System.out.println(atomicReference.compareAndSet(hi, hello));//true System.out.println(atomicReference.get());//User [name=hello, age=18] } }
-
ABA问题是怎么产生的
package com.haha; import java.util.concurrent.atomic.AtomicReference; public class ABADemo { private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); public static void main(String[] args) { new Thread(() -> { atomicReference.compareAndSet(100, 101); atomicReference.compareAndSet(101, 100); }).start(); new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } atomicReference.compareAndSet(100, 1020); System.out.println(atomicReference.get()); }).start(); } }
当有一个值从A改为B 又改为A,这就是ABA问题。(首位一致,中间数据有变化)
-
时间戳原子引用 (解决ABA方案)
package com.haha; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; import org.apache.derby.tools.sysinfo; public class ABADemo2 { private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100, 1); public static void main(String[] args) { new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + "\t 的版本号是:" + stamp); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); }).start(); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + "\t 的版本号是:" + stamp); try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } boolean r = atomicStampedReference.compareAndSet(100, 1020, stamp, stamp + 1); System.out.println(r); System.out.println(atomicStampedReference.getReference()); }).start(); } }
我们先保证两个线程的初始版本为一致,后面修改是由于版本不一样就会修改失败。
我们知道 ArrayList 是线程不安全,请编写一个不安全的案例并给出解决方案?
-
故障现象
package com.haha; import java.util.ArrayList; import java.util.List; import java.util.Random; public class ContainerDemo { public static void main(String[] args) { //CopyOnWriteArrayList<>() List<Integer> list = new ArrayList<>(); Random random = new Random(); for (int i = 0; i < 100; i++) { new Thread(() -> { list.add(random.nextInt(10)); System.out.println(list); }).start(); } } } //java.util.ConcurrentModificationException
异常:java.util.ConcurrentModificationException
-
导致原因
-
并发修改导致的异常
-
-
解决方案
-
new Vector()
-
Collections.synchronizedList(new ArrayList<>());
-
new CopyOnWriteArrayList<>();
-
-
优化建议
-
在读多写少的时候推荐使用CopyOnWriteArrayList这个类
-
package com.haha; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; public class ContainerNotSafeDemo { public static void main(String[] args) { // List<String> list =Arrays.asList("a","b","c"); // list.forEach(System.out::println); // List<String> list = new ArrayList<>();// new ArrayList的初始值是ten. // list.add("a"); // list.add("b"); // list.add("c"); // for (String element : list) { // System.out.println(element); // } // List<String> list = new ArrayList<>();// new ArrayList的初始值是ten. List<String> list = new CopyOnWriteArrayList<>(); for (int i = 1; i <= 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list); }, String.valueOf(i)).start(); } } }
ArrayList,Set,Map,的并发的写时复制
package com.haha; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import com.sun.jersey.client.impl.CopyOnWriteHashMap; public class ContainerNotSafeDemo { public static void main(String[] args) { // List<String> list =Arrays.asList("a","b","c"); // list.forEach(System.out::println); // List<String> list = new ArrayList<>();// new ArrayList的初始值是ten. // list.add("a"); // list.add("b"); // list.add("c"); // for (String element : list) { // System.out.println(element); // } // List<String> list = new ArrayList<>();// new ArrayList的初始值是ten. // List<String> list = new CopyOnWriteArrayList<>(); // Set<String> list = new CopyOnWriteArraySet<>(); // for (int i = 1; i <= 30; i++) { // new Thread(() -> { // list.add(UUID.randomUUID().toString().substring(0, 8)); // System.out.println(list); // }, String.valueOf(i)).start(); // } // new HashSet<>().add(); //看源码 // Map<String, String> map = new CopyOnWriteHashMap<>();错误! Map<String, String> map = new ConcurrentHashMap<>(); for (int i = 1; i <= 30; i++) { new Thread(() -> { map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8)); System.out.println(map); }).start(); } } }
Java 中锁你知道哪些?请手写一个自旋锁?
公平锁和非公平锁
-
是什么
-
公平锁:是指多个线程按照申请的顺序来获取值
-
非公平锁:是只多个线程获取值的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,可能会造成优先级反转或者饥饿现象
-
-
两者的区别
-
公平锁:在并发环境中,没个线程在获取锁时会先查看此锁维护的的等待队列,如果为空,或者当前线程是等待队列的第一个就占有锁,否则就加入到等待队列,按照FIFO原则获取锁。
-
非公平锁:一上来就尝试占有锁,如果失败再进行排队(类似公平锁那种方式)。
-
-
Java ReentrantLock 而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁,非公平锁的优点在于吞吐量比公平锁大。
-
对Synchronized而言,也是一种非公平锁
package com.haha; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class T1 { volatile int n = 0; public void add() { n++; } public static void main(String[] args) { Lock lock = new ReentrantLock();// notFair 非公平锁 } }
可重入锁和不可重入锁
-
是什么
-
ReentrantLock/Synchronized就是典型的可重入锁
-
可重入锁最大的作用是避免死锁
-
可重入锁:指的是同一个线程外层函数获得锁之后,内层仍然能获取到该锁,在同一个线程在外层方法获取锁时,再进入内层方法会自动获取该锁
-
不可重入锁:所谓不可重入锁,就是若当前线程执行某个方法已经获得了该锁,name在方法中尝试再次获得取锁时,就会被阻塞,获取不到。
-
可重入锁:
-
package com.hi; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Phone implements Runnable { // 线程操作资源类 public synchronized void sendDX() { System.out.println(Thread.currentThread().getName() + "\t 发送短信"); sendEmail(); } public synchronized void sendEmail() { System.out.println(Thread.currentThread().getName() + "\t *****发送邮件"); } // ============================================================================== Lock lock = new ReentrantLock(); @Override public void run() { try { get(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void get() throws Exception { lock.lock(); lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\t get()"); set(); } finally { lock.unlock(); lock.unlock(); } } public void set() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\t*********** set()"); } finally { lock.unlock(); } } } public class ReentrantLockDemo { public static void main(String[] args) throws Exception { Phone phone = new Phone(); new Thread(() -> { phone.sendDX(); }, "t1").start(); new Thread(() -> { phone.sendDX(); }, "t2").start(); Thread.sleep(3000); System.out.println(); System.out.println(); System.out.println(); System.out.println(); Thread t3 = new Thread(phone, "t3"); Thread t4 = new Thread(phone, "t4"); t3.start(); t4.start(); } } /* t1 发送短信 t1 *****发送邮件 t2 发送短信 t2 *****发送邮件 t3 get() t3 *********** set() t4 get() t4 *********** set() */
上面例子可以说明 ReentrantLock 和Synchronized是可重入锁
不可重入锁:
package com.hi; public class Count { NotReentrantLockDemo lock = new NotReentrantLockDemo(); public void print() throws Exception { lock.lock(); doAdd(); } private void doAdd() throws Exception { lock.lock(); lock.unlock(); } public static void main(String[] args) throws Exception { Count count = new Count(); count.print(); } } // 线程一直处于阻塞状态
当前线程执行print()方法首先获取lock , 接下来执行doAdd()方法就无法执行doAdd()中的逻辑,必须先释放锁。
自旋锁
-
是指定尝试获取锁的线程不会立即堵塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU.
-
实现自旋锁
package com.hi; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class SpinLock2 { AtomicReference<Thread> atomicReference = new AtomicReference<>(); public void sLock() { Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + "\t come in O(∩_∩)O哈哈~"); while (!atomicReference.compareAndSet(null, thread)) { } } public void sUnLock() { Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread, null); System.out.println(Thread.currentThread().getName() + "\t invoked myUnLock()"); } public static void main(String[] args) { SpinLock2 spinLock2 = new SpinLock2(); new Thread(() -> { spinLock2.sLock(); try { TimeUnit.SECONDS.sleep(5);、、50000 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } spinLock2.sUnLock(); }, "AAA").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } new Thread(() -> { spinLock2.sLock(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } spinLock2.sUnLock(); }, "BBB").start(); } }
输出结果: AAA come in O(∩_∩)O哈哈~ BBB come in O(∩_∩)O哈哈~ AAA invoked myUnLock() BBB invoked myUnLock()
获取所得时候,如果原子应用为空就获取空锁,不为空表示有人获取了锁,就循环等待。
独占锁(写写锁) / 共享锁 (读锁)
-
是什么?
-
独占锁:指该锁一次只能被一个线程持有
-
共享锁:该锁可以被多个线程持有
-
-
对于ReentrantLock 和synchonized都是独占锁;对于ReentrantReadWriteLock 其读锁是共享锁而写锁是独占锁,读锁的共享可以保证高效的并发读,读写、写读和写写的过程是互斥的。
-
读写锁实例:
实例1:不加读写锁
package com.hi; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class MyCache { private volatile Map<String, Object> map = new HashMap<>(); // private Lock lock =new ReentrantLock(); public void put(String key, Object value) { System.out.println(Thread.currentThread().getName() + "\t正在写入" + key); try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } map.put(key, value); System.out.println(Thread.currentThread().getName() + "***写入完成"); } public void get(String key) { System.out.println(Thread.currentThread().getName() + "\t正在读取"); try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Object result = map.get(key); System.out.println(Thread.currentThread().getName() + "\t读取完成" + result); } } public class ReadWriteLockDemo01 { public static void main(String[] args) { MyCache myCache = new MyCache(); for (int i = 1; i <= 5; i++) { final int ii = i; new Thread(() -> { myCache.put(ii + "", ii + ""); }, String.valueOf(i)).start(); } for (int i = 1; i <= 5; i++) { final int ii = i; new Thread(() -> { myCache.get(ii + ""); }, String.valueOf(i)).start(); } } } /* 2 正在写入2 5 正在写入5 4 正在写入4 1 正在写入1 3 正在写入3 1 正在读取 2 正在读取 3 正在读取 4 正在读取 5 正在读取 2***写入完成 5 读取完成null 5***写入完成 4 读取完成4 3 读取完成3 2 读取完成2 1 读取完成null 1***写入完成 4***写入完成 3***写入完成 * */
实例2:读写锁
package com.hi; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; class MyCache { private volatile Map<String, Object> map = new HashMap<>(); // private Lock lock =new ReentrantLock(); ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); public void put(String key, Object value) { rwLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "\t正在写入" + key); try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } map.put(key, value); System.out.println(Thread.currentThread().getName() + "***写入完成"); } catch (Exception e) { e.printStackTrace(); } finally { rwLock.writeLock().unlock(); } } public void get(String key) { rwLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + "\t正在读取"); try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Object result = map.get(key); System.out.println(Thread.currentThread().getName() + "\t读取完成" + result); } catch (Exception e) { e.printStackTrace(); } finally { rwLock.readLock().unlock(); } } // public void clearMap(){ // map.clear(); // } } public class ReadWriteLockDemo01 { public static void main(String[] args) { MyCache myCache = new MyCache(); for (int i = 1; i <= 5; i++) { final int ii = i; new Thread(() -> { myCache.put(ii + "", ii + ""); }, String.valueOf(i)).start(); } for (int i = 1; i <= 5; i++) { final int ii = i; new Thread(() -> { myCache.get(ii + ""); }, String.valueOf(i)).start(); } } } /* 3 正在写入3 3***写入完成 5 正在写入5 5***写入完成 1 正在写入1 1***写入完成 4 正在写入4 4***写入完成 2 正在写入2 2***写入完成 3 正在读取 1 正在读取 2 正在读取 4 正在读取 5 正在读取 3 读取完成3 1 读取完成1 4 读取完成4 2 读取完成2 5 读取完成5 */
CountDownLatch/CyclicBarrier/Semaphore 使用过么?
CountDownLatch
让一些线程堵塞直到另一个线程完成一系列操作后才被唤醒。CountDownLatch主要有两个方法,当一个或多个线程条用await()方法时,调用线程会被阻塞,其他线程调用countDown方法会将计数减一(调用countDown方法的线程不会阻塞),当计数器值变为零时,因调用await方法被堵塞的线程会被唤醒,继续执行。
假设我们有这样一个场景,教室里有班长和另外6个同学在教室自习,怎么保证班长等同学都走出教室再关门。(做减法)
before
package com.hi; import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { public static void main(String[] args) { //CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 1; i <= 6; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t上完自习,go home "); //countDownLatch.countDown(); }, String.valueOf(i)).start(); } // try { // //countDownLatch.await(); // } catch (InterruptedException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } System.out.println(Thread.currentThread().getName() + "\t班长锁门 "); } } /* 2 上完自习,go home 6 上完自习,go home 5 上完自习,go home 1 上完自习,go home main 班长锁门 4 上完自习,go home 3 上完自习,go home */
after:
package com.hi; import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { public static void main(String[] args) { CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 1; i <= 6; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t上完自习,go home "); countDownLatch.countDown(); }, String.valueOf(i)).start(); } try { countDownLatch.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t班长锁门 "); } } /* 1 上完自习,go home 5 上完自习,go home 6 上完自习,go home 2 上完自习,go home 4 上完自习,go home 3 上完自习,go home main 班长锁门 */
CyclicBarrier
我们假设有这么一个场景,集齐七颗龙珠,召唤神龙。(做加法)
package com.hi; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> { // Thread.currentThread().getName() System.out.println("————————召唤神龙"); }); for (int i = 1; i <= 7; i++) { final int ii = i; new Thread(() -> { System.out.println(Thread.currentThread().getName() + "\t收集到地" + ii + "颗龙珠"); try { cyclicBarrier.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } }, String.valueOf(i)).start(); } } } /* 1 收集到地1颗龙珠 7 收集到地7颗龙珠 3 收集到地3颗龙珠 2 收集到地2颗龙珠 5 收集到地5颗龙珠 6 收集到地6颗龙珠 4 收集到地4颗龙珠 ————————召唤神龙 */
Semaphore
假设我们有3个停车位,6辆车去抢
package com.hi; import java.util.concurrent.Semaphore; public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore = new Semaphore(3); for (int i = 1; i <= 6; i++) { new Thread(() -> { try { semaphore.acquire();// 获取一个许可 System.out.println(Thread.currentThread().getName() + "\t 抢到车位..."); Thread.sleep(3000); System.out.println(Thread.currentThread().getName() + "\t 离开车位..."); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { semaphore.release();//释放一个许可 } }, String.valueOf(i)).start(); } } } /* 3 抢到车位... 2 抢到车位... 1 抢到车位... 3 离开车位... 2 离开车位... 1 离开车位... 4 抢到车位... 5 抢到车位... 6 抢到车位... 4 离开车位... 6 离开车位... 5 离开车位... */
堵塞队列知道么?
阻塞队列有哪些
-
ArrayBlockingQueue: 是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)对元素进行排序。
-
LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按FIFO(先进先出)对元素进行排序,吞吐量通常要高于ArrayBlockingQueue.
-
SynchronousQueue:是一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常高于LinkedBlockingQueue.
什么是阻塞队列 FIFO
-
阻塞队列,首先是一个队列,而一个阻塞队列在数据结构中所起的作用大致FIFO(脑补)
-
当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
-
当阻塞队列是满时,往队列中添加元素的操作将会被阻塞。
-
核心方法
| 方法\行为 | 抛异常 | 特定的值 | 阻塞 | 超时 |
:——-: | :——-: | :—————: | :—-: | :————————-: |
---|---|---|---|---|
插入方法 | add(o) | offer(o) | put(o) | offer(o, timeout, timeunit) |
-------- | ------ | -------- | ------ | --------------------------- |
移除方法 | poll()、remove(o) | take() | poll(timeout, timeunit) | |
-------- | ---- | ----------------- | ------ | ----------------------- |
检查方法 | element() | peek() | ||
-------- | --------- | ------ | ---- | ---- |
-
行为解释:
-
抛异常:如果操作不能马上进行,则抛出异常
-
特定的值:如果操作不能马上进行,将会返回一个特殊的值,一般是true或false
-
阻塞:如果操作不能马上进行,操作会被阻塞
-
超时:如果操作不能马上进行,操作会被阻塞指定的时间,如果指定时间没执行,则返回一个特殊值,一般是true或者false
-
-
插入方法:
-
add(E e): 添加成功返回true,失败抛IllegalStateException 异常
-
offer(E e): 成功返回true,如果此队列已满,则返回false.
-
put(E e): 将元素插入此队列的尾部,如果该队列已满,则一直阻塞
-
-
删除方法:
-
remove(Object o); 移除指定元素,成功 返回true
-
poll(): 获取并移除此队列的头元素,若队列为空,则返回null
-
take():获取并移除此队列头元素,若没有元素则一直阻塞
-
-
检查方法:
-
element(): 获取但不移除此队列的头元素,没有元素则抛异常
-
peek():获取但不移除此队列的头;若队列为空,则返回null
-
SynchronousQueue
SynchronousQueue,实际上它不是一个真正的队列,因为它不会为队列中元素维护存储空间。与其他队列不同的是,它维护一组线程,这些线程在等待着吧元素加入或移除队列。一个put() 等待一个 take();
package com.hi; import java.util.concurrent.SynchronousQueue; public class SynchronousQueueDemo { public static void main(String[] args) { SynchronousQueue<Integer> synchronousQueue = new SynchronousQueue<>(); new Thread(()->{ try { synchronousQueue.put(1); Thread.sleep(3000); synchronousQueue.put(2); Thread.sleep(3000); synchronousQueue.put(3); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }).start(); new Thread(()->{ try { Integer v1 = synchronousQueue.take(); System.out.println(v1); Integer v2 = synchronousQueue.take(); System.out.println(v2); Integer v3 = synchronousQueue.take(); System.out.println(v3); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }).start(); } }
使用场景
-
生成者消费者模式
-
线程池
-
消息中间件
线程通信生产者,消费者
package com.hi; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class ShareData { private int num = 0; private Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void increment() { lock.lock(); try { while (num != 0) { condition.await(); } num++; System.out.println(Thread.currentThread().getName() + "\t" + num); condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrement() { lock.lock(); try { //while 和 if 的区别 ! if 线程虚假唤醒! while (num == 0) { condition.await(); } num--; System.out.println(Thread.currentThread().getName() + "\t" + num); condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } public class ProdConsumer_TraditionDemo { public static void main(String[] args) { ShareData shareData = new ShareData(); for (int i = 1; i <= 5; i++) { new Thread(() -> { shareData.increment(); }, "AAA").start(); } for (int i = 1; i <= 5; i++) { new Thread(() -> { shareData.decrement(); }, "BBB").start(); } for (int i = 1; i <= 5; i++) { new Thread(() -> { shareData.increment(); }, "CCC").start(); } for (int i = 1; i <= 5; i++) { new Thread(() -> { shareData.decrement(); }, "DDD").start(); } } } /*AAA 1 BBB 0 AAA 1 BBB 0 AAA 1 BBB 0 AAA 1 BBB 0 CCC 1 BBB 0 ...*/
Synchronized 和Lock有什么区别?
-
-
原始结构
-
Synchronized 是关键字属于JVM层面,反应在字节码上是Monitorenter和monitorexit,其底层是通过monitor对象来完成的,其实wait/notify等方法也是依赖monitor对象只有在同步方法或同步块中才能调用wait/notify等方法。
-
Lock是具体类(java.until.concurrent.locks.Lock)是 api 层面的锁。
-
-
使用方法
-
synchronzied 不需要用户手动的释放锁,当synchronized代码执行完毕后,系统会自动让线程释放对锁的占用。
-
ReentrantLock 则需要用户手动的释放锁,若没有主动释放锁,可能导致死锁的现象,lock()和unlock()方法需要配合try/finally语句来完成。
-
-
等待是否可中断
-
synchronized 不可中断,除非抛出异常或正常运行完成。
-
ReentrantLock 可中断,设置超市方法 tryLock(long timeout,timeUnut unit), lockInterruptibly()放代码块中,调用interrupt()方法可中断。
-
-
加锁是否公平
-
synchronized 非公平锁
-
ReentrantLock默认是非公平锁,构造方法中可以传入boolean值,true为公平锁,false为非公平锁。
-
-
锁可以绑定多个Condition
-
synchronized 没有Condition
-
ReentrantLock 用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程,要么唤醒全部线程。
-
实例:绑定多个Condition
package com.hi; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class ShareResource { private int num = 1; Lock lock = new ReentrantLock(); Condition c1 = lock.newCondition(); Condition c2 = lock.newCondition(); Condition c3 = lock.newCondition(); Condition c4 = lock.newCondition(); public void print5() { lock.lock(); try { while (num != 1) { try { c1.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } num = 2; c2.signal(); } finally { lock.unlock(); } } public void print10() { lock.lock(); try { while (num != 2) { try { c2.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } num = 3; c3.signal(); } finally { lock.unlock(); } } public void print15() { lock.lock(); try { while (num != 3) { try { c3.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } for (int i = 1; i <= 15; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } num = 1; c1.signal(); } finally { lock.unlock(); } } } public class SyncAndReentrantLockDemo { public static void main(String[] args) { ShareResource shareResource = new ShareResource(); new Thread(() -> { for (int i = 1; i <= 10; i++) { shareResource.print5(); } }, "AAA").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { shareResource.print10(); } }, "BBB").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { shareResource.print15(); } }, "CCC").start(); } }
阻塞队列版生产者和消费者:
package com.hi;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger;
class Mylulu { private volatile boolean FLAG = true; private AtomicInteger atomicInteger = new AtomicInteger(); BlockingQueue<String> blockingQueue = null;
public Mylulu(BlockingQueue<String> blockingQueue) { this.blockingQueue = blockingQueue; System.out.println(blockingQueue.getClass().getName()); }
// System.out.println(Thread.currentThread().getName()+"\t"); public void prod() throws Exception { String data = null; boolean resVal; while (FLAG) { data = atomicInteger.incrementAndGet() + ""; resVal = blockingQueue.offer(data, 2L, TimeUnit.SECONDS); if (resVal) { System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "成功"); } else { System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "失败"); } TimeUnit.SECONDS.sleep(1);
} System.out.println("老板带着小姨子跑路了,停止生产.");
} public void constu() throws Exception { String result = null;
while (FLAG) { result = blockingQueue.poll(2, TimeUnit.SECONDS); if (result == null || result.equalsIgnoreCase("")) { FLAG = false; System.out.println(Thread.currentThread().getName() + "\t超过两秒,消费退出"); System.out.println(); System.out.println(); return;
} // System.out.println(Thread.currentThread().getName()+"\t");
System.out.println("消费" + result + "成功"); }
}
public void stop() { this.FLAG = false; }
}
public class ProdConsumer_BlockingQueueTest {
public static void main(String[] args) throws Exception {
Mylulu mylulu = new Mylulu(new ArrayBlockingQueue<>(10));
new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + "\t生产线程启动"); mylulu.prod(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }, "生产线程").start();
new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + "\t消费线程启动"); mylulu.constu(); System.out.println(); System.out.println(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }, "消费线程").start();
Thread.sleep(5000); System.out.println(); System.out.println(); System.out.println(); System.out.println("5秒钟后,王八蛋老板带着小姨子跑路了,结束。"); mylulu.stop();
}
}
## 线程池用过么?谈谈对ThreadPoolExector的理解? #### 为什么使用线程池,线程池的优势? 线程池用于多线程处理中,它可以根据系统的情况,可以有效地控制线程执行的数量,优化运行效果。线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数,那么超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。 主要特点为: * 线程复用 * 控制最大并发数量 * 管理线程 主要优点为: * 降低资源消耗,通过重复利用已创建的线程来降低线程创建和销毁造成的消耗。 * 提高相应的速度,当任务到达时,任务可以不需要等到线程创建就能立即执行。 * 提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅仅会消耗系统资源,还会降低系统稳定性,使用线程可以进行统一分配,调优和监控。 ### 创建线程的几种方式 * 继承Thread * 实现Runnable接口 run() * 实现Callable call() ```java package com.hi; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; class MyThread1 implements Runnable { @Override public void run() { // TODO Auto-generated method stub } } class MyThread2 implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("********come Callable"); return 1024; } } public class CallableDemo { public static void main(String[] args) throws Exception, Exception { // 有两个线程 main , AAA futureTask FutureTask<Integer> futureTask = new FutureTask<>(new MyThread2()); // FutureTask<Integer> futureTask2 = new FutureTask<>(new MyThread2()); Thread t1 = new Thread(futureTask, "AAA"); new Thread(futureTask, "BBB").start();// futureTask2 t1.start(); int result01 = 100; while (!futureTask.isDone()) {// 类似自旋锁 } int result02 = futureTask.get();// 建议放在最后(futureTask.get()) System.out.println("****result" + (result01 + futureTask.get())); } }
线程池如何使用?
架构说明
编码实现
package com.hi; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MyThreadPoolDemo { // 第四种使用java多线程的方式 线程池 public static void main(String[] args) { // 5个处理线程 // 模拟10个用户来办理业务,每个用户就是一个来自外部的线程 // ExecutorService threadPool = Executors.newFixedThreadPool(5); // //1个处理线程 //ExecutorService threadPool = Executors.newSingleThreadExecutor(); ExecutorService threadPool = Executors.newCachedThreadPool(); // 模拟10个用户来办理业务,每个用户就是一个来自外部的线程 try { for (int i = 1; i <= 10; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t 办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } } }
-
Executors.newSingleThreadExecutor() :只有一个线程的线程池,因此所有提交的任务是顺序执行
-
Executors.newFixedThreadExecutor():拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待
-
Executors.newCacheThreadPool():线程池中有很多线需要同时执行,老的可用线程将被新的任务触发重新执行,如果线程超过60秒内没有被执行,那么将被终止并从池中删除
-
Executors.newScheduledThreadPool():用来调度即将执行的任务的线程池
-
Executors.newWorkStealingPool(): newWorkStealingPool适合使用在很耗时的操作,但是newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展,但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中
ThreadPoolExecutor
ThreadPoolExecutor作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等服务。
ThreadPoolExecutor是以上底层(Executors.newSingleThreadExecutor() ...)的底层实现,5个参数,实现了LinkedBlockingQueue<Runnable>() 阻塞队列和SynchronousQueue<Runnable> 。
线程池的几个重要参数介绍?
7大参数
-
corePoolSize:线程池中的常驻核心线程数
-
maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1
-
keepAliveTime:多余的空闲线程的存活时间。 当前线程池数量超过corePoolSIze时,当空闲时间达到keepAliveTime值时,当多余空闲线程会被销毁直到只剩下corePoolSize个线程为止
-
unit:keepAliveTime的单位( 时间单位)
-
workQueue:任务队列,被提交但尚未被执行的任务。
-
threadFactory: 表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。
-
handler:拒绝策略,表示当队列满了并且工作线程大于线程池的最大线程数(miximumPoolSize)时如何来拒绝请求执行的runnable的策略。
参数 | 作用 |
---|---|
corePoolSize | 核心线程池大小 |
maxmumPoolSize | 最大线程池大小 |
keepAliveTime | 线程池中超过corePoolSize 数目的空闲线程最大存活时间;可以allowCoreThreadTimeOut(true)使得核心线程有效时间 |
TimeUnit | KeepAliveTime时间单位 |
WorkQueue | 阻塞队列任务 |
threadFactory | 新建线程工厂 |
RejectedExecutionHandler | 当提交任务数超过maxmumPoolSize+WorkQueue之和时,任务会交给RejectedExecutionHandler来处理 |
说说线程池的底层工作原理?
重点讲解: 其中比较容易让人误解的是:corePoolSize,maximumPoolSize,workQueue之间关系。
-
当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
-
当线程池达到corePoolSize时,新提交任务将会被放入workQueue中,等待线程池中任务调度执行。
-
当workQueue已满,且maxmumPoolSize大于corePoolSize时,新提交任务将会创建新线程执行任务。
-
当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
-
当线程池中超过corePoolSIze线程,空闲时间达到keepAliveTime时,关闭空闲线程。
-
当设置allowCoreThreadTimeOut(true)时,线程池中 corePoolSize线程空闲时间达到keepAiveTime也将关闭。
#### 以下重要 以下重要 以下重要 以下重要 以下重要
-
在创建了线程池后,等待提交过来的任务请求。
-
当调用了execute()方法添加一个请求任务时,线程池会做如下判断;
-
如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务
-
如果正在运行的线程数量大于corePoolSize,那么会放入任务队列
-
如果这时,任务队列也满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务。
-
如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
-
-
当一个线程完成任务时,它会从队列中取下一个任务来执行
-
当一个线程无事可做超过一定的时间(KeepAliveTime)时,线程池会判断;如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。
线程池用过吗?生产上你如何设置合理参数?
线程池的拒绝策略你谈谈?
-
是什么?
-
等待队列已经满了,再也塞不下新的任务,同时线程池中的线程数达到了最大线程数,无法继续为新任务服务。
-
-
拒绝策略
-
AbortPolicy: 处理程序遭到拒绝将抛出运行时RejectedExecutionException
-
CallerRunsPolicy:线程调用运行该任务的 execute 本事。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
-
DiscardPolicy: 不能执行的任务将被删除
-
DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)
-
你在工作中单一的、固定数的和可变的三种创建线程池的方法,你用哪个多,超级大坑?
如果读者对java中的阻塞队列有所了解的话,看到这里或许就能明白原因了。
java中的BlockingQueue主要有两种实现,分别是ArrayBlockingQueue和LinkedBlockingQueue.
ArrayBlockingQueue是一个用数组实现的有界阻塞队列,必须设置容量。
LinkedBlockingQueue是一个用链表实现的有界阻塞队列,容量可以选择设置,不设置的话,将是一个无边界的阻塞队列,最大长队为Integer.MAX_VALUE.
这里的问题就在于:不设置的话,将是一个无边界阻塞队列,最大长度为Integer.MAX_VALUE.也就是说,如果我们不设置LinkedBlockingQueue容量的话,其默认值为Integer.MAX_VALUE.
而newFixedThreadPool中创建LinkedBlockingQueue时,并未指定容量。此时LinkedBlockingQueue就是一个无边界队列,对于一个无边界队列来说是可以不断的向队列中加入任务的,这种情况下就可能因为任务过多而导致OOM(内存溢出问题)。
上面提到的问题主要体现在newFixedThreadPool和newSingleThreadExecutor两个工厂方法上,并不是说newCachedThreadPool和newScheduleThreadPool这两个方法就安全了,这两种方式创建的最大线程数可能是Integer.MAX_VALUE,而创建这么多线程,必然有可能导致OOM.
你在工作中如何使用线程池的,是否自定义过线程池使用?
自定义线程池
package com.hi; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolExecutorDemo { public static void main(String[] args) { new ThreadPoolExecutor(2, 3, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5), Executors.defaultThreadFactory(), // new ThreadPoolExecutor.DiscardPolicy() new ThreadPoolExecutor.DiscardPolicy());// 拒绝策略 } }
合理配置线程池你是如何考虑的?
-
CPU密集型
-
CPU 密集的意思是该任务需要大量的运算,而且没有阻塞,CPU一直全速运行。
-
CPU密集型任务尽可能的少的线程数量,一般为CPU核数+1个线程的线程池。
-
-
IO密集型
-
由于IO密集型任务线程并不是一直在执行任务,可以多分配一点线程数,如CPU*2.
-
也可以使用公式:CPU核数/(1-阻塞系数);其中阻塞系数在0.8~0.9之间。
-
死锁编码以及定位分析
-
产生死锁的原因
-
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种相互等待的现象,如果无外力的干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
-
-
代码:
package com.hi; import java.util.concurrent.TimeUnit; class HoldLockThread implements Runnable { private String lockA; private String lockB; public HoldLockThread(String lockA, String lockB) { super(); this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "\t 自己持有" + lockA + "\t尝试获得" + lockB); try { TimeUnit.SECONDS.sleep(2); } catch (Exception e) { e.printStackTrace(); } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "\t 自己持有" + lockB + "\t尝试获得" + lockA); } } } } public class DeadLockDemo { public static void main(String[] args) { String lockA = "lockA"; String lockB = "lockB"; new Thread(new HoldLockThread(lockA, lockB), "AAA").start(); new Thread(new HoldLockThread(lockB, lockA), "BBB").start(); } }
-
解决
-
jps-l 命令查定位进程号
-
E:\workspacenew1\ShiXun\src\main\java\com\hi>jps -l 14016 4992 com.hi.DeadLockDemo 6512 sun.tools.jps.Jps E:\workspacenew1\ShiXun\src\main\java\com\hi>
jstack 4992 找到死锁查看
Java stack information for the threads listed above: =================================================== "BBB": at com.hi.HoldLockThread.run(DeadLockDemo.java:26) - waiting to lock <0x000000078309c230> (a java.lang.String) - locked <0x000000078309c268> (a java.lang.String) at java.lang.Thread.run(Thread.java:748) "AAA": at com.hi.HoldLockThread.run(DeadLockDemo.java:26) - waiting to lock <0x000000078309c268> (a java.lang.String) - locked <0x000000078309c230> (a java.lang.String) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock. E:\workspacenew1\ShiXun\src\main\java\com\hi>
最后发现死锁!
参考链接
-
参考学习人(向他学习): http://blog.cuzz.site/2019/05/10/JVM面试/
-
编辑工具 Typora.