1. 线程同步的方法  

1.1 同步方法  

就是使用synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

1.2 同步代码块  

即由synchronized关键字修饰的代码块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。

因为同步是一种高开销的操作,如果没有必要同步整个方法,那就使用同步代码块同步关键代码即可。

1.3 使用volatile关键字实现线程同步

使用volatile修饰变量就相当于告诉JVM,该值可能会被很多线程访问,每次访问时都是从内存中读取,而不是从缓存中读取,因此每个线程访问到的变量值都是一样的,这也就是所谓的可见性。这样就保证了同步。

但是volatile不会提供任何原子操作,因此volatile不能代替synchronized。也不能用来修饰final类型的变量。

1.4 使用ReentrantLock类实现线程同步

它相对于synchronized关键字有更强的灵活性,而且在竞争比较激烈时有更好的性能,但是需要在finally代码中手动释放锁。

1.5 使用ThreadLocal

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

2.  Lock原理基石——CAS算法简述  

比如i++的操作,第一个线程先读取i的值,执行计算后获得一个新值B(i从1变成2),然后CAS算法就试图将i的值从1改为2。这时如果检测到i的值未被其他线程更改(值还是1),则说明这段时间无竞争,CAS 算法将i的值变成2。

若其他线程修改了变量(比如i从1变成了3),那么CAS会检测到这个变化,就会放弃把i的值赋值为2。这时会获得新值3,并重新进行自己的计算。

CAS算法无法避免ABA问题,误认为没有竞争,可以使用版本号解决,即1A2B3A。

3.  Synchronized的实现原理

Java中的每个对象都可以作为锁。

(1)普通同步方法,锁是当前实例对象。

(2)静态同步方法,锁是当前类的class对象。

(3)同步代码块,锁是括号中的对象。

3.1  Synchronized的使用

public class SynchronizedTest {private static Object object = new Object();public static void main(String[] args) throws Exception{synchronized(object) {}}public static synchronized void m() {}
}

通过javap -c命令查看程序对应的字节码。

public static void main(java.lang.String[]) throws java.lang.Exception;descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=3, args_size=10: getstatic     #2                  // Field object:Ljava/lang/Object3: dup4: astore_15: monitorenter            //监视器进入,获取锁6: aload_17: monitorexit              //监视器退出,释放锁8: goto          1611: astore_212: aload_113: monitorexit14: aload_215: athrow16: returnpublic static synchronized void m();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZEDCode:stack=0, locals=0, args_size=00: returnLineNumberTable:line 9: 0

3.2  对于同步代码块

可以看到编译器会在同步代码块的前后,分别会为我们添加Monitorenter和Monitorexit指令。

关于Monitorenter和Monitorexit的官方解释如下:

//Each object is associated with a monitor. A monitor is locked if and only if it has an owner.
//The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
//If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one.
//The thread is then the owner of the monitor.
//If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
//If another thread already owns the monitor associated with objectref, the thread blocks
//until the monitor's entry count is zero, then tries again to gain ownership.
//The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
//The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner.
//Other threads that are blocking to enter the monitor are allowed to attempt to do so.

简单翻译总结概述如下:

每个对象有一个monitor。当monitor被占有时就会处于锁定状态,线程执行到Monitorenter指令时尝试获取monitor的所有权:

(1)如果monitor的进入数为0,那么该线程就会进入monitor,然后将进入数置为1,从而该线程即占有了monitor。

(2)如果该线程已经占有该monitor,重新进入会使monitor的进入数加1。

(3)并且执行monitorexit的线程肯定是monitor的持有者。当monitorexit指令执行时,monitor的进入数减1,如果进入数变为0,占有锁的线程就会退出monitor,不再是这个monitor的持有者。

(4)如果其他线程已经占有了monitor,那么这个线程就会进入阻塞状态,直到monitor的进入数为0,才会尝试去获取monitor的所有权。

通过上面的描述,我们知道Synchronized关键字的底层是通过一个monitor的对象来完成同步的功能,其实wait/notify等方法也是依赖于monitor对象,所以Java才规定只有在同步块/方法中才能调用wait/notify等方法。

3.3  对于同步方法

同步方法中依靠方法的ACC_SYNCHRONIZED标识符来实现。当同步方法被调用时,会检查方法的 ACC_SYNCHRONIZED标识符是否被设置,如果设置了,执行线程将先获取monitor后才能执行方法体,执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

综上,两种同步方式本质上都是对指定对象相关联的monitor的互斥性获取。

