一网打尽!synchronized关键字入门(三)

简介: 一网打尽!synchronized关键字入门(三)

6.1 偏向锁


 有时锁不存在多线程竞争,总是由同一个线程多次获得。为了让线程获得的锁的代价更低,引入偏向锁。


 当一个线程访问同步代码块之后,会把Mark Word中的偏向锁标识由0改为1,以后该线程进入和退出时,不需要进行CAS操作来加(解)锁,只需要简单的测试一下对象头里是否存储着指向当先线程的偏向锁。

23.png


24.png


偏向锁的撤销:


 等到竞争出现了才释放锁。


 注意:需要等到一个全局安全点来执行撤销


优点:


 只有同一个线程来访问同步代码块时,效率很高


JVM关闭偏向锁指令:-XX:-UseBiasedLocking

25.png


6.2 轻量级锁


 JDK6中引入


 目的:在多线程交替执行同步代码块的情况下,尽量避免重量级锁引起的性能消耗,主要靠自旋来实现。


 如果多线程在同一时刻进入临界区(自旋次数超过某一阈值时,默认是10),会导致轻量级锁变为重量级锁。


栈帧:


 JVM中内存划分中,栈中还包含了对象的各种方法,一个方法就相当于一个“栈桢”,可以存储一些内容。

26.png


原理:

27.png


 线程在执行同步代码块之前:


 ①JVM会现在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word 复制到锁记录当中(Displaced Mark Word)


 ②JVM利用CAS尝试将对象的Mark Word更新为指向锁记录的指针。如果成功则该线程获得锁


 ③失败则需要判断当前对象的Mark Word是否指向当前线程,如果是则表示当线程已经持有对象的锁,执行同步代码快。如果不是只能说明该锁对象被其他线程占用,此时锁需要膨胀到重量级锁,后面的线程进入阻塞状态。


释放过程:


 解锁的时候,会使用CAS操作将Displaced Mark Word替换回到对象头。如果失败,表示当前锁存在竞争,锁会膨胀成重量级锁。

28.png


6.3 自旋锁


 JDK1.4中引入


 循环去获取锁。当线程竞争锁失败之后,先自旋来尝试获取锁,如果锁被占用的时间很短,自旋的效果就较好。默认值是10次。


 适应性自旋锁:


 自旋锁会带来一定的性能消耗,但又不确定自旋次数,此时可以使用适应性自旋锁,意味着自旋的时间由前一次在同一个锁的自旋时间及所得拥有者的状态来决定。

29.png


6.4 锁消除


public String getContent() {
        return new StringBuffer().append("a").append("b").append("c").toString();
    }


其中


@Override
 public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }


append方法是同步的,但getContent方法,每次都是新new一个对象来进行操作。所以对不同的线程来说锁住的对象不同。


编译器(JIT)在运行时,会对一些被检测到不可能存在共享数据竞争的锁进行消除。


6.5 锁粗化

public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 100; i++) {
            sb.append("a");
        }
    }


其中


@Override
 public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }


外层循环了100次,意味着要进入和退出锁各100次,此时JVM就会进行锁粗化。


把append方法同步关键字去掉,扩大在外面来,就只需要进入和退出1次即可。


7.1 代码优化


减少sync的同步代码块的范围:



 同步代码块精简,执行就会更快,可能轻量级锁、自旋锁就搞定了,不会升级为重量级锁。


public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        synchronized (sb) {
          System.out.println("a");
        }
}


降低sync锁的粒度:


 本身没有任何业务相关的代码,锁的对象不设为同一个


读写分离:


 读的时候不加锁,写入和删除的时候加锁,这样就可以保证多个线程同时来读取数据。


 HashTable容器竞争激烈的并发环境下,效率低是因为多个线程竞争同一把锁,假如容器有多把锁,每一把锁用于锁住容器中一部分数据,那么多线程访问容器里面不同的数据段的数据时,线程间不会存在锁竞争,从而有效提高并发访问率。(ConcurrentHashMap)


相关文章
|
8月前
|
安全 算法 Java
多线程(初阶四:synchronized关键字)
多线程(初阶四:synchronized关键字)
55 0
|
5月前
|
Java 程序员 开发者
深入解读:synchronized关键字背后的“黑科技”!
深入解读:synchronized关键字背后的“黑科技”!
37 4
|
6月前
|
存储 安全 Java
(二) 彻底理解Java并发编程之 Synchronized关键字实现原理剖析
Synchronized 关键字(互斥锁)原理,一线大厂不变的面试题,同时也是理解 Java 并发编程必不可少的一环!其中覆盖的知识面很多,需要理解的点也很多,本文会以相关书籍和结合自己的个人理解,从基础的应用范围到底层深入剖析的方式进行阐述,如果错误或疑问欢迎各位看官评论区留言纠正,谢谢!
119 0
|
7月前
|
缓存 Java 编译器
必知的技术知识:Java并发编程:volatile关键字解析
必知的技术知识:Java并发编程:volatile关键字解析
31 0
|
8月前
|
安全 Java 编译器
Java多线程基础-6:线程安全问题及解决措施,synchronized关键字与volatile关键字(一)
线程安全问题是多线程编程中最典型的一类问题之一。如果多线程环境下代码运行的结果是符合我们预期的,即该结果正是在单线程环境中应该出现的结果,则说这个程序是线程安全的。 通俗来说,线程不安全指的就是某一代码在多线程环境下执行会出现bug,而在单线程环境下执行就不会。线程安全问题本质上是由于线程之间的调度顺序的不确定性,正是这样的不确定性,给我们的代码带来了很多“变数”。 本文将对Java多线程编程中,线程安全问题展开详细的讲解。
110 0
|
8月前
|
安全 Java 编译器
是时候来唠一唠synchronized关键字了,Java多线程的必问考点!
本文简要介绍了Java中的`synchronized`关键字,它是用于保证多线程环境下的同步,解决原子性、可见性和顺序性问题。从JDK1.6开始,synchronized进行了优化,性能得到提升,现在仍可在项目中使用。synchronized有三种用法:修饰实例方法、静态方法和代码块。文章还讨论了synchronized修饰代码块的锁对象、静态与非静态方法调用的互斥性,以及构造方法不能被同步修饰。此外,通过反汇编展示了`synchronized`在方法和代码块上的底层实现,涉及ObjectMonitor和monitorenter/monitorexit指令。
512 0
|
Java
关于关键字volatile的一二
关于关键字volatile的一二
86 0
|
存储 缓存 安全
【Java并发编程】Synchronized关键字实现原理(二)
【Java并发编程】Synchronized关键字实现原理
|
存储 缓存 安全
【Java并发编程】Synchronized关键字实现原理(一)
【Java并发编程】Synchronized关键字实现原理
|
SQL 缓存 安全
Java并发编程学习系列七:深入了解volatile关键字
Java并发编程学习系列七:深入了解volatile关键字
123 0
Java并发编程学习系列七:深入了解volatile关键字