0
点赞
收藏
分享

微信扫一扫

Java synchronized关键字

synchronized是什么?

synchronized是Java的一个关键字,synchronized依赖于JVM具体实现。

synchronized的作用

synchronized也称为同步锁。synchronized是一种独占锁。

synchronized的两大功能:

  • 内存可见性
  • 操作原子性

什么是内存可见性?

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

可参考《Java内存分级和指令重排序》。volatile也可实现内存可见性。

什么是操作原子性?

所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换 (context switch: 换到另一个线程)。

原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱(禁止重排序),也不可以被切割而只执行其中的一部分。

将整个操作视作一个整体是原子性的核心特征。

synchronized什么时候释放锁?

  • 出现异常或代码块执行结束,自动释放锁。
  • 因为异常导致线程结束,同步锁释放,引起等待线程执行了,发生程序乱入,出现意想不到的效果。


/**
 * 测试synchronized出现异常释放锁
 *
 * @author zhouronghua
 * @time 2021/6/28 5:03 下午
 */
public class TestSyncException {
    /**
     * 计算器
     */
    private int count = 0;
 
    /**
     * 测试synchronized异常
     *
     * @author zhouronghua
     * @time 2021/6/28 5:18 下午
     */
    private synchronized void test() {
        System.out.println(Thread.currentThread().getName() + " test start...");
        count++;
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (count == 5) {
            // 产生一个Exception
            System.out.println(Thread.currentThread().getName() + "测试异常");
            int i = 5 / 0;
        }
        System.out.println(Thread.currentThread().getName() + " test end");
    }
 
    public static void main(String[] args) {
        TestSyncException testSyncThread = new TestSyncException();
        for (int i = 0; i < 10; i++) {
            new Thread(testSyncThread::test, "thread " + (i + 1)).start();
        }
    }
}


当count=5发生异常,当前线程结束,释放锁。后续等待队列的线程, 检测到同步锁释放,


开始执行。


thread 1 test start...
thread 1 test end
thread 10 test start...
thread 10 test end
thread 9 test start...
thread 9 test end
thread 8 test start...
thread 8 test end
thread 7 test start...
thread 7测试异常
thread 6 test start...
Exception in thread "thread 7" java.lang.ArithmeticException: / by zero
	at com.joe.helloapp.thread.TestSyncException.test(TestSyncException.java:32)
	at java.lang.Thread.run(Thread.java:748)
thread 6 test end
thread 5 test start...
thread 5 test end
thread 4 test start...
thread 4 test end
thread 3 test start...
thread 3 test end
thread 2 test start...
thread 2 test end


synchronized实现


字节码层级,synchronized是通过monitorenter和monitorexit指令实现的.


例如单例模式获取单例对象,

Java synchronized关键字_线程安全



其中同步代码块,对应的字节码:

MONITORENTER
   L0
    LINENUMBER 18 L0
    GETSTATIC com/joe/helloapp/Sington.sInstance : Lcom/joe/helloapp/Sington;
    IFNONNULL L7
   L8
    LINENUMBER 20 L8
    NEW com/joe/helloapp/Sington
    DUP
    INVOKESPECIAL com/joe/helloapp/Sington.<init> ()V
    PUTSTATIC com/joe/helloapp/Sington.sInstance : Lcom/joe/helloapp/Sington;
   L7
    LINENUMBER 22 L7
   FRAME APPEND [java/lang/Object]
    ALOAD 0
    MONITOREXIT


根据虚拟机规范的要求,在执行monitorenter指令时,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1;相应地,在执行monitorexit指令时会将锁计数器减1,当计数器被减到0时,锁就释放了。如果获取对象锁失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。


synchronized锁定的是对象,而不是代码块。


synchronized是可重入锁


可重入锁是指同一线程可以多次获得同一个锁。


若一个程序或子程序可以“在任意时刻被中断然后操作系统调度执行另外一段代码,这段代码又调用了该子程序不会出错”,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,执行线程可以再次进入并执行它,仍然获得符合设计时预期的结果。与多线程并发执行的线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然是安全的。


同一线程在调用自己类中其他synchronized方法/块或调用父类的synchronized方法/块都不会阻碍该线程的执行,就是说同一线程对同一个对象锁是可重入的。


验证


package com.joe.helloapp.thread;
 
/**
 * 测试synchronized可重入锁
 *
 * @author zhouronghua
 * @time 2021/6/28 11:27 上午
 */
public class TestSyncReentrantLock {
 
    /**
     * 线程任务
     * 说明:此处synchronized,锁的对象是this对象
     *
     * @author zhouronghua
     * @time 2021/6/25 9:05 上午
     */
    public synchronized void fun1() {
        System.out.println(Thread.currentThread().getName() + " fun1 start...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        /**
         * 调用synchronized方法。因为synchronized是可重入锁,
         * 同一线程再次访问synchronized方法,可重入,执行fun2。
         */
        fun2();
        System.out.println(Thread.currentThread().getName() + " fun1 end");
    }
 
    /**
     * 线程任务2
     *
     * @author zhouronghua
     * @time 2021/6/25 9:05 上午
     */
    public synchronized void fun2() {
        System.out.println(Thread.currentThread().getName() + " fun2 start...");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " fun2 end");
    }
 
