再看 synchronized

相关博客:

使用synchronized要注意的地方

先看这个示例:

package com.example.threaddesign;

/**
 * @author Dongguabai
 * @date 2018/12/4 17:33
 */
public class SynchronizedTest2 {

    private static final Object LOCK = new Object();

    public static void main(String[] args) {
        TTe task2 = new TTe();
        Thread t1 = new Thread(task2);
        Thread t2 = new Thread(task2);
        Thread t3 = new Thread(task2);
        t1.start();
        t2.start();
        t3.start();
    }
}
package com.example.threaddesign;

/**
 * @author Dongguabai
 * @date 2018/12/4 18:09
 */
public class TTe implements Runnable {

    private final Object MONITOR = new Object();

    @Override
    public void run() {

        while (true) {
            //1
            synchronized (MONITOR) {
                try {
                    Thread.sleep(1000_1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

通过 jconsole 可以看到线程的一些信息:

再看 synchronized_第1张图片

再看 synchronized_第2张图片

再看 synchronized_第3张图片

可以看出是 Thread-0 获得了锁。

再使用 jstack 看看:

"Thread-2" #13 prio=5 os_prio=0 tid=0x0000000019476800 nid=0x18ec waiting for monitor entry [0x000000001a03f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.example.threaddesign.SynchronizedTest2$Task2.run(SynchronizedTest2.java:27)
        - waiting to lock <0x00000000d61f5da8> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"Thread-1" #12 prio=5 os_prio=0 tid=0x0000000019475800 nid=0x3708 waiting for monitor entry [0x0000000019f3f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.example.threaddesign.SynchronizedTest2$Task2.run(SynchronizedTest2.java:27)
        - waiting to lock <0x00000000d61f5da8> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

"Thread-0" #11 prio=5 os_prio=0 tid=0x0000000019473000 nid=0x27f8 waiting on condition [0x0000000019e3e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.example.threaddesign.SynchronizedTest2$Task2.run(SynchronizedTest2.java:27)
        - locked <0x00000000d61f5da8> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:745)

也可以看出是 Thread-0 获得了锁,那个锁是一个 monitor。

看看  TTe 的汇编指令:

汇编指令为:

C:\Users\Dongguabai>cd H:\idea_home\demoClient\src\main\java\com\example\threaddesign

C:\Users\Dongguabai>h:

H:\idea_home\demoClient\src\main\java\com\example\threaddesign>javac TTe.class
javac: 无效的标记: TTe.class
用法: javac  
-help 用于列出可能的选项

H:\idea_home\demoClient\src\main\java\com\example\threaddesign>javac TTe.java

H:\idea_home\demoClient\src\main\java\com\example\threaddesign>javap -c TTe
警告: 二进制文件TTe包含com.example.threaddesign.TTe
Compiled from "TTe.java"
public class com.example.threaddesign.TTe implements java.lang.Runnable {
  public com.example.threaddesign.TTe();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: aload_0
       5: new           #2                  // class java/lang/Object
       8: dup
       9: invokespecial #1                  // Method java/lang/Object."":()V
      12: putfield      #3                  // Field MONITOR:Ljava/lang/Object;
      15: return

  public void run();
    Code:
       0: aload_0
       1: getfield      #3                  // Field MONITOR:Ljava/lang/Object;
       4: dup
       5: astore_1
       6: monitorenter
       7: ldc2_w        #4                  // long 10001000l
      10: invokestatic  #6                  // Method java/lang/Thread.sleep:(J)V
      13: goto          21
      16: astore_2
      17: aload_2
      18: invokevirtual #8                  // Method java/lang/InterruptedException.printStackTrace:()V
      21: aload_1
      22: monitorexit
      23: goto          31
      26: astore_3
      27: aload_1
      28: monitorexit
      29: aload_3
      30: athrow
      31: goto          0
    Exception table:
       from    to  target type
           7    13    16   Class java/lang/InterruptedException
           7    23    26   any
          26    29    26   any
}

可以很明显的看到  monitorenter 和 monitorexit 指令。而且 monitorenter 和 monitorexit 是成对出现的(有些时候会出现一个monitorenter 对应多个 monitorexit,但是每一个 monitorexit 之前必有对应的 monitorenter,这是肯定的)。

monitorenter

每个对象都与一个 monitor 相关联,一个 monitor 的 lock 的锁只能被一个线程在同一时间获得,在一个线程尝试获得与对象关联 monitor 的所有权时会发生如下的几件事情。

  • 如果 monitor 的计数器为 0,则意味着该 monitor 的 lock 还没有被获得,某个线程获得之后将立即对该计数器加一,从此该线程就是这个 monitor 的所有者了。
  • 如果一个已经拥有该 monitor 所有权的线程重入,则会导致 monitor 计数器再次累加。
  • 如果monitor已经被其他线程所拥有,则其他线程尝试获取该 monitor 的所有权时,会被陷人阻塞状态直到 monitor 计数器变为 0,才能再次尝试获取对 monitor 的所有权。

monitorexit

释放对 monitor 的所有权,想要释放对某个对象关联的 monitor 的所有权的前提是,你曾经获得了所有权。释放 monitor 所有权的过程比较简单,就是将 monitor 的计数器减一,如果计数器的结果为 0,那就意味着该线程不再拥有对该 monitor 的所有权,通俗地讲就是解锁。与此同时被该 monitor block 的线程将再次尝试获得对该 monitor 的所有权。

使用 synchronized 要注意的地方

对使用synchronized要注意的地方的补充。

monitor 的对象不能为空

这个很好理解,每一个对象和 monitor 关联,如果对象为 null,monitor 根本就无从谈起。一个简单的例子:

package com.example.threaddesign;

/**
 * @author Dongguabai
 * @date 2018/12/5 14:36
 */
public class SynchronizedTest3 {

    private static Object lock = null;

    public static void main(String[] args) {
        new Thread(()->{
            add();
        },"线程一").start();
        new Thread(()->{
            add();
        },"线程二").start();
        new Thread(()->{
            add();
        },"线程三").start();
    }

    public static void add(){
        synchronized (lock){
            System.out.println(Thread.currentThread().getName()+"-->进入了锁----");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-->出锁----");
        }
    }
}

运行结果:

Exception in thread "线程一" Exception in thread "线程二" java.lang.NullPointerException
	at com.example.threaddesign.SynchronizedTest3.add(SynchronizedTest3.java:24)
	at com.example.threaddesign.SynchronizedTest3.lambda$main$0(SynchronizedTest3.java:13)
	at java.lang.Thread.run(Thread.java:745)
Exception in thread "线程三" java.lang.NullPointerException
	at com.example.threaddesign.SynchronizedTest3.add(SynchronizedTest3.java:24)
	at com.example.threaddesign.SynchronizedTest3.lambda$main$1(SynchronizedTest3.java:16)
	at java.lang.Thread.run(Thread.java:745)
java.lang.NullPointerException
	at com.example.threaddesign.SynchronizedTest3.add(SynchronizedTest3.java:24)
	at com.example.threaddesign.SynchronizedTest3.lambda$main$2(SynchronizedTest3.java:19)
	at java.lang.Thread.run(Thread.java:745)

synchronized 范围过大

由于 synchronized 关键字存在排他性,也就是说所有的线程必须串行地经过 synchronized 保护的共享区域,如果 synchronized 作用域越大,则代表着其效率越低,甚至还会丧失并发的优势。

你可能感兴趣的:(再看 synchronized)