并发编程线程安全之同步锁Synchronized

一、原子性定义

原子性的本质是互斥访问,同一时刻只有一个线程对它进行访问操作

二、原子性问题的简述

public class AutomicDemo {

    int count = 0;
    public static void main(String[] args) throws InterruptedException {

        AutomicDemo automicDemo = new AutomicDemo();


        Thread thread1 = new Thread(() ->
        {
            for (int j = 0; j < 1000; j++) {
                automicDemo.incr();
            }
        });

        Thread thread2 = new Thread(() ->
        {
            for (int j = 0; j < 1000; j++) {
                automicDemo.incr();
            }
        });

        thread1.start();
        thread2.start();
        // join方法保证线程执行完毕
        thread1.join();
        thread2.join();
        System.out.println("i的结果值为:"+automicDemo.count);
    }


    public void incr(){
        count++;
    }
}

运行结果:

线程1线程2各循环一千次执行i++操作,正常情况下应该得到的值是2000,那么为什么会得到1412呢?这是由于当前代码中的i++操作是非原子性的。

并发编程线程安全之同步锁Synchronized_第1张图片

其实一个count++操作是分为3步的:1.加载2.计算3.写入内存。由上图可以看出来,在同一时刻,线程A与线程B同时运行,当线程A刚把count为0的值加载到寄存器的时候,此时线程进行了切换,线程B完成了整个count++操作并把结果写入了内存,此时线程A接着执行,那么线程A加载到寄存器的count还是0,因此它计算后也把count=1存入了内存,这无形中就少了一次计算。思考:那么应该怎么解决这个问题呢?加同步锁synchronized关键字,保证在同一时刻,只有一个线程能够访问并操作count++       

并发编程线程安全之同步锁Synchronized_第2张图片思考:   为什么加了synchronized关键字 系统就会认定它为一个同步锁呢,从而避免多线程对该方法 的一个操作呢?(见3.3)synchronized的作用范围是什么呢?(见3.1)锁的本质又是什么呢?(见3.2)

三、Synchronized关键字

3.1 作用范围

  • 修饰实例方法  创建不同的对象,都可以访问该实例
  • 修饰静态方法   全局锁
  • 修饰代码块

synchronized() 括号中可以存储任何一个对象, 影响锁的作用范围,其实就是括号中对象的生命周期

3.2 抢占锁的本质

抢占锁的本质就是如何实现互斥,那么必定有两个条件

  • 共享资源
  • 锁标记  可以假设 0代表无锁  1 代表有锁

也就是说,抢占锁,一定是共同要访问一个共享的资源,当一个线程先占有了这个资源,就变为有锁状态,阻塞其它的线程。

3.3 锁信息的存储

加了synchronized关键字,系统为何就能识别这是一个同步锁呢,其实,系统根据该关键字,保存了一些信息在作用的对象上。

MarkWord对象头

我们可以通过以下代码打印对象头的信息(不加synchronized和加synchronized的区别)

   public static void main(String[] args) {
        Object lock = new Object();
        System.out.println(VM.current().details());
        System.out.println(ClassLayout.parseInstance(lock).toPrintable());
    }

打印的信息如下

并发编程线程安全之同步锁Synchronized_第3张图片

结合下图可以看出,第一行的前8个字节的最后3位001 则为无锁状态

并发编程线程安全之同步锁Synchronized_第4张图片

四、Synchronized锁升级

1. 无锁状态

2. 偏向锁: 假设没有线程竞争的时候,A线程进入到同步代码 就会偏向A线程 有线程竞争的时候 线程B再进来 就会做升级 轻量级锁

3. 轻量级锁   主要作用是避免线程阻塞   采用自旋锁的方式

4 .重量级锁   表示的是用户态到内核态的交换  没有获得锁的线程会阻塞,再被唤醒

思考: 线程A已经抢占到了锁,当线程B来竞争的时候,如果是重量级锁,则线程A执行完还需要唤醒线程B, 比较消耗性能,那么有没有一种办法可以避免或者优化这种阻塞呢?于是就引入了轻量级锁,线程B会不断的去重试,如果此时正好线程A结束了,那么线程B就可以执行了,不需要再去唤醒了。举个例子,就比如你去找老王,会先敲几下门,然后如果门没开,则会再去一边等着,等着老王来唤醒。

并发编程线程安全之同步锁Synchronized_第5张图片

分析: 当有两个线程的时候,线程A先进入了,则线程B抢占锁的时候,则会先进行自旋,如果抢占到了,则修改lock flag的标记,使用CAS机制保证操作的原子性

五、CAS机制

old: ThreadA

expect: ThreadB

update: ThreadC

CAS机制与乐观锁类似

CAS机制其底层是C++代码,采用了lock指令

举例:

当前线程A获得了偏向锁,线程B来抢占偏向锁

线程B就会来调用CAS,把偏向锁的指针指向自己

CAS(object,线程A的指针,线程B的指针(带更新的值))

并发编程线程安全之同步锁Synchronized_第6张图片

你可能感兴趣的:(java)