目录
三、ArrayList多线程异常:java.util.ConcurrentModificationException (同HashSet、HashMap问题)
四、公平锁、非公平锁、可重入锁(递归锁)、自旋锁、读/写锁(共享锁/独占锁)的理解
五、CountDownLatch、 CyclicBarrier、Semaphore的使用
一、volatile的理解
1.volatile是Java虚拟机提供的轻量级的同步机制
1.1保证可见性
1.2不保证原子性-反JMM:数据加载过快,返回主内存数据覆盖,导致数据丢失
1.3禁止指令重排
2.JMM(Java内存模型)理解
2.1可见性:主内存线程--->分配--->工作内存1、2、3线程...--->返回--->主内存线程
2.2原子性
2.3有序性
3.在哪些地方用到volatile
3.1单例模式DCL(Double Check Lock)代码:DCL双重检查加锁-可能在instance引用对象时,由于指令重排,导致初始化完成前被其他线程引用,造成线程不安全问题。
4.示例:
4.1非volatile 、synchronized解决多线程原子性问题:
JUC:java.util.concurrent.atomic包下提供了原子性方法,如:
AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.getAndIncrement();
4.2volatile单例模式DCL,禁止指令重排
private volatile static Instance ins = null;
public static Instance getInstance(){
if (ins == null){
synchronized (Instance.class){
if (ins == null){
ins = new Instance();
}
}
}
return ins;
}
二、CAS的理解
1.什么是CAS
1.1compareAndSet--->比较并交换,期望相同即交换
2.CAS底层原理
2.1CAS方法
atomicInteger.compareAndSet(expect,update);
2.2调用Unsafe:Unsafe类的方法大部分为native修饰(Java调用非Java代码的接口),其直接调用内存偏移地址获取数据。
unsafe.compareAndSwapInt(this, valueOffset, expect, update);
3.CAS的缺点
3.1由于没有加锁,循环时间长,开销大
3.2只能保证一个共享变量的原子性
3.3会导致ABA问题:
①多线程时间差引起线程1A---(线程2A-B-C-D...-A)---B期间,线程2多次修改数据,导致线程1认为数据未变化。
②适合只管结果不管过程的业务。
4.示例
4.1时间戳原子引用解决ABA问题:AtomicStamped...
public static void main(String[] args) {
String val = "A";
AtomicStampedReference<String> satf = new AtomicStampedReference<String>(val, 1);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "初始版本号:" + satf.getStamp() + " val:" + satf.getReference());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
satf.compareAndSet("A", "B", satf.getStamp(), satf.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "修改后版本号:" + satf.getStamp() + " val:" + satf.getReference());
satf.compareAndSet("B", "A", satf.getStamp(), satf.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "修改后版本号:" + satf.getStamp() + " val:" + satf.getReference());
}, "t1").start();
new Thread(() -> {
int init = satf.getStamp();
System.out.println(Thread.currentThread().getName() + "初始版本号:" + satf.getStamp());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean flg = satf.compareAndSet("A", "B",init, satf.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "等待t1后版本号:" + satf.getStamp() + " cas状态:" + flg + " val值:" + satf.getReference());
}, "t2").start();
}
三、ArrayList多线程异常:java.util.ConcurrentModificationException (同HashSet、HashMap问题)
1.1异常复现(牺牲Vector安全性,提高性能的结果)
List<String> list = new ArrayList<>(); for (int i = 0; i < 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list); }, String.valueOf(i)).start(); }
1.2解决方法
①简单解决:Collections.synchronized...
List<String> list = Collections.synchronizedList(new ArrayList<>());
②提升解决:JUC工具类--->CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap
如add方法,使用ReentrantLock加锁实现
List<String> list = new CopyOnWriteArrayList<>();
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();
}
}
四、公平锁、非公平锁、可重入锁(递归锁)、自旋锁、读/写锁(共享锁/独占锁)的理解
1.公平锁和非公平锁
1.1公平锁:排队占锁。
1.2非公平锁:先抢占锁,抢不到排队占锁。
Synchronized非公平锁,ReentrantLock默认非公平锁,可选择锁方式:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.可重入锁(递归锁)
2.1同一线程进入外层锁后,即不受限制进入内层锁,以此避免死锁的发生。
Synchronized、ReentrantLock为可重入锁。
public synchronized void syc1() {
syc2();
}
public synchronized void syc2() {
}
3.自旋锁
3.1采用循环的方式去尝试获取锁,以此防止出现阻塞,但是耗费CPU资源。
如:atomicInteger.getAndIncrement()--->unsafe.getAndAddInt(this, valueOffset, 1)就是自旋锁;
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
4.读/写锁(共享锁/独占锁)
4.1独占锁:只被一个线程占有;
4.2共享锁:可被多个线程持有;
4.3解决Synchronized、ReentrantLock独占锁的问题:
ReentrantReadWriteLock提供了读写分离的锁机制,多线程操作时,写操作就独占锁,读操作就共享锁。
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
try {
rwLock.writeLock().lock();
//写操作
}finally {
rwLock.writeLock().unlock();
}
try {
rwLock.readLock().lock();
} finally {
//写操作
rwLock.readLock().unlock();
}
五、CountDownLatch、 CyclicBarrier、Semaphore的使用
1.CountDownLatch
1.1定义计数器,让调用await()的线程阻塞,直到计数减为0,再唤醒。
1.2示例
注:如果计数不够,会一直处于等待,耗费资源。
System.out.println("定义countDownLatch计数器");
int cdNum = 6;
CountDownLatch countDownLatch = new CountDownLatch(cdNum);
for (int i = 0; i < cdNum; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "分线程结束,并计数");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
System.out.println(Thread.currentThread().getName() + "等待分线程全部结束后继续主线程");
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "主线程结束");
2.CyclicBarrier
2.1定义计数器,直到计数加为num,才会执行await()后的方法。
2.2示例
System.out.println("定义cyclicBarrier计数器");
int num = 6;
CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
System.out.println("分线程执行完毕,交回主线程");
});
for (int i = 0; i < num; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "分线程执行中");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
3.Semaphore
3.1Semaphore用于多线程抢占多个资源,及并发资源数的控制,Synchronized、ReentrantLock是多线程抢占一个资源,也就是并发数为1。
3.2示例
System.out.println("定义semaphore信号量,进行多线程抢占资源");
int num = 3;
Semaphore semaphore = new Semaphore(num);
for (int i = 0; i < 9; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 线程占用资源中");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 3秒后释放资源");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
六、阻塞队列的理解
队列:先到先得。
栈:先进后出。
1.1阻塞队列,即队列满时能出不能进,队列空时能进不能出。
1.2常用方法:
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
出入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | tkae() | poll(time,unit) |
检查 | element() | peek() | 不可用 | 不可用 |
1.3常用实现类:
SynchronousQueue:不存储元素的阻塞队列,只能put一个,take一个。
ArrayBlockingQueue |
LinkedBlockingQueue |
PriorityBlockingQueue |
SynchronousQueue |
1.4扩展示例:
public static void main(String[] args) throws Exception {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(5));
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t生产线程启动");
try {
myResource.myProd();
} catch (Exception e) {
e.printStackTrace();
}
}, "Prod").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t消费线程启动");
try {
myResource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
}, "Cons").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("时间到,停止活动");
myResource.stop();
}
static class MyResource {
/**
* 默认开启 进行生产消费的交互
*/
private volatile boolean flag = true;
/**
* 默认值是0
*/
private AtomicInteger atomicInteger = new AtomicInteger();
private BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println("接口实现方法\t" + blockingQueue.getClass().getName());
}
public void myProd() throws Exception {
String data = null;
boolean returnValue;
while (flag) {
data = atomicInteger.incrementAndGet() + "";
returnValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (returnValue) {
System.out.println(Thread.currentThread().getName() + "\t 插入队列数据 " + data + " 成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t 插入队列数据 " + data + " 失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "\t 停止 表示 flag" + flag);
}
public void myConsumer() throws Exception {
String result = null;
while (flag) {
result = blockingQueue.poll(2L, TimeUnit.SECONDS);
if (StringUtils.isBlank(result)) {
flag = false;
System.out.println(Thread.currentThread().getName() + "\t 超过2m没有取到 消费退出");
return;
}
System.out.println(Thread.currentThread().getName() + "\t 消费队列 " + result + " 成功");
}
}
public void stop() throws Exception {
flag = false;
}
}
七、Synchronized和Lock的区别
1.Synchronized是关键字,属于JVM层面;Lock是JUC的具体类,是API层面的锁。
2.Synchronized不需要手动释放资源;Lock需要手动释放,用lock和unlock配合使用,否则导致死锁。
3.Synchronized是不能被中断的;Lock是可以中断的。
4.Synchronized是非公平锁;Lock默认非公平锁,设为true即为公平锁。
5.Synchronized随机唤醒锁;Lock可以精准唤醒锁。
Lock lock = new ReentrantLock(); Condition c1 = lock.newCondition(); c1.await(); c1.signal();
八、Callable接口的理解
1.创建线程的四种方式:
①直接继承Thread。
②实现Runnable接口:无返回结果,不抛异常。
③实现Callalbe接口:有返回结果,抛异常。
④线程池Executor。
2.Callable接口可以获取执行结果,并利用阻塞一直等待结果返回。
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> oft = new FutureTask<>(new MyThread());
new Thread(oft, "1").start();
int num1 = 100;
System.out.println(Thread.currentThread().getName() + "\t 获取Callable线程结果,未完成则堵塞等待");
int num2 = oft.get();
System.out.println(Thread.currentThread().getName() + "\t 处理结果: \t" + (num1 + num2));
}
static class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "\t Callable进行处理中!");
TimeUnit.SECONDS.sleep(3);
return 100;
}
}
九、线程池Executor的理解
1.线程池优势:通过线程的复用,管理线程并发数,最大化利用系统资源,提供响应速度。
2.常见实现方法:
Executors.newFixedThreadPool | 创建定长线程池,使用LinkedBlockingQueue阻塞队列 |
Executors.newSingleThreadPool | 创建一个线程的线程池,使用LinkedBlockingQueue阻塞队列 |
Executors.newCachedThreadPool | 创建可调节的线程池,使用SynchronousQueue阻塞队列 |
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "\t 进入业务处理");
});
}
threadPool.shutdown();
}
3.线程池参数说明
--->public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
--->public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue ) { this(corePoolSize,------------------------>核心线程数 maximumPoolSize,--------------------->最大线程数 keepAliveTime, ---------------------->空闲线程存活时间,最大线程数减为核心线程数 unit,-------------------------------->空闲线程存活时间d单位 workQueue,--------------------------->任务队列,被提交但未被执行的任务 Executors.defaultThreadFactory(), --->线程工厂 defaultHandler);}-------------------->拒绝策略,最大线程数和任务队列都满的时候执行拒绝策略
4.拒绝策略说明
AbortPolicy | 默认策略,直接抛出RejectedExecution异常 |
CallerRunsPolicy | 将任务回退到调用者处理,从而降低新任务的流量 |
DiscardOldestPolicy | 将队列中等待最久的任务抛弃,并提交当前任务 |
DiscardPolicy | 直接丢弃当前任务 |
5.生产实现策略
5.1线程池使用ThreadPoolExecutor的方式创建。
5.2最大线程5+阻塞队列5<小于任务15,执行决绝策略。
5.3简单配置:
①CPU密集型任务线程数=CPU核数+1;
② IO密集型任务线程数=CPU核数*2 或 CPU核数/(1-阻塞系统)
阻塞系数≈0.8 - 0.9
public static void main(String[] args) {
int cpuNum = Runtime.getRuntime().availableProcessors();
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(5),
Executors.defaultThreadFactory()
// new ThreadPoolExecutor.AbortPolicy()
// new ThreadPoolExecutor.CallerRunsPolicy()
// new ThreadPoolExecutor.DiscardOldestPolicy()
// new ThreadPoolExecutor.DiscardPolicy()
);
for (int i = 0; i < 15; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t 进入业务处理");
});
}
threadPool.shutdown();
十、死锁的理解
1.死锁即多个线程相互等待的现象,由系统资源不足,进程运行推进的顺序不合适,资源分配不当导致。
2.示例:
public static void main(String[] args) {
String lock1 = "lock1";
String lock2 = "lock2";
new Thread(new DeadLock(lock1, lock2), "A").start();
new Thread(new DeadLock(lock2, lock1), "B").start();
}
static class DeadLock implements Runnable {
private String val1;
private String val2;
public DeadLock(String val1, String val2) {
this.val1 = val1;
this.val2 = val2;
}
@Override
public void run() {
synchronized (val1) {
System.out.println(Thread.currentThread().getName() + "\t 当前持有" + val1 + "\t 准备获取" + val2);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (val2) {
System.out.println(Thread.currentThread().getName() + "\t 已获取" + val2);
}
}
}
}
3.如何排查是否发生死锁
① 查询进程号
jps -l
② 得到Java stack information for the threads listed above,即死锁发生的具体信息
stack 进程号
十一、异步线程CompletableFuture
1.
2.
3.