多线程以及高并发学习总结
基础:
- 创建线程的三种方式:
i.new Thread(new MyRun()).start()
其中MyRun为实现Runnable接口并且重写run()方法
ii.new MyThread().start()
其中MyThread为继承Thread类的子类,重写run()方法
iii.new Thread(() -> {}).start()
匿名内部类 升级为 lambda表达式的写法
iiii. 还有几种写法
T_exercise t1 = new T_exercise();
new Thread(() -> t1.a()).start();
//等同于以下写法
new Thread(t1::a).start();
创建线程不是启动线程,启动线程的三种方式为:
1.Thread 2. Runnable 3. Executors.newCachedThread
- 相关方法:
i. sleep() 线程睡眠 (不占用cpu资源)
ii. yeild() 谦让,但不一定谦让成功
iii. join() 在本线程中插入其他线程,等待其他线程执行结束之后再执行本线程. join是在自己的线程中调用其他线程(调用自己的线程没有意义)
iv. interrupt() 打断,需要根据捕获异常 解决.
以上方法都可以被interrupt()方法打断, 当以上方法(sleep, yeild, join等)被打断时会抛出异常, 我们应该对这个异常进行捕获
线程分为几种状态
- new状态 (new完不start)
- start开始运行(线程调度器执行)就是runnable状态
runnable包括ready和running两种状态 - TimedWaiting
Thread.sleep(time), o.wait(time), t.join(time), LockSupport.parkNanos(), LockSupport.partUtil() - Waiting
o.wait(), t.join(), LockSupport.park() -> o.notify(), o.notifyAll(), LockSupport.unpark() - Blocked
synchronized - Teminated
Teminated之后不能调用start()方法.
不要关闭线程stop()方法, 应该让他正常结束. 容易产生状态不一致!
Synchronized关键字
synchronized 锁的不是代码,锁的是对象, 包括class对象.
锁的得是同一个对象才能锁住.
Synchronized既保证原子性又保证可见性.
synchronized 不能锁常量 基本数据类型
问题1: 同步方法和非同步方法是否可以同时调用? 当然可以.非同步方法不看有没有锁的.
问题2: 对业务写方法加锁, 读方法不加锁: 可能出现脏读dirtyRead(看业务可不可以)
synchronized是可重入锁(判断是同一把锁就放行,不用再拿锁)
程序出现异常, 默认情况锁被释放.
synchronized的底层实现方法, 在对象上有一个mark word.
锁升级, 重量级锁, 去操作系统内核要锁.
hotspot实现:
sync(obj) -> markword 记录这个线程ID (偏向锁)
如果出现争用 升级 膨胀 为 -> 自旋锁(无意义循环)
如果争用过大 继续升级 膨胀为 -> 重量级锁(系统锁)
问题: 什么时候使用轻量级锁(自旋锁, 乐观锁) 什么时候使用重量级锁(系统锁)?
执行时间短 + 线程数少, 用自旋锁, 因为自旋锁消耗cpu资源
执行时间长 + 线程数多, 用系统锁
volatile关键字
保证可见性, 禁止指令重排序.
为什么引入volatile概念? 因为超高并发环境下可能就算使用synchornized也有可能出现问题, 原因就是volatile. 出现情况(双重检查锁).
保证可见性
什么是可见性: 就是一个线程对堆内存中的数据的更改其实先改自己副本中的数据, 但什么时候写回堆内存不一定, 可见性就是保证每次修改完副本中数据之后,马上写回堆内存中, 这样对于其他线程而言就是可见的.(指的是线程之间可见)
可见性实现 CPU缓存一致性协议(MESI)
指令重排序
单例模式(饿汉 -> 懒汉 -> 懒汉升级synchornized -> 锁细化 ->
DCL单例
Double Check Lock(双重检查锁)
双重检查锁到底单例对象应不应该加volatile?
应该, 因为超高并发情况下会出现数据异常的问题.原因就是指令重排序.
volatile不是能够替代synchornized, volatile不能保证多个线程共同修改running变量时带来的数据不一致问题.
锁优化:
锁细化, 锁粗化
AtomicXxx类 (juc包下的atomic的包中)
这里用的就是CAS锁(Compare And Set/Swap)
例如AtomicInteger 中 incrementAndGet中就调用了Unsafe类中的compareAndSet(现在叫weakCompareAndSet()方法
cas方法就是cas(V, Excepted, NewValue)
其中V为 改的值, Excepted为 期望的值, NewValue为 想设置的值.
如果期望值等于拿到的值,代表
什么是乐观锁,什么是悲观锁:?
从思想上来说,synchronized属于悲观锁,悲观的认为程序中的并发情况严重,所以严防死守,CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新。
在java中除了上面提到的Atomic系列类,以及Lock系列类夺得底层实现,甚至在JAVA1.6以上版本,synchronized转变为重量级锁之前,也会采用(Unsafe类中)CAS机制。
CAS机制 以及 ABA问题以及解决办法
这个问题引自https://blog.csdn.net/qq_32998153/article/details/79529704
ABA解决方法增加版本号AtomicStampedReference解决)博客
Unsafe类 单例模式
Unsafe可以直接操纵java虚拟机中的内存 类似C 和 C++中的指针
怎么想到Unsafe类的?
因为AtomicXxx底层使用CAS实现无锁优化, CAS就是使用Unsafe实现的.(计算机源语支持, 所以在cas中就不会出现问题.
AtomicXxx类
使用方法:
AtomicInteger count = new AtomicInteger(0);
for(int i = 0; i < 10000; i++)
count.incrementAndGet(); //count++
有了它就不用再加锁了, AtomicXxx底层使用CAS实现了锁机制
LongAdder这个类在更高并发情况下更适合用来记录, 例如秒杀等.
LongAdder使用了分段锁的概念, 将一个数分成一个数组, 分段锁在数组中, 最后算完加在一起