前言
笔者在【synchronized 用法与原理分析】一文中详细的讲解了synchronized的原理和用法,但是最近发现其实深究起来有些细微的方面还是不够牢固,比如synchronized锁住this,锁住普通对象,类锁等等,在实际使用中的具体表现并没有一个直观的展示。因此这篇博客就是对这部分内容进行一个展示,从实例出发来探究synchronized锁的种种表现,希望各位对synchronized有一个立体的认识。更多线程知识内容请点击【Java 多线程和锁知识笔记系列】。
创建一个测试类
首先我们先创建一个测试类SyncTest
让其实现 Runnable
接口并实现run()方法,那么后面所有的方法都会添加到这个类里面,当然在文中的最后笔者会把整个类的代码放出来给大家参考使用。这个实例的主要变量在于:
- 如果两个线程持有同一个类的同一个对象,那么synchronized对其的影响是什么。
- 如果两个线程持有的是同一个类的不同对象,那么synchronized对其的影响是什么。
- 如果多个线程持有的是同一个类的不同对象,当synchronized加类锁对其影响是什么。
- 所有的结果按照时间对比,确定开始顺序,执行顺序,结束顺序,以判断synchronized影响的效果。
public class SyncTest implements Runnable{
//用于锁住一个普通类对象
private final Object object=new Object();
//主要测试方法
public static void main(String[] args) {
//测试线程一共4个
Thread t1 = new Thread(new SyncTest(), "t1"); //线程1持有一个新创建的对象
Thread t2 = new Thread(new SyncTest(), "t2"); //线程2也是持有一个新创建的对象
SyncTest syncThread = new SyncTest(); //创建一个通用对象
Thread t3 = new Thread(syncThread, "t3"); //线程3持有这个通用对象
Thread t4 = new Thread(syncThread, "t4"); //线程4持有同一个通用对象
t1.start();
t2.start();
t3.start();
t4.start();
}
@Override
public void run() {
//case1(); //测试锁住一个新对象
//case2(); //测试类锁
//case3(); //测试锁住this
//case4(); //测试锁住一个普通方法
//case5(); //测试锁住一个静态方法
//case6(); //测试锁住一个其他对象
}
}
锁住新的实例
给每一个锁创建一个新的实例。
private void case1(){
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (new SyncTest()) { //锁住新对象,synchronized 形同虚设
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
-------执行输出--------
Thread t3 Start time: 16:42:21
Thread t4 Start time: 16:42:21
Thread t2 Start time: 16:42:21
Thread t1 Start time: 16:42:21
Thread t3 SYN Start time: 16:42:21
Thread t4 SYN Start time: 16:42:21
Thread t1 SYN Start time: 16:42:21
Thread t2 SYN Start time: 16:42:21
Thread t3 SYN End time: 16:42:23
Thread t1 SYN End time: 16:42:23
Thread t4 SYN End time: 16:42:23
Thread t1 End time: 16:42:23
Thread t2 SYN End time: 16:42:23
Thread t3 End time: 16:42:23
Thread t4 End time: 16:42:23
Thread t2 End time: 16:42:23
从执行结果的时间上可以看出:锁住新的实例,完全没有影响,四个线程同时开始,同时进入锁,同时执行完毕,synchronized形同虚设。因为每个锁进入后都是一个新的实例,所以四个线程互不影响。
锁住SyncTest类,类锁
也就是我们所说的类锁。
private void case2(){
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (SyncTest.class) { //类锁,synchronized 将会锁住该类所有的实例对象
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
-------执行输出--------
Thread t3 Start time: 16:45:28
Thread t4 Start time: 16:45:28
Thread t2 Start time: 16:45:28
Thread t1 Start time: 16:45:28
Thread t3 SYN Start time: 16:45:28
Thread t3 SYN End time: 16:45:30
Thread t3 End time: 16:45:30
Thread t1 SYN Start time: 16:45:30
Thread t1 SYN End time: 16:45:32
Thread t1 End time: 16:45:32
Thread t2 SYN Start time: 16:45:32
Thread t2 SYN End time: 16:45:34
Thread t2 End time: 16:45:34
Thread t4 SYN Start time: 16:45:34
Thread t4 SYN End time: 16:45:36
Thread t4 End time: 16:45:36
从执行结果的时间上可以看出:如果我们使用类锁,那么四个线程必须一个一个的执行,也就是说类锁会锁住一个类的所有创造出来的所有实例对象,无论这些线程持有的是否是同一个对象。
锁住this
锁住this,也就是锁住当前对象。
private void case3(){
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (this) { //锁住this,注意和新建对象的区别
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
-------执行输出1--------
Thread t1 Start time: 16:48:59
Thread t2 Start time: 16:48:59
Thread t3 Start time: 16:48:59
Thread t4 Start time: 16:48:59
Thread t2 SYN Start time: 16:48:59
Thread t1 SYN Start time: 16:48:59
Thread t3 SYN Start time: 16:48:59
Thread t3 SYN End time: 16:49:01
Thread t1 SYN End time: 16:49:01
Thread t3 End time: 16:49:01
Thread t2 SYN End time: 16:49:01
Thread t1 End time: 16:49:01
Thread t4 SYN Start time: 16:49:01
Thread t2 End time: 16:49:01
Thread t4 SYN End time: 16:49:04
Thread t4 End time: 16:49:04
-------执行输出2--------
Thread t4 Start time: 16:51:42
Thread t3 Start time: 16:51:42
Thread t1 Start time: 16:51:42
Thread t2 Start time: 16:51:42
Thread t4 SYN Start time: 16:51:42
Thread t1 SYN Start time: 16:51:42
Thread t2 SYN Start time: 16:51:42
Thread t4 SYN End time: 16:51:44
Thread t1 SYN End time: 16:51:44
Thread t2 SYN End time: 16:51:44
Thread t1 End time: 16:51:44
Thread t4 End time: 16:51:44
Thread t2 End time: 16:51:44
Thread t3 SYN Start time: 16:51:44
Thread t3 SYN End time: 16:51:46
Thread t3 End time: 16:51:46
这里输出结果笔者贴了两次,是因为这次的结果总是t1、t2同时开始,但是t3、t4互有交替的被synchronized锁住。从执行结果的时间上可以看出:说明锁影响到的是同一个对象,也就是同一个对象被不同线程使用是会被锁住,也就是只有当前线程可以使用。但是持有该类不同对象的线程可以同时访问其持有的资源。
锁住普通的方法
把 synchronized 关键字加到普通方法上。
private synchronized void case4(){ //加到普通方法上,效果和this一致
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
-------执行输出1--------
Thread t1 Start time: 16:54:42
Thread t3 Start time: 16:54:42
Thread t2 Start time: 16:54:42
Thread t1 SYN Start time: 16:54:42
Thread t3 SYN Start time: 16:54:42
Thread t2 SYN Start time: 16:54:42
Thread t3 SYN End time: 16:54:44
Thread t2 SYN End time: 16:54:44
Thread t3 End time: 16:54:44
Thread t1 SYN End time: 16:54:44
Thread t2 End time: 16:54:44
Thread t1 End time: 16:54:44
Thread t4 Start time: 16:54:44
Thread t4 SYN Start time: 16:54:44
Thread t4 SYN End time: 16:54:46
Thread t4 End time: 16:54:46
-------执行输出2--------
Thread t2 Start time: 16:55:09
Thread t4 Start time: 16:55:09
Thread t1 Start time: 16:55:09
Thread t4 SYN Start time: 16:55:09
Thread t2 SYN Start time: 16:55:09
Thread t1 SYN Start time: 16:55:09
Thread t1 SYN End time: 16:55:11
Thread t2 SYN End time: 16:55:11
Thread t1 End time: 16:55:11
Thread t4 SYN End time: 16:55:11
Thread t2 End time: 16:55:11
Thread t4 End time: 16:55:11
Thread t3 Start time: 16:55:11
Thread t3 SYN Start time: 16:55:11
Thread t3 SYN End time: 16:55:13
Thread t3 End time: 16:55:13
从执行结果的时间上可以看出:把synchronized加到普通的方法上,就意味着这个普通的方法被synchronized锁住了,同一个实例不可以同时访问(3、4),非同一个实例可以同时访问(t1、t2),这个结果和this一样。
锁住静态的方法
把 synchronized 关键字加到静态方法上。
private static synchronized void case5(){ //加到静态方法,效果和类锁一致
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
-------执行输出--------
Thread t1 Start time: 16:59:35
Thread t1 SYN Start time: 16:59:35
Thread t1 SYN End time: 16:59:37
Thread t1 End time: 16:59:37
Thread t4 Start time: 16:59:37
Thread t4 SYN Start time: 16:59:37
Thread t4 SYN End time: 16:59:39
Thread t4 End time: 16:59:39
Thread t3 Start time: 16:59:39
Thread t3 SYN Start time: 16:59:39
Thread t3 SYN End time: 16:59:41
Thread t3 End time: 16:59:41
Thread t2 Start time: 16:59:41
Thread t2 SYN Start time: 16:59:41
Thread t2 SYN End time: 16:59:43
Thread t2 End time: 16:59:43
从执行结果的时间上可以看出:锁住静态的方法,所有线程依次执行,这个结果和类锁一样。这是因为在汇编层面静态方法会随着类的定义而被分配和装载入内存中,此时类不可能有任何对象,因此必须使用类锁,所以其表现和类锁一致。
锁住一个普通对象
把synchronized加到一个普通对象上,但是这个对象和使用对象无关。
private void case6(){
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (object) { //加到一个普通对象上,效果和this一致
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
-------执行输出--------
Thread t2 Start time: 17:02:26
Thread t3 Start time: 17:02:26
Thread t4 Start time: 17:02:26
Thread t3 SYN Start time: 17:02:26
Thread t1 Start time: 17:02:26
Thread t2 SYN Start time: 17:02:26
Thread t1 SYN Start time: 17:02:26
Thread t2 SYN End time: 17:02:28
Thread t1 SYN End time: 17:02:28
Thread t2 End time: 17:02:28
Thread t3 SYN End time: 17:02:28
Thread t1 End time: 17:02:28
Thread t3 End time: 17:02:28
Thread t4 SYN Start time: 17:02:28
Thread t4 SYN End time: 17:02:30
Thread t4 End time: 17:02:30
从执行结果的时间上可以看出:对于锁住一个随意的变量,其结果和this一样,说明如果对象锁跟访问的对象没有关系,那么不同线程持有同样对象访问时就会被锁住,但是如果不同线程持有不同对象就会同时访问。
附:完整代码
import java.text.SimpleDateFormat;
import java.util.Date;
public class SyncTest implements Runnable{
private final Object object=new Object();
public static void main(String[] args) {
Thread t1 = new Thread(new SyncTest(), "t1"); //线程1持有一个新创建的对象
Thread t2 = new Thread(new SyncTest(), "t2"); //线程2也是持有一个新创建的对象
SyncTest syncThread = new SyncTest(); //创建一个通用对象
Thread t3 = new Thread(syncThread, "t3"); //线程3持有这个通用对象
Thread t4 = new Thread(syncThread, "t4"); //线程4持有同一个通用对象
t1.start();
t2.start();
t3.start();
t4.start();
}
@Override
public void run() {
//case1(); //测试锁住一个新对象
//case2(); //测试类锁
//case3(); //测试锁住this
//case4(); //测试锁住一个普通方法
//case5(); //测试锁住一个静态方法
case6(); //测试锁住一个其他对象
}
//对象锁:锁住新的实例,完全没有影响,四个线程同时执行完毕。synchronized形同虚设。
private void case1(){
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (new SyncTest()) {
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
//类锁:锁住整一个类,其类的所有创造出来的对象都会被锁住,四个线程必须一个一个的执行,无论这些线程持有的是否是同一个对象。
private void case2(){
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (SyncTest.class) {
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
//对象锁:锁住this,注意和新建对象的区别
// 此时总是1、2同时开始,但是3、4互有交替的被synchronized阻止住,
// 说明锁影响到的是同一个对象,也就是同一个对象被不同线程使用是会被锁住,也就是只有当前线程可以使用。
// 但是持有该类不同对象的线程可以同时访问其持有的资源。
private void case3(){
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (this) {
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
//锁住普通的方法,同一个实例不可以同时访问(3、4),非同一个实例可以同时访问(t1、t2),这个结果和this一样
private synchronized void case4(){
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
//锁住静态的方法,所有线程依次执行,这个结果和类锁一样
// 这是因为在汇编层面静态方法会随着类的定义而被分配和装载入内存中,此时类不可能有任何对象,因此必须使用类锁
private static synchronized void case5(){
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
//对于锁住一个随意的变量,其结果和this一样,说明如果对象锁跟访问的对象没有关系,那么就会都同时访问。
private void case6(){
System.out.println("Thread "+Thread.currentThread().getName()+ " Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (object) {
try {
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN Start time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(2000);
System.out.println("Thread "+Thread.currentThread().getName()+ " SYN End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+Thread.currentThread().getName()+ " End time: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}