synchronized 同步方法和同步代码块,以及synchronized 加锁 this 和 类class 的区别
同步方法是对整个方法中所有内容加锁;同步代码块选择只同步部分代码而不是整个方法,比同步方法要更细颗粒度,也能选择加锁 this 和 类class,还可以是变量。
同步方法的锁用的是 这个方法所在的这个对象/类上的内置锁。
同步代码块的锁用的是 synchronized()括号里参数对象上的锁。可以是 this、类.class、变量。要具体分析参与抢锁的对象是否持有相同的对象锁(也就是this、类.class、变量…是否同一个对象)
同步方法:
即有synchronized 修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。
在调用给方法前,要获取内置锁,否则处于阻塞状态。(不同方法都是用对象上的同一个内置锁)
注意:synchronized修饰静态方法,如果调用该静态方法,将锁住整个类。
非静态方法
示例1:
不同方法,都被synchronized 修饰,但不同方法获取的都是test1 对象上的同一个内置锁。 所以会出现争抢锁。
public class SynchronizedTest_1 {
public synchronized void method_1(){
System.out.println("method_1 start.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method_1 end.");
}
public synchronized void method_2(){
System.out.println("method_2 start.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method_2 end.");
}
public static void main(String[] args) {
SynchronizedTest_1 test1 = new SynchronizedTest_1();
new Thread(new Runnable() {
@Override
public void run() {
test1.method_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test1.method_2(); // 当然这里也test1.method_1()的话也会抢锁
}
}).start();
}
}
输出结果:(现象:线程1执行结束后释放,线程2才获取到锁。)
method_1 start.
method_1 end.
method_2 start.
method_2 end.
示例2:
对比示例1,这次创建了两个对象test1、test2 ,分别在两个线程中使用。不存在抢锁,因为是两个不同对象,分别获取了自己的内置锁。
public static void main(String[] args) {
SynchronizedTest_1 test1 = new SynchronizedTest_1();
SynchronizedTest_1 test2 = new SynchronizedTest_1();
new Thread(new Runnable() {
@Override
public void run() {
test1.method_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test2.method_2();
}
}).start();
}
输出结果:
method_1 start.
method_2 start.
method_2 end.
method_1 end.
静态方法 (全局锁)
示例3:
静态方法,被synchronized 修饰。但是创建了两个对象test1、test2 ,分别在两个线程中使用。
会出现争抢锁。因为是针对类级别的锁,不同对象也会持有同一个内置锁。
public class SynchronizedTest_2 {
public static synchronized void method_1(){
System.out.println("method_1 start.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method_1 end.");
}
public static synchronized void method_2(){
System.out.println("method_2 start.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("method_2 end.");
}
public static void main(String[] args) {
SynchronizedTest_2 test1 = new SynchronizedTest_2();
SynchronizedTest_2 test2 = new SynchronizedTest_2();
new Thread(new Runnable() {
@Override
public void run() {
test1.method_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// test2.method_1(); // 都调用method_1当然也会抢锁
test2.method_2();
}
}).start();
}
}
输出结果:
method_1 start.
method_1 end.
method_2 start.
method_2 end.
同步代码块:
探讨:synchronized 加锁 this 和 类.class 的区别;以及加锁变量、其他类对象的区别。
this
示例4:
synchronized 加锁 this 当前对象,两个线程中的test1是同一个对象, 会出现抢锁。
public class SynchronizedTest_3 {
public void method_1(){
System.out.println("method_1 stand by.");
long start = System.currentTimeMillis();
synchronized (this) {
try {
System.out.printf("method_1 start... 【wait:%s】\n", System.currentTimeMillis() - start);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("method_1 end.");
}
public static void main(String[] args) {
SynchronizedTest_3 test1 = new SynchronizedTest_3();
new Thread(new Runnable() {
@Override
public void run() {
test1.method_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test1.method_1();
}
}).start();
}
}
输出结果:
method_1 stand by.
method_1 stand by.
method_1 start... 【wait:0】
method_1 end.
method_1 start... 【wait:2014】
method_1 end.
或
method_1 stand by.
method_1 start... 【wait:0】
method_1 stand by.
method_1 end.
method_1 start... 【wait:2014】
method_1 end.
示例5:
对比示例4,这次创建了两个对象test1、test2 ,分别在两个线程中使用。
不存在抢锁,因为是两个不同对象,但synchronized 是对两个对象各自的锁。
public static void main(String[] args) {
SynchronizedTest_3 test1 = new SynchronizedTest_3();
SynchronizedTest_3 test2 = new SynchronizedTest_3();
new Thread(new Runnable() {
@Override
public void run() {
test1.method_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test2.method_1();
}
}).start();
}
输出结果:
method_1 stand by.
method_1 stand by.
method_1 start... 【wait:0】
method_1 start... 【wait:0】
method_1 end.
method_1 end.
类.class (全局锁)
示例6:
会抢锁,synchronized 是类加锁,不同对象都拿到同一个Class对象,即也会持有同一个锁。
public class SynchronizedTest_4 {
public void method_1(){
System.out.println("method_1 stand by.");
long start = System.currentTimeMillis();
synchronized (SynchronizedTest_4.class) {
try {
System.out.printf("method_1 start... 【wait:%s】\n", System.currentTimeMillis() - start);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("method_1 end.");
}
public static void main(String[] args) {
SynchronizedTest_4 test1 = new SynchronizedTest_4();
SynchronizedTest_4 test2 = new SynchronizedTest_4();
new Thread(new Runnable() {
@Override
public void run() {
test1.method_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test2.method_1();
}
}).start();
}
}
输出结果:
method_1 stand by.
method_1 stand by.
method_1 start... 【wait:0】
method_1 end.
method_1 start... 【wait:2023】
method_1 end.
这里的类不管是相关还是不相关的类,如:Object.class,不同的对象都只会拿到相同的Object.class,所以一定是同一把锁。
synchronized (Object.class) {
// ....
}
同步代码块使用 类.class加锁,是全局锁。不关心这个类本身。
方法变量
示例7:
对变量加锁,如果传到方法里变量是同一个变量对象,那么就会出现争抢锁。
public class SynchronizedTest_6 {
public void method_1(Integer lockObject){
System.out.println("method_1 stand by.");
long start = System.currentTimeMillis();
synchronized (lockObject) {
try {
System.out.printf("method_1 start... 【wait:%s】\n", System.currentTimeMillis() - start);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("method_1 end.");
}
public static void main(String[] args) {
SynchronizedTest_6 test1 = new SynchronizedTest_6();
SynchronizedTest_6 test2 = new SynchronizedTest_6();
new Thread(new Runnable() {
@Override
public void run() {
test1.method_1(1);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test2.method_1(1);
}
}).start();
}
}
输出结果:
method_1 stand by.
method_1 start... 【wait:0】
method_1 stand by.
method_1 end.
method_1 start... 【wait:2001】
method_1 end.
特别注意:Integer存在静态缓存,范围是 【-128 ~ 127】,当使用Integer A = 127 或者 Integer A = Integer.valueOf(127) 这样的形式,都是从此缓存拿。如果使用 Integer A = new Integer(127),每次都是一个新的对象。还有字符串常量池也要注意。
所以此处关注是,同步代码块传参的对象是否是同一个。
使用非缓存的值:
示例8:
public static void main(String[] args) {
SynchronizedTest_6 test1 = new SynchronizedTest_6();
SynchronizedTest_6 test2 = new SynchronizedTest_6();
new Thread(new Runnable() {
@Override
public void run() {
test1.method_1(128);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test2.method_1(128);
}
}).start();
}
输出结果:
因为128 不是从缓存里拿的,所以是两个不同的Integer 对象,不会抢锁。
method_1 stand by.
method_1 stand by.
method_1 start... 【wait:0】
method_1 start... 【wait:0】
method_1 end.
method_1 end.
成员变量
对成员变量加锁,就去判断每个对象拿到成员变量是否都是同一个。
如test1 、test2两个对象,在创建时,也是分别创建不同object对象,那么这里不存在抢锁。
如果是public static Object lockObject = new Object(); 又会抢锁。此时的object对象是随类加载完成的,所以对于test1 、test2两个对象是相同的对象,就会持有相同的锁。
示例9:
public class SynchronizedTest_5 {
public Object lockObject = new Object();
// public static Object lockObject = new Object(); // 会抢锁。 示例10
// public Integer lockObject = 1; // 会抢锁。 示例11
// public Integer lockObject = 128; // 不会抢锁
public void method_1(){
System.out.println("method_1 stand by.");
long start = System.currentTimeMillis();
synchronized (lockObject) {
try {
System.out.printf("method_1 start... 【wait:%s】\n", System.currentTimeMillis() - start);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("method_1 end.");
}
public static void main(String[] args) {
SynchronizedTest_5 test1 = new SynchronizedTest_5();
SynchronizedTest_5 test2 = new SynchronizedTest_5();
new Thread(new Runnable() {
@Override
public void run() {
test1.method_1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test2.method_1();
}
}).start();
}
}
method_1 stand by.
method_1 stand by.
method_1 start... 【wait:0】
method_1 start... 【wait:0】
示例10:
类变量以及在类加载时创建,后面不管new 多少对象,这里的lockObject 都时是同一个。
public static Object lockObject = new Object();
method_1 stand by.
method_1 stand by.
method_1 start... 【wait:0】
method_1 end.
method_1 start... 【wait:2027】
method_1 end.
示例11:
public Integer lockObject = 1;
虽然不是类变量,但是由于Integer的缓存,test1 、test2两个对象各获取一次lockObject = 1时,拿到的也是同一个1。因此会出现抢锁。
method_1 stand by.
method_1 stand by.
method_1 start... 【wait:0】
method_1 end.
method_1 start... 【wait:2027】
method_1 end.
示例12:
public Integer lockObject = 128;
而128不在缓存中,test1 、test2两个对象各获取一次lockObject = 128时,是不同的128对象。因此不会抢锁。
method_1 stand by.
method_1 stand by.
method_1 start... 【wait:0】
method_1 start... 【wait:0】
method_1 end.
method_1 end.