前言
在多线程开发中,我们经常看到synchronized(this)、synchronized(*.class)、synchronized(Object o)这些玩意。
它们都围绕synchronized关键字,用于同步方法或同步代码块,解决线程安全问题,保证在同一时刻最多只有一个线程执行某段代码。
一、synchronized(this)
/**
* synchronized测试
*/
public class ObjectService {
/**
* 方法A
*/
public void serviceMethodA(){
try {
synchronized (this) {
System.out.println(Thread.currentThread().getName()+" A begin time="+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+" A end time="+System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 方法B
*/
public void serviceMethodB(){
synchronized (this) {
System.out.println(Thread.currentThread().getName()+" B begin time="+System.currentTimeMillis());
System.out.println(Thread.currentThread().getName()+" B end time="+System.currentTimeMillis());
}
}
/**
* 方法C
*/
public void serviceMethodC(){
System.out.println(Thread.currentThread().getName()+" C begin time="+System.currentTimeMillis());
System.out.println(Thread.currentThread().getName()+" C end time="+System.currentTimeMillis());
}
}
/**
* 测试线程A
*/
public class ThreadA extends Thread {
private ObjectService objectService;
public ThreadA(ObjectService objectService){
//调用父类无参构造方法,完成线程初始化工作
super();
this.objectService=objectService;
}
@Override
public void run() {
//调用父类普通方法
super.run();
//执行方法A
objectService.serviceMethodA();
}
}
/**
* 测试线程B
*/
public class ThreadB extends Thread {
private ObjectService objectService;
public ThreadB(ObjectService objectService){
//调用父类无参构造方法,完成线程初始化工作
super();
this.objectService=objectService;
}
@Override
public void run() {
//调用父类普通方法
super.run();
//执行方法
//objectService.serviceMethodA();
objectService.serviceMethodB();
}
}
/**
* 测试线程C
*/
public class ThreadC extends Thread {
private ObjectService objectService;
public ThreadC(ObjectService objectService){
//调用父类无参构造方法,完成线程初始化工作
super();
this.objectService=objectService;
}
@Override
public void run() {
//调用父类普通方法
super.run();
//执行方法B
objectService.serviceMethodC();
}
}
public class MainTest {
public static void main(String[] args) {
ObjectService service=new ObjectService();
ThreadA a=new ThreadA(service);
a.setName("ThreadA");
a.start();
ThreadB b=new ThreadB(service);
b.setName("ThreadB");
b.start();
ThreadC c=new ThreadC(service);
c.setName("ThreadC");
c.start();
}
}
执行结果:
ThreadA A begin time=1622080816475
ThreadC C begin time=1622080816475
ThreadC C end time=1622080816475
ThreadA A end time=1622080818479
ThreadB B begin time=1622080818479
ThreadB B end time=1622080818479
分析可知:
-
当一个线程访问对象实例的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象实例中的非synchronized(this)同步代码块。(线程A执行时间段内,另一个线程同时进入了普通方法,这里的同时都是宏观上的)
-
当多个并发线程访问同一个对象实例中的某个synchronized(this)同步代码块时,同一时刻只能有一个线程进入。另一个线程必须等待当前线程执行完这个代码块以后才能执行。(线程B执行方法A的开始时间在线程A执行方法A的结束时间之后,说明synchronized(this)同步方法同一时刻只有一个线程可以访问)
-
当一个线程访问对象实例的一个synchronized(this)同步代码块时,其他线程对于对象实例中的所有其它synchronized(this)同步代码块的访问都将被阻塞,包括此时访问的这个synchronized(this)同步代码块。(线程A执行同步代码块时,其它线程可以穿插执行非同步代码块,其它线程必须顺序执行其它同步代码块)
-
synchronized (this) 锁的是当前对象实例,所有该对象实例中的同步方法都会被锁住。即某一时刻,只有一个线程能进入某个同步方法,锁住该方法,与此同时,其它所有synchronized (this)同步方法也会锁住。
二、静态synchronized同步方法
为了更清楚的测试静态同步方法锁类的效果,我们先测试一下锁某个实例对象的情况:
public class ObjectService {
/**
* 同步方法A
*/
public synchronized void methodA(Object o){
try {
System.out.println("static methodA begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("static methodA end 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 同步方法B
*/
public synchronized void methodB(Object o){
System.out.println("static methodB begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
System.out.println("static methodB end 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
}
}
/**
* 线程A
*/
public class ThreadA extends Thread {
@Override
public void run() {
ObjectService objectService = new ObjectService();
objectService.methodA(objectService);
}
}
/**
* 线程B
*/
public class ThreadB extends Thread {
@Override
public void run() {
ObjectService objectService = new ObjectService();
objectService.methodA(objectService);
}
}
public class MainTest {
public static void main(String[] args) {
ThreadA a=new ThreadA();
a.setName("A");
a.start();
ThreadB b=new ThreadB();
b.setName("B");
b.start();
}
}
执行结果:
static methodA begin 线程名称:A time:1622085729602
static methodA begin 线程名称:B time:1622085729602
static methodA end 线程名称:B time:1622085731615
static methodA end 线程名称:A time:1622085731615
分析可知:
Synchronized(Object o)传入不同的实例对象,锁住的是不同的实例。多个线程对于同一个实例对象的同步方法会阻塞,对于不同实例对象的同步方法可以同时执行。
修改代码,线程A和B都访问同步方法A:
public class ThreadB extends Thread {
@Override
public void run() {
ObjectService.methodA();
}
}
执行结果:
static methodA begin 线程名称:A time:1622092770910
static methodA end 线程名称:A time:1622092772920
static methodA begin 线程名称:B time:1622092772920
static methodA end 线程名称:B time:1622092774928
public class ObjectService {
/**
* 静态同步方法A
*/
public synchronized static void methodA(){
try {
System.out.println("static methodA begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("static methodA end 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 静态同步方法B
*/
public synchronized static void methodB(){
System.out.println("static methodB begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("static methodB end 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
}
public void methodC(){
System.out.println("static methodC begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
System.out.println("static methodC end 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
}
public static void methodD(){
System.out.println("static methodD begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
System.out.println("static methodD end 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
}
}
/**
* 线程A
*/
public class ThreadA extends Thread {
@Override
public void run() {
new ObjectService().methodA();
}
}
/**
* 线程B
*/
public class ThreadB extends Thread {
@Override
public void run() {
new ObjectService().methodB();
}
}
执行结果:
static methodA begin 线程名称:A time:1622097007969
static methodA end 线程名称:A time:1622097009983
static methodB begin 线程名称:B time:1622097009983
static methodB end 线程名称:B time:1622097011990
综上,分析可知:
1)用类直接在两个线程中分别调用两个不同的静态同步方法,会顺次访问。
2)用类直接在两个线程中分别调用一个静态同步方法和一个静态方法,可以同时访问。
3)用类直接在一个线程中调用一个静态同步方法,另一个用实例对象调用一个静态方法(或普通方法),可以同时访问。
4)用类的不同实例对象分别调用两个不同的静态同步方法,会顺次访问。
结论:
synchronized static静态同步方法锁的是当前类,针对的是该类的所有实例对象,多个线程并发访问时,不论是直接通过类还是类的对象实例,访问同一个静态同步方法或访问不同的静态同步方法,必须顺序访问;访问同一个非静态方法或访问不同的非静态方法,可以同时访问。(即控制当前类下同步方法的并发访问)
三、synchronized(*.Class)同步方法
public class ObjectService {
/**
* 方法A
*/
public void methodA(){
try {
synchronized (ObjectService.class) {
System.out.println("methodA begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("methodA end 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 方法B
*/
public void methodB(){
synchronized (ObjectService.class) {
System.out.println("methodB begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
System.out.println("methodB end 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
}
}
/**
* 方法C
*/
public void methodC(){
System.out.println("static methodC begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
System.out.println("static methodC end 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
}
/**
* 方法D
*/
public static void methodD(){
System.out.println("static methodD begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
System.out.println("static methodD end 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
}
}
/**
* 线程A
*/
public class ThreadA extends Thread {
private ObjectService objectService;
public ThreadA(ObjectService objectService) {
//调用父类无参构造,完成线程初始化
super();
this.objectService = objectService;
}
@Override
public void run() {
objectService.methodA();
}
}
/**
* 线程B
*/
public class ThreadB extends Thread {
private ObjectService objectService;
public ThreadB(ObjectService objectService) {
//调用父类无参构造,完成线程初始化
super();
this.objectService = objectService;
}
@Override
public void run() {
objectService.methodB();
}
}
public class MainTest {
public static void main(String[] args) {
ObjectService service = new ObjectService();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
service = new ObjectService();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}
四、同步代码块和同步方法的区别
我的理解是,同步代码块就是需要控制并发访问的代码片段(方法内),同步方法就是需要控制并发访问的方法(方法本身)。
五、最后小结
-
synchronized(this) 只可以锁一个实例对象,synchronized(object)可以锁多个实例对象(多次调用,传入不同的实例即可)。
-
synchronized(this)相当于当前实例的一把锁,synchronized(object)相当于N个实例对象的N把锁。
-
synchronized(this)和synchronized (Object o)锁的都是实例对象,不是方法。都是控制实例对象下同步方法的访问。
-
synchronized static静态同步方法锁的是当前类,针对的是该类的所有实例对象,多个线程并发访问时,不论是直接通过类还是类的对象实例,访问同一个静态同步方法或访问不同的静态同步方法,必须顺序访问;访问同一个非静态方法或访问不同的非静态方法,可以同时访问。(即控制当前类下同步方法的并发访问)
-
synchronized锁的是实例对象,static synchronized 锁的是类对象。
-
synchronized(*.class)同步代码块的作用和synchronized static静态同步方法一样,都是锁类对象。
-
使用synchronized (*.class)的方法,多线程并发访问,同一实例对象访问,会顺次访问;不同实例对象访问,会顺次访问。说明Class锁对类的所有对象实例起作用。