一、简述

1.1 synchronized介绍

synchronized是一种互斥锁,也成为同步锁,它的作用是保证在同一时刻,被修饰的代码块或方法只会有一个线程执行,以到达保证并发安全效果。在JDK1.6以前,很多人称之为重量级锁,性能不高。但是在JDK1.6以后,对sychronized进行了一些优化,引入了偏向锁,轻量级锁,以及重量级锁。这个时候,synchrionized会根据线程的竞争程度对锁进行升级和降级。

二、使用

2.1 synchronized使用重要性

容易引发线程安全

 比如下面代码,加了synchronized和没有加synchronized有明显的区别

public class ThreadTestByPool {

    //获取CPU个数
    private static int cpuCount = Runtime.getRuntime().availableProcessors();
    private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(cpuCount, cpuCount * 2, 20,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>(20000));
    static {
        System.out.println("我的cpu颗数: " + cpuCount);
    }
    //成员变量,可被多个线程 共享
    private static long n = 0L;

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10000);
        for (int i = 0; i < 10000; i++) {
            threadPool.execute(() -> {
                try {
                    //synchronized (ThreadTestByPool.class) {
                        n++;
                    //}
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        //等待所有线程执行完毕以后再打印
        countDownLatch.await();
        System.out.println("n++的结果集为:" + n);
    }

}

如果没有加synchronized,三次打印效果

每次执行的结果集都会不一样,但是执行的结果都是不正确的

去掉上面synchronized注释,加了synchronized以后执行,无论执行了几次,返回的n值都是正确的

2.2 synchronized使用

2.2.1 synchronized三种使用方式

  • 修饰实例方法:作用相当于给实例加锁
  • 修饰静态方法:作用相当于给对象加锁
  • 修饰代码块:指定加锁对象
public class ThreadTest {

    //给方法加锁
    public synchronized  void test(){

    }
    //给静态方法加锁
    public static synchronized  void staticTest(){

    }

    public void test2(){
        // 代码块
        synchronized (this){

        }
        synchronized (ThreadTest.class){

        }
    }

}

注意点:

  • synchronized关键字不能被继承
  • 定义接口方法不能使用synchronized
  • 构造方法不能使用synchronized

2.2.2 案例

  ThreadPoolUtil线程池工具地址:线程池工具类_java-zh的博客-CSDN博客

public class SyncTest {
    //获取CPU个数
    private static int cpuCount = Runtime.getRuntime().availableProcessors();

    static {
        System.out.println("我的cpu颗数: " + cpuCount);
    }

    //成员变量,可被多个线程 共享
    private static  long a = 0L;
    private static long b = 0L;
    private static long c = 0L;

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10000);
        SyncTest lockobj = new SyncTest();
        for (int i = 0; i < 10000; i++) {
            ThreadPoolUtils.execute(() -> {
                try {
                    lockobj.sync(countDownLatch);
                    sync2(countDownLatch);
                    lockobj.sync3(countDownLatch);
                }finally {
                    countDownLatch.countDown();
                }
            });
        }
        // 等待所有的线程执行完再打印
        countDownLatch.await();
        System.out.println("a=" + a);
        System.out.println("b=" + b);
        System.out.println("c=" + c);
    }

    /**
     * 修饰生源方法,其使用的锁对应时当前类所在的实例对象(或者说当前方法的调用对象),也就是this
     *
     * @param countDownLatch
     */
    private synchronized void sync(CountDownLatch countDownLatch) {
        try {
            a++;
        } finally {
           // countDownLatch.countDown();
        }
    }

    /**
     * 修饰静态方法,使用的锁对象是当前类
     *
     * @param countDownLatch
     */
    private synchronized static void sync2(CountDownLatch countDownLatch) {
        try {
            b++;
        }  finally {
            //countDownLatch.countDown();
        }
    }

    private void sync3(CountDownLatch countDownLatch) {
        try {
            synchronized (countDownLatch) {
                c++;
            }
        }  finally {
            //countDownLatch.countDown();
        }
    }

}

三、 synchronized锁住的真正资源 

参考文献:万字长文分析synchroized_wx6402d9406dac7的技术博客_51CTO博客

3.1 真正的资源ObjectMonitor

实际上,不管被标记的ACC_SYNCHRONIZED方法还是被插入的monitorenter/monitorexit指令的同步块,最终JVM底层都是一个逻辑。在进入该方法或同步块时,必须竞争到给定对象对应的ObjectMonitor上的资源(这里的竞争具体表现为:某个线程通过CAS操作来给定对象对应的ObjectMonitor上的owner指针指向自己,CAS操作成功,就代表获取锁成功,CAS失败,就代表未获取到锁)。同时,必须清楚一点,每个对象都有一个ObjectMonitor与之相对应,且唯一。

3.2 锁对象与ObjectMonitor的关系

  1. 执行monitorenter的线程试图获得与指定对象(synchronized块中的锁对象)关联的ObjectMonitor,每个对象都有一个监听器(ObjectMonitor)与之相关联
  2. 当且仅当监听器(ObjectMonitor)和某个对象产生关联时,ObjectMonitor标志已被锁定/持有

如图(下面的图为重量锁):

synchronized简单理解-LMLPHP

 解释:

  1. 尝试获取:当前程获取锁时,会进行cas操作,尝试将owner指针指向当前线程,如果获取成功,则进入同步块执行逻辑
  2. 获取失败后:从cxq队首插入,包装了当前线程的node
  3. 当持有锁的线程释放后:首先肯定的就是他将会owner置为null,好让出资源,然后会从EntryList(如果没有从cxq)队列中挑选一个线程抢锁,被选中的线程叫做Heir presumptive,即叫做"假定继承人","假定继承人"尝试获取锁,但synchronized是非公平的,所以"假定继承人"也不一定能获取锁(所以这也是叫"假定继承人"的原因)。
  4. 当持有锁的线程调用Object的wait方法后:则会将当前线程加入到WaitSet中
  5. 当被Object.notify/notifyAll方法唤醒以后:会将对应的线程从WaitSet移动到cxq或EntryList中去。需要注意的是,当调用一个锁对象的wait或notify/notifyAll方法时,如果当前锁的状态是偏向锁或轻量级锁则会先膨胀成重量级锁。

四、锁的升级

参考文献:并发编程笔记:synchronized关键字浅析_zhoutaoping1992的博客-CSDN博客

4.1 锁的状态

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

  • 无锁:在没有开启偏向锁或者偏向锁延迟未到或者批量撤销后,对象创建完成后,没有任何线程使用该对象来加锁,此时该对象状态是无锁。对应的Java对象头lock=1,biased_lock=0。(biased_lock=0的含义表示不可偏向)
  • 偏向锁:对象创建完成后处于可偏向状态,某一个对象使用该对象来加锁,那么这个对象就偏向这个线程。对应的java头lock=1,biased_lock=1。
  • 轻量级锁:当有多个线程交替使用某个对象来加锁,并无无竞争的情况下,该对象会成为轻量级锁。对应的Java对象头lock=00
  • 重量级锁:当有多个线程竞争使用某个对象来加锁,该对象会成为重量级锁。对应的JAV对象头lock=10

4.2 JVM相关参数

4.3 synchronized关键字执行流程

如图(不考虑重偏向和批量撤销的情况):

synchronized简单理解-LMLPHP

06-07 18:48