synchronized
多线程问题
//希望输出是0,但结果几乎每次都不一样
public class IncTest {
private volatile static int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread() {
@Override
public void run() {
for( int j = 0 ; j < 1000000 ; j++ ) i++;
}
};
a.start();
Thread b = new Thread() {
@Override
public void run() {
for( int j = 0 ; j < 1000000 ; j++ ) i--;
}
};
b.start();
a.join();
b.join();
System.out.println(i);
}
}
其中结果可能不为0的原因就是其中的i++ 和 i-- 不是 原子性 的操作
即在线程a操作i变量值的时候可能会有另外的线程插入进来也进行i变量的操作,
从而导致i变量 值变化的不稳定性
java多线程解决方法
解决多线程问题的方式就在于将并发变成 同步,即操作共享变量的原子性,
同一时刻 内一般只能由 一个线程进行操作(共享锁除外)
java保证共享变量操作的原子性方法有:
- synchronized 锁机制
- 循环 CAS
syn用法
syn用法 | 用法 | 含义 |
---|---|---|
语句级别 | synchronized (Object obj) {同步代码块} | obj对象 中持有锁,谁拥有obj对象中的锁即可执行同步代码块,否则需等待其他线程释放obj中的锁 |
实例方法级别 | public synchronized void methodName(paramters…){方法块} | 等同于 锁为 实例对象this : public void methodName(paramters…){synchronized (this){方法块} } |
静态方法级别 | public static synchronized void methodName(paramters…){方法块} | 等同于 锁为 类对象class : public void methodName(paramters…){synchronized (本类Class对象)){方法块} } |
synchronized过程
锁对象头的内存结构
大小 | 名称 | 存储内容 |
---|---|---|
64bit | Mark Word | 对象 hashcode,gc 分代年龄,锁相关信息(锁的类型,锁对应的线程ID,锁的状态(是否被获取)) |
64bit | Class Metadata Address | 对象对应的类型地址 |
32bit | Array Length | 如果是数组对象,即为数组长度 |
syn 锁的升级
偏向锁
适用场景 : 适用于竞争较少,经常处于同一个线程自己竞争锁和释放锁的情况,就好比于有一个公共卫生间,
但是一般只有一个人在那片区域使用厕所,可能就上厕所不需要关门,也不需要调整厕所指示灯,全部显示为占用即可,
这样就减少了指示灯调整的开销。
竞争锁
理想状态下的偏向锁竞争
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lp2rLxhD-1575521748542)(en-resource://database/522:0)]
轻量锁
使用场景: 适用于竞争一般,读多写少的情况
简介: 在CAS 竞争锁失败后,便自旋CAS 获取锁
重量锁
简介:
获取锁 CAS获取锁失败后,自身阻塞,等待持有锁的线程释放锁通知后才继续CAS争取锁
释放锁 释放锁,通知其他阻塞线程开始竞争锁
适用场景:
适用于竞争激烈,写比较多的情况
各种锁的比较
锁 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 对于同个线程的锁切换几乎没有开销 | 对于多个线程竞争锁需要临时暂停持有锁线程,导致竞争锁时延时加大 | 锁竞争不是特别激烈,适用于只有一个线程访问情景 |
轻量锁 | 自旋CAS,响应时间较短 | 如果长时间没有获取到锁,cpu消耗较大 | 读多写少,追求响应时间 |
重量锁 | 通知机制实现,不会过度消耗cpu,增大了吞吐量 | 线程阻塞,响应时间较慢 | 竞争较大,追求吞吐量 |