4  锁的四种状态

一般锁有4种状态:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。

偏向锁,轻量级锁,重量级锁分别解决三个问题,只有一个线程进入临界区,多个线程交替进入临界区,多线程同时进入临界区。

4.1  重量级锁

Synchronized同步是通过对象内部的monitor来实现的。JVM实现线程之间的切换成本比较高,这也是为什么Synchronized效率低的原因。因此,称之为重量级锁。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,引入了“轻量级锁”和“偏向锁”。

4.2  偏向锁

public class SynchronizedTest {private static Object lock = new Object();public static void main(String[] args) {method1();method2();}synchronized static void method1() {}synchronized static void method2() {}
}

当某个线程访问method1时,会在SynchronizedTest.class对象的对象头和栈帧的锁记录中存储该线程ID,下次该线程在进入同步方法2时,只需要判断对象头存储的线程ID是否为当前线程,而不需要进行加锁和解锁操作。

4.3  轻量级锁

轻量级锁并不是用来代替重量级锁的,它的本意是为了减少多线程进入互斥的几率,并不是要替代互斥。

轻量级锁性能提升的依据是“大部分同步代码一般都处于无锁竞争状态”,这是一个经验数据。轻量级锁的轻就体现在,如果没有竞争,就会使用CAS操作完成锁的获取和释放,然后避免使用互斥锁的开销。但如果存在竞争。除了互斥的开销外,还要有额外的CAS操作。因此在有竞争的情况下,轻量级锁会比传统的重量级锁还要慢。这个时候轻量锁就不再有效,要膨胀为重量级锁,后面等待锁的线程要进入阻塞状态。

5  其他的一些优化

5.1 自旋锁

自旋不是锁的一种状态,只是轻锁膨胀成重锁后的一个优化动作。

重锁的互斥同步操作对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转移到内核态去执行,这些操作会降低并发性能,同时很多时候synchronized代码块中逻辑简单执行速度快,为了这段时间去挂起和恢复线程并不值得。

因此,当线程在获取轻量级锁执行CAS操作失败的时候,要通过自旋来获取重量级锁的。就是让等待锁的线程不要被阻塞,而是在Synchronized的边界做忙循环,这就是自旋。就是让后面请求的线程先“稍等一会”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。当尝试一定的次数后如果仍然没有成功获得锁,再进入阻塞状态。

(自旋锁在1.4就有只不过默认的是关闭的,jdk1.6是默认开启的)

5.2 适应性自旋锁

自旋本身也要一直占用处理器时间,如果锁被占用的时间很短,那么自旋等待的效果就会很好。反之自旋的线程只能白白的占用处理器自旋,而不会做任何有用的工作,反而会给性能带来浪费。所以自旋几次没有获取到锁之后才把线程挂起。

但是JDK1.6之后采用了一种更聪明的方式——适应性自旋,简单来说就是如果在同一个锁对象上,自旋锁刚刚成功获得过锁,并且持有锁的线程正在运行,那么虚拟机就会认为这次自旋也很有可能再次成功,进而他会将自旋等待的时间相对更长一些,比如100个循环。反之,如果在这个锁上通过自旋从来没有获得过,以后获取这个锁将可能省略了自旋过程,以避免浪费处理器时间。所以说适应性自旋是对自旋的一种优化。

5.3 锁粗化

原则上,将同步代码块的范围尽量缩小,使得需要同步的操作尽可能的少,如果存在锁竞争那等待的线程也能很快拿到锁。但是如果在没有竞争时,对一个对象进行一系列连续的加解锁,就会造成性能的浪费,比如:

StringBuffer sb = new StringBuffer();
sb.append(1);
sb.append(2);
sb.append(3);

这种情况下,虚拟机就会进行锁粗化,以上述代码为例,就是将锁范围扩展到第一个append之前到最后一个append之后。这样只需要一次加锁就可以了。

5.4 锁消除
锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁。看下面这段程序:

for (int i = 0; i < 100000000; i++) {append("abc", "def");
}
public void append(String str1, String str2) {
StringBuffer sb = new StringBuffer();
sb.append(str1).append(str2);
}

虽然StringBuffer的append是一个同步方法,但是这段程序中的StringBuffer属于一个局部变量,并且不会从该方法中逃逸出去,所以其实这过程是线程安全的,就可以进行锁消除。

博主水平有限,如有问题请多指正。转载请注明出处为:Java技术——同步锁的各种知识总结_SEU_Calvin的博客-CSDN博客_同步方法锁的是什么。

