JUC并发相关

6. 并发终结之synchronized

2020-09-22  本文已影响0人  涣涣虚心0215

锁的简介

锁能够保护共享数据以实现线程安全,其作用包括保障原子性、保障可见性和保障有序性。

b = a + 1
c = 2
flag = true

一个读线程在临界区内能够看到c = 2,那么flag必然是true,b的值必然是比a大1,尽管过能保证有序性,但不保证临界区内不会发生重排序,只能保证临界区内的操作不会被重排序到临界区之外,临界区内的操作还是可以重排序。
锁的原子性和可见性结合,可以保证临界区内的代码能够读到共享数据的最新值,且对引用性共享变量,锁还可以保障临界区代码能够读取到该变量所引用对象字段(实例变量和类变量)的最新值(当然前提是对共享变量的访问都是在临界区,即锁内部)。
Java内部锁是通过synchronized关键字实现的,synchronized修饰的普通方法称为同步方法,修饰静态方法就称为静态同步方法,同步方法就是一个临界区。

Synchronized修饰方法或者修饰代码块有什么不同

修饰代码块,通过monitorenter和monitorexit来控制。
修饰方法,则会有ACC_SYNCHRONIZED的flag来进行控制。
修饰静态方法,则会有ACC_STATIC,ACC_SYNCHRONIZED的flag来进行控制。

修饰静态方法,方法内部没有monitorenter和monitorexit,只有ACC_STATIC, ACC_SYNCHRONIZED的flag标识
{
  public static synchronized void method();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
}
===========
修饰方法同样也没有monitorenter和monitorexit,而是通过ACC_SYNCHRONIZED来标识
  public synchronized void method();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lio/github/viscent/Test;


为什么称为内部锁

因为对锁的申请和释放都是有JVM负责的,且注意内部锁的使用并不会导致锁泄漏,因为就算临界区代码出现异常,内部锁仍然能释放(如下图所示)。

public class Test {
    public void method(){
        synchronized (Test.class){
            System.out.println("hello world");
        }
    }
}
------------
public class io.github.guangping0215.Test {
  public io.github.guangping0215.Test();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void method();
    Code:
       0: ldc           #2                  // class io/github/guangping0215/Test
       2: dup
       3: astore_1
       4: monitorenter      //=============获得锁
       5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       8: ldc           #4                  // String hello world
      10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      13: aload_1
      14: monitorexit      //===========正常结束释放锁
      15: goto          23
      18: astore_2
      19: aload_1
      20: monitorexit      //===========抛出异常时释放锁
      21: aload_2
      22: athrow
      23: return
    Exception table:
       from    to  target type
           5    15    18   any
          18    21    18   any
}

内部锁的调度

JVM会为每个内部锁分配一个入口集(EntrySet),用于记录等待获得内部锁的线程。多个线程在申请同一个锁的时候,只有一个申请者能够持有该锁,其他线程申请锁失败,这些线程会进入BLOCKED状态,并被存入相应内部锁的EntrySet里,称为等待线程。那些持有锁的线程释放锁之后,该锁的EntrySet里面的任意一个等待线程会被JVM唤醒,从而获得再次申请锁的机会。且内部锁的调度策略是非公平策略,则该唤醒的线程不一定能获得锁,可能还会跟其他活跃线程一起再次争抢锁。

Synchronized总结

1.监视器对象
private static final Object lock = new Object();
Synchronized(object)
如果new一个Object来当监视器,那么需要注意的是最好写成static final的,如果这个object重新指向另一个对象,那么这个Synchronized就变成监视另一个对象,则之前的锁跟这个锁就不会互斥。
Synchronized不是锁代码块,而是锁对象,要么是this对象,要么是Class对象;Synchronized写在方法上或者Synchronized(this)锁定的是this对象;
Synchronized写在静态方法上或者Synchronized(this.class)锁定的是Class对象。
2.线程八锁
八种情况(打印one or two):
1).两个普通同步方法 . // one two
2).新增Thread.sleep()给getOne(). // one two
3).新增普通方法getThree(). // Three one two 同步和普通方法不存在竞态条件
4).两个普通同步方法,两个两个Number对象 .// two one
5).修改getOne()为静态同步方法,一个方法为同步,一个Number对象 . // two one 静态同步和普通同步不存在竞态条件
6).修改两个方法都为静态同步方法,一个Number对象. // one two
7).一个方法为静态同步方法,一个方法为同步,两个Number对象 // two one
8).修改两个方法都为静态同步方法,两个Number对象 //one two, 因为是对class加锁
非静态同步方法的锁默认是this对象(类似于方法里写synchronize(this),静态方法的锁为对应的Class实例,类对象本身)

上一篇 下一篇

猜你喜欢

热点阅读