    /**
     * main入口
     *
     * @param args 参数列表
     * @author zhouronghua
     * @time 2021/6/28 11:28 上午
     */
    public static void main(String[] args) {
        TestSyncReentrantLock testSyncReentrantLock = new TestSyncReentrantLock();
        new Thread(testSyncReentrantLock::fun1, "thread 1").start();
    }
}


  • 1)进入fun1,synchronized对this加锁,对应的线程为thread1;
  • 2)fun1中调用synchronized方法后调用fun2,需要用synchronized对this加锁,此时fun1中已经对this加锁了,且此时访问的线程为thread1,是同一线程,满足可重入;
  • 3)fun2执行完成,回到fun1继续执行


thread 1 fun1 start...
thread 1 fun2 start...
thread 1 fun2 end
thread 1 fun1 end

synchronized使用


synchronized修饰方法


synchronized修饰方法相当于对整个方法synchronized(this)处理,相当于this对象加锁。


测试多线程访问synchronized方法


/**
 * 测试线程同步
 *
 * @author zhouronghua
 * @time 2021/6/25 8:57 上午
 */
public class TestSyncThread {
 
    /**
     * 线程任务
     * 说明:此处synchronized,锁的对象是this对象
     *
     * @author zhouronghua
     * @time 2021/6/25 9:05 上午
     */
    public synchronized void fun1() {
        System.out.println(Thread.currentThread().getName() + " fun1 start...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " fun1 end");
    }
 
 
    /**
     * 测试多线程访问同一个方法
     *
     * @author zhouronghua
     * @time 2021/6/25 6:15 下午
     */
    private static void testSyncLockMethod() {
        TestSyncThread testSyncThread = new TestSyncThread();
        for (int i = 0; i < 10; i++) {
            new Thread(testSyncThread::fun1, "thread " + (i + 1)).start();
        }
    }
 
    public static void main(String[] args) {
        testSyncLockMethod();
    }
}


1)线程1执行fun1;


2)线程2执行fun1,检测到已经加锁,进入任务队列等待;线程3执行fun1,检测已经加锁,如在等待队列队首;线程4~线程10也如此。


3)线程1执行完,释放锁,开始执行任务对列表中的等待任务线程10;


线程10执行完成,执行等待任务线程9;。。。


thread 1 fun1 start...

thread 1 fun1 end

thread 10 fun1 start...

thread 10 fun1 end

thread 9 fun1 start...

thread 9 fun1 end

thread 8 fun1 start...

thread 8 fun1 end

thread 7 fun1 start...

thread 7 fun1 end

thread 6 fun1 start...

thread 6 fun1 end

thread 5 fun1 start...

thread 5 fun1 end

thread 4 fun1 start...

thread 4 fun1 end

thread 3 fun1 start...

thread 3 fun1 end

thread 2 fun1 start...

thread 2 fun1 end


synchronized修饰静态方法


synchronized修饰方法相当于对整个方法synchronized(T.class)处理,相当于类的class对象加锁。


因为静态方法是锁定的class对象,


/**
 * 测试synchronized静态方法
 * 说明: synchronized静态方法相当于对使用到TestSyncStaticThread.class对象同步
 *
 * @author zhouronghua
 * @time 2021/6/28 2:08 下午
 */
public class TestSyncStaticThread implements Runnable {
   /**
    * 测试多线程访问同一个synchronized静态方法
    *
    * @author zhouronghua
    * @time 2021/6/28 2:05 下午
    */
   private static void testStaticSyncLockMethod() {
      for (int i = 0; i < 10; i++) {
         new Thread(new TestSyncStaticThread(), "thread " + (i + 1)).start();
      }
   }
 
    @Override
    public void run() {
        fun2();
    }
 
   /**
    * 线程任务2
    *
    * @author zhouronghua
    * @time 2021/6/25 9:05 上午
    */
   public synchronized static void fun2() {
         System.out.println(Thread.currentThread().getName() + " fun2 start...");
         try {
            Thread.sleep(500);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
         System.out.println(Thread.currentThread().getName() + " fun2 end");
   }
 
   public static void main(String[] args) {
      testStaticSyncLockMethod();
   }
}


虽然线程使用的是不同的TestSyncStaticThread对象,静态同步方法锁定的是TestSyncStaticThread.class对象,而TestSyncStaticThread.class对象是唯一的。因此这些线程需要同步执行。


thread 1 fun2 start...
thread 1 fun2 end
thread 10 fun2 start...
thread 10 fun2 end
thread 9 fun2 start...
thread 9 fun2 end
thread 8 fun2 start...
thread 8 fun2 end
thread 7 fun2 start...
thread 7 fun2 end
thread 6 fun2 start...
thread 6 fun2 end
thread 5 fun2 start...
thread 5 fun2 end
thread 4 fun2 start...
thread 4 fun2 end
thread 3 fun2 start...
thread 3 fun2 end
thread 2 fun2 start...
thread 2 fun2 end


3. synchronized锁定代码块


对代码块部分进行加锁处理。


synchronized锁定对象


synchronized选取的锁定对象,不能是String常量,Long和Integer。


synchronized锁升级过程


偏向锁--》自旋锁--》重量级锁


什么时候偏向使用自旋锁


自旋锁:通过CAS处理,占用CPU时间。线程数比较少的时候,操作时间端。


重量级锁:通过等待队列。线程比较多的时候,操作消耗时间长,使用重量级锁。


举报

相关推荐

0 条评论