代码如下图所示,代码逻辑主要是启动多个线程,对同一个实例的成员变量进行同步和操作:
public class SynTest {
private Integer obj = 1;
/**
* @param args
*/
public static void main(String[] args) {
final SynTest synTest = new SynTest();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
synTest.add();
}
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void add() {
synchronized (obj) {
System.out.println(Thread.currentThread());
obj++;
System.out.println(Thread.currentThread() + "===" + obj);
obj++;
System.out.println(Thread.currentThread() + "-" + obj);
obj++;
System.out.println(Thread.currentThread());
}
}
}
运行结果如下:
我的问题点主要是:
1. 为什么单个线程的操作不是连续的?我理解只有一个线程获取到锁,然后进行操作并输出结果,那么输出结果应该是一个线程输出完,另一个线程才会继续输出
2.JVM会对同步的代码块进行同步消除优化?实际的代码应该是和下图所示?
1. 为什么单线程操作不是连续的,这是因为虽然你使用了 synchronized (obj) {} 来尝试获取排它锁,但是其他线程把obj修改了,比如从1修改到2 ,又因为Integer使用了享元模式,1 和 2 不是同一个对象,所以你的每个线程可能锁的不是同一个对象,所以也就无法按照你的需求输出,将obj修改为final常量后,各个线程锁定的肯定是同一个对象了,也就可看到连续输出了。
2. 第二个问题不太熟悉,直接使用 javap -c synTest.class 查看字节码如下,因为 synchronized 进入监视器的字节码指令是 monitorenter ,退出监视器的字节码命令是monitorexit,而这两个命令仅仅出现一次分别是第6行指令和106行指令,所以并没有同步消除优化
public void add();
Code:
0: aload_0
1: getfield #3 // Field obj:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
10: invokestatic #16 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
13: invokevirtual #17 // Method java/lang/Thread.getName:()Ljava/lang/String;
16: invokevirtual #18 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
19: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
22: new #19 // class java/lang/StringBuilder
25: dup
26: invokespecial #20 // Method java/lang/StringBuilder."<init>":()V
29: invokestatic #16 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
32: invokevirtual #17 // Method java/lang/Thread.getName:()Ljava/lang/String;
35: invokevirtual #21 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
38: ldc #22 // String ===
40: invokevirtual #21 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: aload_0
44: getfield #3 // Field obj:Ljava/lang/Object;
47: invokevirtual #23 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
50: invokevirtual #24 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
53: invokevirtual #18 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
56: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
59: new #19 // class java/lang/StringBuilder
62: dup
63: invokespecial #20 // Method java/lang/StringBuilder."<init>":()V
66: invokestatic #16 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
69: invokevirtual #17 // Method java/lang/Thread.getName:()Ljava/lang/String;
72: invokevirtual #21 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
75: ldc #25 // String -
77: invokevirtual #21 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
80: aload_0
81: getfield #3 // Field obj:Ljava/lang/Object;
84: invokevirtual #23 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
87: invokevirtual #24 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
90: invokevirtual #18 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
93: getstatic #15 // Field java/lang/System.out:Ljava/io/PrintStream;
96: invokestatic #16 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
99: invokevirtual #17 // Method java/lang/Thread.getName:()Ljava/lang/String;
102: invokevirtual #18 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
105: aload_1
106: monitorexit
107: goto 115
110: astore_2
111: aload_1
112: monitorexit
113: aload_2
更多Java并发编程的学习笔记欢迎访问 https://www.zhoutao123.com/page/book/11
第一个问题:前面有人已经回答了,你这里Integer对象++之后,和之前不是同一个对象了,简单验证如下:
执行i++之前:
执行i++之后:
第二个问题:没太明白你说的意思,不过锁消除和锁粗化的意思大概是:
锁消除:经过逃逸分析技术,如果发现不会出现多线程竞争,那么可能进行锁消除优化,典型的是对非共享变量(未逃逸)的操作进行加锁,比如:
public void test() {
Test test = new Test();
synchronized (test) {
test.xxx();
}
}
这个test变量存在局部变量表,随栈帧出栈就销毁了,不会出现多线程竞争,没有方法逃逸,更没有线程逃逸,这里的加锁就可以消除
锁粗化:其实在你举的这个例子中才应该进行粗化处理:
先不考虑偏向锁和轻量级锁,这个代码块频繁的加锁-解锁更加影响性能,还不如一个synchronized把和这个代码块都包起来
synchronized锁定的是integer对象实例的引用地址,integer++之后已经指向了另外的地址,锁定的已经不是同一个对象引用了,可以使用一个单独的锁定对象,避免对象地址修改
synchronized (obj) 改为 synchronized (this),详细的解释可以看看这篇文章 http://tutorials.jenkov.com/java-concurrency/synchronized.html
做个总结:
第一个问题的确是因为Integer、Long等这些封装类型进行自增后,引用地址发生变化,多线程同步时实际锁的是不同对象,最后实现同步效果
第二个问题不重要,是我理解有误,以为第一个问题的结果是因为JVM进行了重排序