synchronized和lock简单理解

2019-11-07  本文已影响0人  木木呦

synchronized(同步锁)

思考:锁什么?锁对象

可能锁对象包括:this,临界资源对象(所有线程可能访问的对象,例:new Object()),方法。

synchronized锁对象

方法1到方法3,锁逐渐变重。testSync1是最轻量的。这是因为使用第二/三种对象时,当T1线程访问该对象的同步方法时,T1对整个类进行加锁,其他线程再访问类中其他同步方法需要进行等待。如果用第一种方法进行加锁,锁定的是临界资源object,当其他线程访问该类其他同步方法时,依旧可以访问。所以testSync1是轻量级的,开发中推荐使用。

testSync2和testSync3方法:T1所在线程栈帧访问Test_01类中方法时,会对堆中的Test_01对象进行加锁,此时T2,T3..线程无法访问加锁的Test_01其他同步方法。

加锁的目的:保证操作的原子性。

锁重入

锁可重入:同一个线程,多次调用同步代码,锁定同一个对象,可重入。

锁重入代码 锁重入-继承

以上两种锁重入,都不会发生死锁或者影响结果。

锁与异常

当同步方法中发生异常时,自动释放锁资源。不会影响其他线程执行。注:使用lock时,需要手动释放资源,在finally中。

思考:如果在同步方法中发生了异常应该如何处理?


volatile(可见性)

我们知道,线程是通过cpu调度器执行的,并且在执行中调度器会频繁切换,让我们看起来是多个线程在同时执行,因为速度非常快所以肉眼所见是同时执行。

未加volatile的布尔值,while无限循环。 加了volatile的,while会跳出循环

volatile通知操作系统,在cpu计算过程中,都要检查内存中数据的有效性。保证最新的内存数据被使用。

原理简介:操作系统中有cpu,内存,硬盘等。cpu中有高速缓存(线程之间相互隔离),用来缓存线程执行方法的临时变量。硬盘中存放class字节码文件,加载到内存中。cpu让线程T1去执行内存中的方法时,会把临时变量volatile修饰的b读取到高速缓存中,此时b=true,当其他线程T2对b进行了更改false后,cpu切换到T1,系统会通知T1每次都去内存中读取最新的数据,此时缓存中的b是true,内存中的是false。这就达到了volatile的可见性。

volatile只能保证可见性,不能保证一致性。


线程的Join方法

在t2后运行t1的join方法 运行结果

我们看到,t1调用了join方法后,t1和t2是交叉运行的。t1.join()是在main主函数上调用的,所以只会让main挂起,从而不执行到t3,所以t1  t2之间互不影响。


常量问题

在定义同步代码块时,不要使用常量对象作为锁对象。

例:String s = "abc" 或者 String s = new String("abc");


阻塞状态

ReentrantLock(读音:蕊安春特烙柯)

Lock lock =  new ReentrantLock();

(一)尝试锁:如果有锁,无法获取锁标记,返回false,反之,true。 
boolean isLocked = lock.tryLock();

(二)阻塞尝试锁:阻塞参数代表时长,尝试获取锁标记,若超时,不等待直接返回。

boolean isLocked = lock.tryLock(5, TimtUnit.SECONDS);//尝试等待5秒

(三)lock.lockInterruptibly(); //尝试打断阻塞状态(看上图阻塞状态)

使用这个方法的线程在进行等待时,例如T1获取锁执行代码,未执行完。T2再执行同步代码就需要等待。T2中调用了lock.lockInterruptibly(),此时主线程调用T2.interrupt(),就可以打断T2的等待,相当于杀死了T2,不过T2只要被打断就会抛异常。如果T2获取到了锁,再尝试打断,那么就无法打断了。

例:在windows系统中,打开word文档,此时word白屏未响应,可能需要等一会才能正常显示,这时我们可以尝试用任务管理器强制杀死word软件。

(四)Condition condition = lock.newCondition();  为lock增加条件,例如何时解锁何时加锁。

while(list.size() == 10){

            System.out.println("容器达到10,其它线程就可以进入访问了");

             condition.await();  //释放锁

}


公平锁

Lock lock =new ReentrantLock(true);

解释:有四个线程,T1,T2,T3,T4.  T1获取锁,其他线程访问此T1锁定的代码块时,T2先来的等待2秒了,T3已经等了4秒了,T4等了10秒了。在操作系统中,cpu不管哪个线程等待多久,当T1执行完释放锁资源,会让T2~T4同时竞争锁资源,这时候每个线程都可能获取到锁。公平锁就是,谁等待时间长,就让谁获取锁。公平锁是绝对公平的。

一般不建议,因为实现公平锁,cpu中会额外计算线程的等待时间,如果线程太多100个来说,它会消耗cpu的性能,并发量高的10000个,可想而知。


锁的种类

java中的锁大致分为:偏向锁、自旋锁、轻量级锁、重量级锁

锁的使用方式为:先提供偏向锁,如果不满足的时候,升级为轻量级锁,再不满足升级为重量级锁。自旋锁是一个过渡的锁状态,不是一种实际的锁类型。

锁只能升级,不能降级。

偏向锁

偏向锁 偏向锁示例

如上示例,每次访问同步方法都创建一个object对象,这种情况即使多个线程访问,因为每次都是对一个新的object加锁,所以不会发生多线程争抢同一个资源,也就相当于单线程,JVM会进行优化,把它转为偏向锁。

轻量级锁(过渡锁)


重量级锁简单描述

两种同步总结

reentrantLock(重入锁)量级较轻,建议使用。

synchronized在JDK1.5版本开始做了优化,到JDK1.7版本后,优化效率已经非常好了。在绝对效率上不比reentrantLock差,二者可忽略不计。

使用重入锁,必须必须手工释放锁标记。一般在finally中进行释放unlock()方法。

重入锁功能更多,实现公平锁,尝试打断等。synchronized能做的重入锁都能做。


关于wait和notify都是和while配合应用的。可以避免多线程并发判断逻辑失效的问题。

上一篇下一篇

猜你喜欢

热点阅读