一. Synchronized的用法
Synchronized是Java里的一个关键字。
1. 可以用来修饰方法。那么同时只能有一个线程进入某个对象的这个方法。
public synchronized void syncMethod() {}
例如以下这个方法中,两个线程就是互斥的。第二个线程需要等第一个线程执行结束。
package demo.multithread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SyncLock {
public static void main(String[] args) {
Person p1 = new Person(20);
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(new Runnable() {
@Override
public void run() {
p1.testSync();
}});
pool.submit(new Runnable() {
@Override
public void run() {
p1.testSync();
}});
pool.shutdown();
}
}
class Person {
public Integer age;
Person(Integer age) {
this.setAge(age);
}
public synchronized void testSync() {
System.out.println("step into testSync");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("step out testSync");
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
2. 修饰静态方法。那么同时只能有一个线程进入这个class的此方法。
public synchronized static void syncMethod() {}
例如下面这个例子中,同一个class的两个对象。两个线程同时,各自访问一个对象的同步方法,是互斥的。
package demo.multithread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SyncLock {
public static void main(String[] args) {
Person p1 = new Person(20);
Person p2 = new Person(30);
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(new Runnable() {
@Override
public void run() {
p1.testSync();
}});
pool.submit(new Runnable() {
@Override
public void run() {
p2.testSync();
}});
pool.shutdown();
}
}
class Person {
public Integer age;
Person(Integer age) {
this.setAge(age);
}
public synchronized static void testSync() {
System.out.println("step into testSync");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("step out testSync");
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
3. 修饰代码块,synchronized后面括号里是一个变量。那么同时只能有一个线程获取这个变量。
synchronized(a1) {}
例如下面这个例子,两个线程在竞争同一个变量p1.age
package demo.multithread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SyncLock {
public static void main(String[] args) {
Person p1 = new Person(20);
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(new Runnable() {
@Override
public void run() {
synchronized(p1.age) {
System.out.println("read age!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("complete!");
}
}});
pool.submit(new Runnable() {
@Override
public void run() {
//p2.testSync();
synchronized(p1.age) {
System.out.println("read age!");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("complete!");
}
}});
pool.shutdown();
}
}
class Person {
public Integer age;
Person(Integer age) {
this.setAge(age);
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
4. 修饰代码块,synchronized后面括号里是this。那么一个线程访问一个对象中的该代码块时,其他线程会被阻塞。
synchronized(this) {}
在下面这个例子里,两个线程不能同时访问同一个对象的同步方法或者代码块。
package demo.multithread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SyncLock {
public static void main(String[] args) {
Person p1 = new Person(20);
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(new Runnable() {
@Override
public void run() {
p1.testSync();
}});
pool.submit(new Runnable() {
@Override
public void run() {
p1.testSync2();
}});
pool.shutdown();
}
}
class Person {
public Integer age;
Person(Integer age) {
this.setAge(age);
}
public void testSync() {
synchronized(this) {
System.out.println("step into testSync");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("step out testSync");
}
}
public synchronized void testSync2() {
System.out.println("step into testSync2");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("step out testSync2");
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
5. 修饰代码块,synchronized后面括号里是class:
synchronized(Person.class) {}
看这个例子,同一个class的不同对象,不能同时访问synchronized(Person.class)修饰的同步代码块。
package demo.multithread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SyncLock {
public static void main(String[] args) {
Person p1 = new Person(20);
Person p2 = new Person(30);
ExecutorService pool = Executors.newFixedThreadPool(5);
pool.submit(new Runnable() {
@Override
public void run() {
p1.testSync();
}});
pool.submit(new Runnable() {
@Override
public void run() {
p2.testSync2();
}});
pool.shutdown();
}
}
class Person {
public Integer age;
Person(Integer age) {
this.setAge(age);
}
public void testSync() {
synchronized(Person.class) {
System.out.println("step into testSync");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("step out testSync");
}
}
public void testSync2() {
synchronized(Person.class) {
System.out.println("step into testSync2");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("step out testSync2");
}
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
二. Synchronized的原理
synchronized是怎样实现这种同步的效果呢?
1. 首先,java class的每个对象都有个Monitor对象。
到http://hg.openjdk.java.net/jdk8/jdk8/hotspot/ 上下载JVM源码,目录src/share/vm/runtime里面有个objectMonitor class。这个monitor class里记录了,当前是哪个线程拥有该monitor,以及线程的重入次数。
// initialize the monitor, exception the semaphore, all other fields
// are simple integers or pointers
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0; // 线程的重入次数
_object = NULL; //
_owner = NULL; //标识拥有该monitor的线程
_WaitSet = NULL; //处于wait状态的线程,会被加到这里
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //处于等待锁,block状态的线程,加到这个list
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
2. 再来看看编译成后的class文件.
- 2.1 synchronized代码块
class文件中,synchronized被翻译成monitorenter, monitorexit. 当线程执行到monitorenter, 就会去看String对象name的锁,是否被其它线程占有。如果没有,那么当前线程就会持有name的锁,其他线程就不能再获取name的锁,进而不能同时执行同步代码块了。
当线程执行到monitorexit,就会释放持有的name的锁,那么其他线程就可以去竞争name的锁。
package demo.multithread;
public class Sync1 {
public static void main(String[] args) {
String name = "Tom";
synchronized(name) {
System.out.print(name + " hello");
}
}
}
$ javap -v Sync1.class
Classfile Sync1.class
Last modified 2021-5-15; size 959 bytes
MD5 checksum f62167984791ee72e37d981232b44217
Compiled from "Sync1.java"
public class demo.multithread.Sync1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // demo/multithread/Sync1
#2 = Utf8 demo/multithread/Sync1
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Methodref #3.#9 // java/lang/Object."<init>":()V
#9 = NameAndType #5:#6 // "<init>":()V
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Ldemo/multithread/Sync1;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = String #17 // Tom
#17 = Utf8 Tom
#18 = Fieldref #19.#21 // java/lang/System.out:Ljava/io/PrintStream;
#19 = Class #20 // java/lang/System
#20 = Utf8 java/lang/System
#21 = NameAndType #22:#23 // out:Ljava/io/PrintStream;
#22 = Utf8 out
#23 = Utf8 Ljava/io/PrintStream;
#24 = Class #25 // java/lang/StringBuilder
#25 = Utf8 java/lang/StringBuilder
#26 = Methodref #27.#29 // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
#27 = Class #28 // java/lang/String
#28 = Utf8 java/lang/String
#29 = NameAndType #30:#31 // valueOf:(Ljava/lang/Object;)Ljava/lang/String;
#30 = Utf8 valueOf
#31 = Utf8 (Ljava/lang/Object;)Ljava/lang/String;
#32 = Methodref #24.#33 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
#33 = NameAndType #5:#34 // "<init>":(Ljava/lang/String;)V
#34 = Utf8 (Ljava/lang/String;)V
#35 = String #36 // hello
#36 = Utf8 hello
#37 = Methodref #24.#38 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#38 = NameAndType #39:#40 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#39 = Utf8 append
#40 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#41 = Methodref #24.#42 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#42 = NameAndType #43:#44 // toString:()Ljava/lang/String;
#43 = Utf8 toString
#44 = Utf8 ()Ljava/lang/String;
#45 = Methodref #46.#48 // java/io/PrintStream.print:(Ljava/lang/String;)V
#46 = Class #47 // java/io/PrintStream
#47 = Utf8 java/io/PrintStream
#48 = NameAndType #49:#34 // print:(Ljava/lang/String;)V
#49 = Utf8 print
#50 = Utf8 args
#51 = Utf8 [Ljava/lang/String;
#52 = Utf8 name
#53 = Utf8 Ljava/lang/String;
#54 = Utf8 StackMapTable
#55 = Class #51 // "[Ljava/lang/String;"
#56 = Class #57 // java/lang/Throwable
#57 = Utf8 java/lang/Throwable
#58 = Utf8 SourceFile
#59 = Utf8 Sync1.java
{
public demo.multithread.Sync1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldemo/multithread/Sync1;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=1
0: ldc #16 // String Tom
2: astore_1
3: aload_1
4: dup
5: astore_2
6: monitorenter
7: getstatic #18 // Field java/lang/System.out:Ljava/io/PrintStream;
10: new #24 // class java/lang/StringBuilder
13: dup
14: aload_1
15: invokestatic #26 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
18: invokespecial #32 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
21: ldc #35 // String hello
23: invokevirtual #37 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
26: invokevirtual #41 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: invokevirtual #45 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
32: aload_2
33: monitorexit
34: goto 40
37: aload_2
38: monitorexit
39: athrow
40: return
Exception table:
from to target type
7 34 37 any
37 39 37 any
LineNumberTable:
line 5: 0
line 6: 3
line 7: 7
line 6: 32
line 9: 40
LocalVariableTable:
Start Length Slot Name Signature
0 41 0 args [Ljava/lang/String;
3 38 1 name Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 37
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 2
}
SourceFile: "Sync1.java"
- 2.2 synchronized方法
synchronized修饰方法时,在class文件中,方法上有flags: ACC_PUBLIC, ACC_SYNCHRONIZED。当方法被调用时,会检查标志位ACC_SYNCHRONIZED是否被设置,如果被设置了,就会去执行monitorenter, monitorexit这一套操作。
package demo.multithread;
public class Sync2 {
public synchronized void testSync2() {
System.out.println("helloworld");
}
}
$ javap -v Sync2.class
Classfile Sync2.class
Last modified 2021-5-15; size 411 bytes
MD5 checksum d73c40e82c4502c5d72c37692168a702
Compiled from "Sync2.java"
public class demo.multithread.Sync2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#14 // java/lang/Object."<init>":()V
#2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #17 // helloworld
#4 = Methodref #18.#19 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #20 // demo/multithread/Sync2
#6 = Class #21 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 testSync2
#12 = Utf8 SourceFile
#13 = Utf8 Sync2.java
#14 = NameAndType #7:#8 // "<init>":()V
#15 = Class #22 // java/lang/System
#16 = NameAndType #23:#24 // out:Ljava/io/PrintStream;
#17 = Utf8 helloworld
#18 = Class #25 // java/io/PrintStream
#19 = NameAndType #26:#27 // println:(Ljava/lang/String;)V
#20 = Utf8 demo/multithread/Sync2
#21 = Utf8 java/lang/Object
#22 = Utf8 java/lang/System
#23 = Utf8 out
#24 = Utf8 Ljava/io/PrintStream;
#25 = Utf8 java/io/PrintStream
#26 = Utf8 println
#27 = Utf8 (Ljava/lang/String;)V
{
public demo.multithread.Sync2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public synchronized void testSync2();
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 helloworld
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
}
SourceFile: "Sync2.java"
- 2.3 synchrnozed静态方法
synchronized修饰静态方法时,方法上有flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED。当方法被调用时,会检查标识位ACC_STATIC, ACC_SYNCHRONIZED是否被设置。如果被设置,就会对class对象进行monitorenter, monitorexit操作。
package demo.multithread;
public class Sync3 {
public synchronized static void testSync2() {
System.out.println("helloworld");
}
}
$ javap -v Sync3.class
Classfile Sync3.class
Last modified 2021-5-15; size 411 bytes
MD5 checksum 546d7d535763ab592ccc73b012ccbc03
Compiled from "Sync3.java"
public class demo.multithread.Sync3
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#14 // java/lang/Object."<init>":()V
#2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #17 // helloworld
#4 = Methodref #18.#19 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #20 // demo/multithread/Sync3
#6 = Class #21 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 testSync2
#12 = Utf8 SourceFile
#13 = Utf8 Sync3.java
#14 = NameAndType #7:#8 // "<init>":()V
#15 = Class #22 // java/lang/System
#16 = NameAndType #23:#24 // out:Ljava/io/PrintStream;
#17 = Utf8 helloworld
#18 = Class #25 // java/io/PrintStream
#19 = NameAndType #26:#27 // println:(Ljava/lang/String;)V
#20 = Utf8 demo/multithread/Sync3
#21 = Utf8 java/lang/Object
#22 = Utf8 java/lang/System
#23 = Utf8 out
#24 = Utf8 Ljava/io/PrintStream;
#25 = Utf8 java/io/PrintStream
#26 = Utf8 println
#27 = Utf8 (Ljava/lang/String;)V
{
public demo.multithread.Sync3();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public static synchronized void testSync2();
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 helloworld
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 6: 0
line 7: 8
}
SourceFile: "Sync3.java"
三. Synchronized与lock的区别
-
synchronized阻塞的线程不能被中断,一直被阻塞导致效率低;而lock里可以使用trylock,让线程获取不到锁,就去干别的事情。
-
synchronized会自动获取锁及释放锁,而lock需要手动去lock及unlock。
网友评论