多线程, 高并发, synchronized, volatile

多线程以及高并发学习总结

基础:

  1. 创建线程的三种方式:
    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

  1. 相关方法:
    i. sleep() 线程睡眠 (不占用cpu资源)
    ii. yeild() 谦让,但不一定谦让成功
    iii. join() 在本线程中插入其他线程,等待其他线程执行结束之后再执行本线程. join是在自己的线程中调用其他线程(调用自己的线程没有意义)
    iv. interrupt() 打断,需要根据捕获异常 解决.
    以上方法都可以被interrupt()方法打断, 当以上方法(sleep, yeild, join等)被打断时会抛出异常, 我们应该对这个异常进行捕获

线程分为几种状态

  1. new状态 (new完不start)
  2. start开始运行(线程调度器执行)就是runnable状态
    runnable包括ready和running两种状态
  3. TimedWaiting
    Thread.sleep(time), o.wait(time), t.join(time), LockSupport.parkNanos(), LockSupport.partUtil()
  4. Waiting
    o.wait(), t.join(), LockSupport.park() -> o.notify(), o.notifyAll(), LockSupport.unpark()
  5. Blocked
    synchronized
  6. 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使用了分段锁的概念, 将一个数分成一个数组, 分段锁在数组中, 最后算完加在一起

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值