Java技术——同步锁的各种知识总结相关推荐

  1. java中同步锁的原理和实现

    接口 Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作.此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象. 锁是控制 ...

  2. java多线程--同步锁、

    同步代码块: 语法: synchronized(同步锁) { 需要同步操作的代码 } --------------------------------------------------- 同步锁: ...

  3. Java:同步锁概念

    同步锁介绍 对于非 static 方法,同步锁就是 this. 对于 static 方法,使用当前方法所在类的字节码对象(A.class). 同步代码块: 语法 synchronized(同步锁) { ...

  4. Java的同步锁和同步方法(synchronized)

    一.同步锁 当多个线程使用同一个共享资源时 可以将处理共享资源的代码放置在一个代码块中 使用synchronized关键字来修饰 被称作同步代码块 同步锁(又称 对象监视器) 语法: synchron ...

  5. java线程锁为什么要唯一,java线程同步-锁 - 谁说这么长又没有中心思想还与个人技术主题无关的的标题是胡闹??? - OSCHINA - 中文开源技术交流社区...

    1.synchronized 用在方法和代码块上有什么区别呢? synchronized 用在方法签名上(以test为例),当某个线程调用此方法时,会获取该实例的对象锁,方法未结束之前,其他线程只能去 ...

  6. java同步锁实例_Java lock同步锁使用实例解析

    这篇文章主要介绍了Java lock同步锁使用实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1)Lock是一个接口,而synchroniz ...

  7. Java线程学习实例——采用同步锁,互斥锁与同步锁的区别,synchronized的使用方法

    栗子来源:https://blog.csdn.net/wenzhi20102321/article/details/52524545 首先对java中同步锁与互斥锁进行区分,主要来源于知乎中的大佬总结 ...

  8. Java基础知识多线程,同步锁

    多线程 /*多线程: 多任务执行,多路径执行优点:提高性能提高效率进程与线程之间的区别:进程 : 系统中的程序,一个进程之间可以包含1~n个线程,系统中资源分配的最小单位,每个进程都有自己的代码与数据 ...

  9. 深入理解多线程(五)—— Java虚拟机的锁优化技术

    本文是<深入理解多线程>的第五篇文章,前面几篇文章中我们从synchronized的实现原理开始,一直介绍到了Monitor的实现原理. 前情提要 通过前面几篇文章,我们已经知道: 1.同 ...

最新文章

  1. 面试季:如何在面试中介绍自己的项目经验
  2. 《python源码剖析》,看看
  3. java层 android_Android开发实践:Java层与Jni层的数组传递
  4. pycharm创建mysql项目_python+Django+pycharm+mysql 搭建首个web项目详解
  5. JUnit规则–引发异常时执行附加验证
  6. leetcode1046. 最后一块石头的重量(堆)
  7. windows环境下nginx的入门配置跳转tomcat
  8. 最强开源OCR!印刷体古籍文字识别超越著名商业软件ABBYY
  9. 快速搭建MQTT服务器(MQTTnet和Apache Apollo)
  10. Java 8 基础教程 - Predicate,java基础面试笔试题
  11. 炸了!亚马逊薪资文件泄露!原来这么多人年薪百万
  12. android 投屏 ipad,安卓手机投屏到ipad上
  13. 苹果4s什么时候上市的_前景***好苹果苗什么时候价格
  14. java dozer,MapStruct相当于提示(Dozer)?
  15. c语言编程订单统计,C语言课程设计订单管理系统.pdf
  16. 读书笔记-《wxPython in Action》一
  17. python中的数据过滤
  18. 网传国内互联网梯队划分,网友坐不住了!
  19. 51Nod - 1588 幸运树(DFS)
  20. 电脑开机时自动开启小键盘

热门文章

  1. 解决游戏中的通货膨胀问题
  2. magic2可以用鸿蒙系统吗,magic4.0是鸿蒙系统吗_magic4.0是不是鸿蒙系统
  3. Linux搭建MonggoDB环境
  4. 城际通09年新版3550-3512主要更新(附原版下载)
  5. 2023中国重庆大数据及云计算展览会
  6. 写一份java开发周报
  7. python谷歌地图查找附近地铁站_如何在谷歌地图中可视化显示路线
  8. Notepad++强大的代码补全和代码提示功能的方法
  9. 软件实施-01-服务器
  10. 工作小技巧—在word中使用邮件批量插入excel中的数据