并发存在的问题:
- 可见性:要求一个线程对变量的操作改变对另外一个线程要是可以看见的。例如:当线程1读取主内存中变量到工作内存,线程2也读取并在工作内存改变其值,返回主内存,但线程1还是默认自己工作内存中的值。
- 原子性:在一次或多次操作中,要么所有的操作都执行不受其他干扰,要么所有的操作都不执行。
- 有序性:程序中代码的执行顺序,java编译和执行时会对代码进行优化,可能执行顺序与我们编写顺序不一致
java内存模型:
解决原子性问题:synchronized保证只有一个线程拿到锁,能够进入代码块。
解决可见性问题:1.变量加volatile. 2.用synchronized代码段(执行lock指令,使工作内存中共享变量的值更新到主内存,并使其他工作内存中值失效,从而重新去主存中拿实现刷新)
解决有序性问题:1.加了synchronized后这一段代码块的执行顺序无序就没关系了,因为别的代码块必须等这一段都操作完成后才能拿到锁。2.变量加volatile后执行顺序就不重排序
可重入锁:
- 外层函数获得锁之后 ,内层函数仍然可以获取该锁,synchronized锁的对象中有一个计数器,可以记录线程获得几次该锁,在执行完一次同步代码块计数器减一直到为0释放锁
- 可重入好处:1.避免死锁 2.更好地让我们封装代码
-
ReentrantLock的tryLock和lock都实现了锁重入,用state+表示
公平锁:
- 公平锁是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到
不可中断性:
- 不可中断定义:一个线程获得锁后,另一个线程想获得该锁必须处于阻塞或等待状态,如果第一个线程不释放锁,那该线程一直保证该状态,该线程状态不可以被中断(thread.interrupt())。
- 不可中断锁:synchronized,Lock的lock()方法是不可中断锁,tryLock()是可中断锁
synchronized重量级锁种monitor:
- synchronized的锁对象会关联到一个monitor,是jvm执行到同步代码块,发现锁对象没有monitor,就会创建monitor,monitor有两个重要成员变量,owner:拥有这把锁的线程。recursions:该线程拥有锁的次数。执行到monitorenter时recursions会加1,执行monitorexit时recursions会减一,当recursions减到0这个线程就会释放锁。 monitor才是正真的锁对象
- 反汇编:线程执行monitorenter,获得该锁 。执行monitorexit,释放该锁
- 当synchronized出现异常时,会释放锁,执行汇编中monitorexit方法
- 同步方法(使用synchronized修饰的方法):会隐式调用monitorenter和monitorexit。
CAS(compare and swap):
- CAS可以保证共享变量赋值时的原子性(两个线程同时修改时,不会出现意外情况)
- CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。内存值和预期值一样更新B,不一样则重新去地址拿预期值。
- CAS机制属于乐观锁,synchronized属于悲观锁
synchronized锁升级过程:
- 无锁>偏向锁>轻量级锁>重量级锁
- 偏向锁:当一个进程反复获得该锁的情况下使用偏向锁。当锁对象第一次被线程获得时,虚拟机将对象头设置为01,同时通过CAS操作,将线程ID记录在Mark Word中。
- 轻量级锁:线程交替执行代码块,无竞争的情况下时候使用。
- 自旋锁:就是指当有另外一个线程来竞争锁时,这个线程会在原地循环等待,而不是把该线程给阻塞,直到那个获得锁的线程释放锁之后,这个线程就可以马上获得锁的。锁在原地循环的时候,是会消耗cpu的,就相当于在执行一个啥也没有的for循环。要求物理机有一个以上的处理器。
- 锁消除:锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除。锁削除的主要判定依据来源于逃逸分析的数据支持,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就可以把它们当作栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行。https://blog.csdn.net/weixin_33877885/article/details/93194672?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161780147716780261975319%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=161780147716780261975319&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-93194672.pc_search_result_hbase_insert&utm_term=%E9%94%81%E6%B6%88%E9%99%A4
- 锁粗化:https://blog.csdn.net/zj57356498318/article/details/103364024?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161780183316780269838274%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=161780183316780269838274&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-103364024.pc_search_result_hbase_insert&utm_term=%E9%94%81%E7%B2%97%E5%8C%96
平时代码对synchronized优化
- 读不加锁,写的时候加锁
- 减少synchronized的范围
- 降低synchronized的粒度
synchronized与Lock区别
- synchronized是一个关键字,Lock是一个接口
- synchronized会自动释放锁,Lock必须手动释放
- synchronized是不可中断的,Lock是可中断(tryLock)也可不中断(lock)的
- Lock可以知道线程有没有拿到锁(Boolean b=lock.tryLock,返回true或者false),而synchronized不能
- synchronized可以锁住代码块和方法,Lock只能锁代码块
- Lock(ReentrantReaderLock)可以提高多个线程读的效率
- synchronized是非公平锁,ReentrantLock可以控制是否是公平锁
as-if-serial:
- 不管怎么重排序单线程程序的执行结果不能被改变
volatile:
- 只能修饰变量
- 不能保证原子性
- 保证线程可见性:每次读取一个变量都会去主内存中重写读取(执行汇编中lock指令,使工作内存中共享变量的值更新到主内存,并使其他工作内存中值失效,从而重新去主存中拿实现刷新)。
- 保证执行有序性:加屏障,不可重排序。
- volatile不会造成线程的阻塞,synchronized会造成线程的阻塞
ReentrantLock :
-
tryLock:表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待
- ReentrantLock的tryLock和lock都实现了锁重入,用state+1表示表示锁状态