1 并发编程三要素
Java 并发编程三要素_新猿一马的博客-CSDN博客并发编程三要素学习。https://blog.csdn.net/jack1liu/article/details/124310132
2 死锁产生和解决
3 创建线程的方式
创建线程的四种方式_新猿一马的博客-CSDN博客_线程的创建四种创建方式及其比较。https://blog.csdn.net/jack1liu/article/details/112973372
4 线程的生命周期
Java线程生命周期_新猿一马的博客-CSDN博客Java线程生命周期https://blog.csdn.net/jack1liu/article/details/124312463
5 深入理解和运用线程池
6 ThreadLocal 理解和源码分析
7 单例模式的线程安全性
8 synchronized 的实现原理
[死磕 Java 并发] --- 深入分析synchronized的实现原理 - Java 技术驿站http://cmsblogs.com/?p=2071
8.1 自旋锁
什么是自旋?
执行一段无意义的循环。
为什么需要自旋锁?
许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的。所以引入自旋锁。 何谓自旋锁? 所谓自旋锁,就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。
自旋锁有啥问题么?
自旋等待不能替代阻塞,虽然它可以避免线程切换带来的开销,但是它占用了处理器的时间。如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好,反之,自旋的线程就会白白消耗掉处理的资源,它不会做任何有意义的工作,典型的占着茅坑不拉屎,这样反而会带来性能上的浪费。
在JDK1.6中默认开启。同时自旋的默认次数为10次,可以通过参数-XX:PreBlockSpin来调整。
8.2 自适应自旋锁
所谓自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。它怎么做呢?
线程如果自旋成功了,那么下次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。
反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。 有了自适应自旋锁,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测会越来越准确,虚拟机会变得越来越聪明。
8.3 锁消除
为了保证数据的完整性,我们在进行操作时需要对这部分操作进行同步控制。
有些情况下,JVM 检测到不可能存在共享数据竞争,JVM 会对这些同步锁进行锁消除。
public void vectorTest(){
Vector<String> vector = new Vector<String>();
for(int i = 0 ; i < 10 ; i++){
vector.add(i + "");
}
System.out.println(vector);
}
在运行这段代码时,JVM 可以明显检测到变量 vector 没有逃逸出方法 vectorTest() 之外,所以JVM 可以大胆地将 vector 内部的加锁操作消除。
8.4 锁粗化
我们知道在使用同步锁的时候,需要让同步块的作用范围尽可能小。仅在共享数据的实际作用域中才进行同步,这样做的目的是为了使需要同步的操作数量尽可能缩小,如果存在锁竞争,那么等待锁的线程也能尽快拿到锁。
在大多数的情况下,上述观点是正确的,LZ 也一直坚持着这个观点。但是如果一系列的连续加锁解锁操作,可能会导致不必要的性能损耗。为了减少性能损耗,引入锁粗化的概念。
锁粗话概念比较好理解,就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。如上面实例:vector 每次 add 的时候都需要加锁操作,JVM 检测到对同一个对象(vector)连续加锁、解锁操作,会合并一个更大范围的加锁、解锁操作,即加锁解锁操作会移到 for 循环之外。
9 volatile的实现原理
[死磕 Java 并发] --- 深入分析volatile的实现原理 - Java 技术驿站http://cmsblogs.com/?p=2092
10 深入分析CAS
[死磕 Java 并发] --- J.U.C之并发工具类:CyclicBarrier - Java 技术驿站http://cmsblogs.com/?p=2235
10.1 CAS缺陷
CAS虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方面:
循环时间太长、只能保证一个共享变量原子操作、ABA问题
- 循环时间太长
如果自旋 CAS 长时间地不成功,则会给 CPU 带来非常大的开销。在 JUC 中有些地方就限制了CAS 自旋的次数,例如 BlockingQueue的SynchronousQueue。
- 只能保证一个共享变量原子操作
看了 CAS 的实现就知道这只能针对一个共享变量,如果是多个共享变量就只能使用锁了,当然如果你有办法把多个变量整成一个变量,利用 CAS 也不错。例如读写锁中 state 的高地位。
- ABA问题
CAS 需要检查操作值有没有发生改变,如果没有发生改变则更新。但是存在这样一种情况:如果一个值原来是 A,变成了 B,然后又变成了 A,那么在 CAS 检查的时候会发现没有改变,但是实质上它已经发生了改变,这就是所谓的 ABA 问题。对于 ABA 问题其解决方案是加上版本号,即在每个变量都加上一个版本号,每次改变时加1,即 A —> B —> A,变成 1A —> 2B —> 3A。
Java 提供了 AtomicStampedReference 来解决。AtomicStampedReference 通过包装 [E,Integer]的元组来对对象标记版本戳 stamp,从而避免ABA问题。
11 深入分析 AQS
[死磕 Java 并发] --- J.U.C之AQS:AQS简介 - Java 技术驿站Java的内置锁一直都是备受争议的,在JDK1.6之前,synchronized这个重量级锁其性能一直都是较为低下,虽然在1.6后,进行大量的锁优化策略(【死磕Java并发】-https://www.cmsblogs.com/article/1391297814356692992[死磕 Java 并发] --- J.U.C之AQS:CLH同步队列 - Java 技术驿站在上篇博客【死磕Java并发】-----J.U.C之AQS:AQS简介中提到了AQS内部维护着一个FIFO队列,该队列就是CLH同步队列。CLH同步队列是一个FIFO双向队列,https://www.cmsblogs.com/article/1391297829913366528
AQS 使用一个int类型的成员变量state来表示同步状态,当state>0时表示已经获取了锁,当state = 0时表示释放了锁。
它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作,当然AQS可以确保对state的操作是安全的。
12 重入锁 ReentrantLock
12.1 ReentrantLock 与 synchronized 的区别
首先他们肯定具有相同的功能和内存语义。
- 与 synchronized 相比,ReentrantLock 提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。
- ReentrantLock 还提供了条件 Condition,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock 更加适合(以后会阐述Condition)。
- ReentrantLock 提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而 synchronized 则一旦进入锁请求要么成功要么阻塞,所以相比synchronized 而言,ReentrantLock 会不容易产生死锁些。
- ReentrantLock 支持更加灵活的同步代码块,但是使用 synchronized 时,只能在同一个synchronized 块结构中获取和释放。注:ReentrantLock 的锁释放一定要在 finally 中处理,否则可能会产生严重的后果。
- ReentrantLock 支持中断处理,且性能较 synchronized 会好些。
13 常用的并发工具类有哪些
- CountDownLatch
CountDownLatch 主要用来解决一个线程等待多个线程的场景。
- CyclicBarrier
CyclicBarrier 是一组线程之间互相等待
13.1 CountDownlatch 与 CyclicBarrier 对比
- CountDownLatch 的作用是允许1或 N 个线程等待其他线程完成执行;而 CyclicBarrier 则是允许 N 个线程相互等待。
- CountDownLatch 的计数器无法被重置;CyclicBarrier 的计数器可以被重置后使用,因此它被称为是循环的 barrier。
- Semaphore
- Exchanger
14 乐观锁和悲观锁
14.1 悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
- mysql 的行锁、表锁、读锁、写锁,都是在做操作之前先上锁。
- Java 中
synchronized 和 Lock 也是
悲观锁
。
14.2 乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下,在此期间有没有别人去更新这个数据,可以使用版本号机制和CAS算法实现。
乐观锁适用于多读的应用类型,这样可以提高吞吐量。
- 乐观锁一般会使用版本号机制或CAS算法实现。
15 深入理解 ConcurrentHashMap
16 Sleep 和 Wait
16.1 sleep 的理解
sleep 的作用是让线程休眠指定的时间,在时间到达时恢复。也就是说 sleep 将在时间到达事件事恢复线程执行。
16.2 wait 的理解
调用 wait 方法将会将调用者的线程挂起,直到其他线程调用同一个对象的 notify 方法才会重新激活调用者。
16.3 sleep 与 wait 差异
1、来自不同的类:sleep 是 Thread 的静态类方法,wait 是 Object 类的方法。
2、有没有释放锁:sleep 方法没有释放锁,而 wait 方法释放了锁。
3、wait 要等待其他线程调用 notify/notifyAll 唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep 可以用时间指定使它自动唤醒过来,如果时间不到只能调用 interrupt() 强行打断。
4、sleep 必须捕获异常,而 wait,notify 和 notifyAll 不需要捕获异常。
5、使用范围:wait 只能在同步控制方法或者同步控制块里面使用,而 sleep 可以在任何地方使用。
17 其他
多线程同步有哪几种方法?
线程的调度策略
怎么唤醒一个阻塞的线程
不可变对象对多线程有什么帮助
Java 中用到的线程调度算法是什么