一、什么是synchronized
synchronized是java提供的关键字,被synchronized修饰的代码在同一时刻只能被一个线程访问。用于解决java代码中多个线程的共享存储空间的访问冲突问题,有效避免了同一个数据对象被多个线程同时访问。
二、synchronized的使用方式
使用synchronized关键字修饰在成员方法/静态方法前,如:
public synchronized void method(){...}
或public synchronized static void method(){...}
;作为方法内语句使用,修饰代码块,如:
synchronized(this){...}
;
三、一些要点
一个对象或一个类(class)只有一把锁(lock)与之相关联;
无论synchronized如何修饰或使用,它获取的一定是对象锁或类锁,而不是其它的什么;
慎用synchronized关键字,当你的java代码是在多线程环境下且需要考虑数据的共享和同步的时候可以使用一下;
实现多线程之间的数据同步是非常消耗系统开销,特别是对于锁的竞争问题,而且还有可能造成死锁问题;
四、synchronized的使用方法及demo
我们先看一个没有经过任何同步处理的实例demo
- Task.java
package com.xiayc.sync;
public class Task {
private Long id;
private String name;
public Task(Long id, String name) {
super();
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
//这个操作可能需要一定的时间
try {
Thread.currentThread().sleep(3000);
this.name = name;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- TaskTester.java
package com.xiayc.sync;
public class TaskTester {
public static void main(String[] args) {
Task task = new Task(1L,"任务1");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
task.setId(2L);
task.setName("任务2");
}
},"t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("id:"+task.getId());
System.out.println("name:"+task.getName());
}
},"t2");
t2.start();
}
}
- 执行结果
id:2
name:任务1
- 分析
如上所示我们在main方法中创建了一个Task对象,并且id为1,name为任务1。在t1线程中我们想改变一下Task对象的id和name的值,但是setName可能需要消耗一些时间,这在生产环境中是很正常的现象,我在这里模拟的是消耗3s。
但是接下来t2线程紧接着就获取了Task对象的id和name的值并打印,这时我们我们希望得到的值是id:1,name:任务1
或者id:2,name:任务2
,但是我们看到的结果是:id:2,name:任务1
,这很明显是一个错误的结果,是因为id已经修改了但name还没有来的及修改。
这个错误就是在多线程中对同一个数据对象的访问在没有进行同步控制的情况下造成的,所以我们接下来要用synchronized关键字来改造一下。
使用synchronized关键字修饰方法实现同步
- SyncTask1.java
package com.xiayc.sync;
public class SyncTask1 {
private Long id;
private String name;
public SyncTask1(Long id, String name) {
super();
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public synchronized String getName() {
return name;
}
public synchronized void setName(String name) throws InterruptedException {
//这个操作可能需要一定的时间
Thread.currentThread().sleep(3000);
this.name = name;
}
}
- SyncTaskTester.java
package com.xiayc.sync;
public class SyncTaskTester {
public static void main(String[] args) {
SyncTask1 task = new SyncTask1(1L,"任务1");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
task.setId(2L);
task.setName("任务2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("id:"+task.getId());
System.out.println("name:"+task.getName());
}
},"t2");
t2.start();
}
}
- 执行结果
id:2
//注意这里有阻塞3s
name:任务2
- 分析
这里的操作和前面的操作一样,而这次的打印结果是我们希望得到的,因为这次我们使用synchronized关键字修饰setName和getName实现了同步。
首先当前SyncTask1对象只有一把对象锁,所以你进入了setName方法(当然此时当前对象的锁没有被其它线程持有),那么你就获得了当前对象的锁,其它线程就无法进入setName和getName方法了,反之进入getName方法亦然。
所以当t1线程进入SyncTask1的setName方法后,t2线程再调用getName方法时会被阻塞在外面直到t1执行完setName方法并释放了当前对象锁。
这里有一个问题就是虽然synchronized是个好东西,让我们在多线程下访问数据更加的安全,但并不高效。特别是访问同一数据对象的线程达到一定数量后,如果这时不是t2一个线程在等待,而是有100、1000甚至10000个线程在等待执行getName方法时,如果t1线程一旦释放了锁,那么剩下的所有线程都会同时抢这把锁,这是很消耗性能的。
使用
synchronized(this){...}
方式实现同步
- SyncTask2.java
package com.xiayc.sync;
public class SyncTask2 {
private Long id;
private String name;
public SyncTask2(Long id, String name) {
super();
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
synchronized(this) {
return name;
}
}
public void setName(String name) throws InterruptedException {
synchronized(this) {
//这个操作可能需要一定的时间
Thread.currentThread().sleep(3000);
this.name = name;
}
}
}
- SyncTaskTester.java
package com.xiayc.sync;
public class SyncTaskTester {
public static void main(String[] args) {
SyncTask2 task = new SyncTask2(1L,"任务1");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
task.setId(2L);
task.setName("任务2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("id:"+task.getId());
System.out.println("name:"+task.getName());
}
},"t2");
t2.start();
}
}
- 执行结果
id:2
//注意这里有阻塞3s
name:任务2
- 分析
这种方式和上一种方式没有本质的区别,只是这里是显示使用了
synchronized(this)
的方式来获取当前对象的锁
使用
synchronized(Object)
的方式来实现同步
- SyncTask3.java
package com.xiayc.sync;
public class SyncTask3 {
private Long id;
private String name;
private Object lock = new Object();
public SyncTask3(Long id, String name) {
super();
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
synchronized(lock) {
return name;
}
}
public void setName(String name) throws InterruptedException {
synchronized(lock) {
//这个操作可能需要一定的时间
Thread.currentThread().sleep(3000);
this.name = name;
}
}
}
- SyncTaskTester.java
package com.xiayc.sync;
public class SyncTaskTester {
public static void main(String[] args) {
SyncTask3 task = new SyncTask3(1L,"任务1");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
task.setId(2L);
task.setName("任务2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("id:"+task.getId());
System.out.println("name:"+task.getName());
}
},"t2");
t2.start();
}
}
- 执行结果
id:2
//注意这里有阻塞3s
name:任务2
- 分析
这里的执行效果和前面的同步方式一致,只是在SyncTask3专门创建了一个Object用于锁定
以上的几种方式都是基于基于对象锁实现的,需要注意的是如果是不同线程对于不同的数据对象进行同步是无效的,所以还有一种基于类粒度的锁同步方式
- SyncStaticTask1.java
package com.xiayc.sync;
public class SyncStaticTask1 {
private Long id;
private String name;
public SyncStaticTask1(Long id, String name) {
super();
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
synchronized(SyncStaticTask1.class) {
return name;
}
}
public void setName(String name) throws InterruptedException {
synchronized(SyncStaticTask1.class) {
//这个操作可能需要一定的时间
Thread.currentThread().sleep(3000);
this.name = name;
}
}
}
- SyncStaticTaskTester.java
package com.xiayc.sync;
public class SyncStaticTaskTester {
public static void main(String[] args) {
SyncStaticTask1 task1 = new SyncStaticTask1(1L,"任务1");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
task1.setId(2L);
task1.setName("任务2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1");
t1.start();
SyncStaticTask1 task2 = new SyncStaticTask1(2L,"任务2");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(task2.getId());
System.out.println(task2.getName());
}
},"t2");
t2.start();
}
}
- 执行结果
id:2
//注意这里有阻塞3s
name:任务2
- 分析
这里同步所使用的是类锁,它与前面所使用的对象锁的区别是它的范围更广,我们从上面的代码可以看到尽管是不同的线程对不同的数据对象进行操作,它还是会进行同步操作。
- SyncStaticTask2.java
package com.xiayc.sync;
public class SyncStaticTask2 {
private Long id;
private static String name;
public SyncStaticTask2(Long id, String name) {
super();
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public synchronized static String getName() {
return name;
}
public synchronized static void setName(String name) throws InterruptedException {
//这个操作可能需要一定的时间
Thread.currentThread().sleep(3000);
name = name;
}
}
- SyncStaticTaskTester.java
package com.xiayc.sync;
public class SyncStaticTaskTester {
public static void main(String[] args) {
SyncStaticTask2 task1 = new SyncStaticTask2(1L,"任务1");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
task1.setId(2L);
task1.setName("任务2");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1");
t1.start();
SyncStaticTask2 task2 = new SyncStaticTask2(2L,"任务2");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(task2.getId());
System.out.println(task2.getName());
}
},"t2");
t2.start();
}
}
- 执行结果
id:2
//注意这里有阻塞3s
name:任务2
- 分析
使用synchronized修饰静态方法所加的锁的也是类锁,效果和前面的一致
以上,如果有什么不正确的地方,欢迎大家斧正……