synchronized
作用:实现线程同步,让多个线程排队依次获取某个资源,保证数据不会出错
关键:synchronized 修饰什么?锁的是什么元素?
修饰方法
- 非静态方法,锁的是方法的调用者(对象)
- 静态方法,锁的是类
修饰代码块, (synchronized (parm)) 锁的是传入的对象(parm )
测试
1 正常方法测试
class Data {
public void fun1() {
try {
// sleep 3秒
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun1..1.");
}
public void fun2() {
try {
// sleep 1秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun2..2.");
}
}
public class Test {
public static void main(String[] args) {
Data data = new Data();
// 新建一个线程A
new Thread(() -> {
data.fun1();
},"A").start();
// 新建一个线程B
new Thread(() -> {
data.fun2();
},"B").start();
}
}
程序输出
fun2..2.
fun1..1.
解析
主函数中新建两个线程,同时启动,互不影响。线程A暂停3秒才输出,线程B暂停1秒才输出。
所以输出结果是先输出 fun2..2.
后2秒,才输出 fun1..1.
2 测试修饰非静态方法
class Data {
public synchronized void fun1() {
try {
// sleep3秒
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun1..1.");
}
public synchronized void fun2() {
try {
// sleep3秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun2..2.");
}
}
public class Test {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
data.fun1();
}, "A").start();
new Thread(() -> {
data.fun2();
}, "B").start();
}
}
输出结果
fun1..1.
fun2..2.
解析
由于使用了 synchronized
关键字修饰非静态方法,synchronized
锁的是方法的调用者,由于两个方法fun1
和 fun2
是被同一个对象data
调用,所以两个线程不能同时占有对象data
,得依次排队。由于是线程A先占有资源,所以B必须排队,等到线程A执行完线程B才能执行,
所以结果就是线程A
先等待3秒输出 fun1..1.
,线程B
在等待1秒输出fun2..2.
。
下面试试不是同一个调用者,输出又会怎样?
class Data {
public synchronized void fun1() {
try {
// sleep3秒
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun1..1.");
}
public synchronized void fun2() {
try {
// sleep3秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun2..2.");
}
}
public class Test {
public static void main(String[] args) {
Data data = new Data();
Data data2 = new Data();
new Thread(() -> {
data.fun1();
}, "A").start();
new Thread(() -> {
data2.fun2();
}, "B").start();
}
}
结果输出
fun2..2.
fun1..1.
解析
上面的代码分别创建了两个对象,分别是data
和data2
。线程A中对象data
调用fun1
, 线程B对象data2
调用fun2
, 由于两个方法调用不是同一调用者(对象)。所以结果就是两个线程同时进行,不用排队等候。
所以输出就是:先线程B
等待1秒输出fun2..2.
后线程A
再等待2秒输出fun1.1
。
3 测试修饰静态方法fun2
class Data {
public static synchronized void fun1() {
try {
// sleep3秒
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun1..1.");
}
public static synchronized void fun2() {
try {
// sleep3秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fun2..2.");
}
}
public class Test {
public static void main(String[] args) {
Data data = new Data();
Data data2 = new Data();
new Thread(() -> {
data.fun1();
}, "A").start();
new Thread(() -> {
//data2.fun2();
data2.fun2();
}, "B").start();
}
}
结果输出
fun1..1.
fun2..2.
解析
由于synchronized
修饰的是静态代码块,synchronized
锁的是类。线程A中对象data
调用fun1
, 线程B对象data2
调用fun2
,由于对象data
和对象data2
都是属于同一个类Data,所以两个线程锁的东西是一样的,所以线程B必须等线程A执行完之后再执行。
所以结果就是线程A
先等待3秒输出 fun1..1.
,线程B
在等待1秒输出fun2..2.
。
4 测试修饰代码块
4.1 先来个正常的测试试试(没有加synchronized
),然后比较一下
public class Test {
public static void main(String[] args) {
Data2 data2 = new Data2();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
data2.fun();
}).start();
}
}
}
class Data2 {
public void fun() {
{
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
}
程序输出
start...
start...
start...
start...
start...
end...
end...
end...
end...
end...
解析
由于5个线程同时几乎同时启动,没有任何的限制,所有5个线程同时进行同时执行。
所以结果就是输出5个start…先,再输出5个end…
4.2 ``synchronized`修饰静态代码块进行测试
public class Test {
public static void main(String[] args) {
Data2 data2 = new Data2();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
data2.fun();
}).start();
}
}
}
class Data2 {
public void fun() {
synchronized (this) {
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
}
结果输出
start...
end...
start...
end...
start...
end...
start...
end...
start...
end...
解析
由于 synchronized (this)
这里的 this 表示对象,5个线程都是用对象data2
进行测试,所以当多个线程执行中,如果有线程已经占有了该对象(锁住了)。其余线程需要排队等候,等到已经占有的该对象的线程释放到资源,后面的线程才能依次执行。
所以先输出第一个线程的执行输出,然后在第二个线程,第三个线程,依次排队。
所以第1个线程输出start...
后等待1秒然后输出end...
,释放掉占有的资源,第二个线程类似执行…
4.3 换种方法又测试
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Data2 data2 = new Data2();
new Thread(() -> {
data2.fun();
}).start();
}
}
}
class Data2 {
public void fun() {
synchronized (this) {
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
}
输出
start...
start...
start...
start...
start...
end...
end...
end...
end...
end...
解析
由于现在5个进行调用的函数的对象不一样,所以锁的东西也不一样,没有出现争夺资源的情况。所以5个进程同时进行,同时输出start...
后然后再同时输出end...
4.4 修饰代码块能不能锁点别的?只能是this对象吗?
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Data2 data2 = new Data2();
new Thread(() -> {
data2.fun(100);
}).start();
}
}
}
class Data2 {
public void fun(Integer id) {
synchronized (id) {
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
}
输出
start...
end...
start...
end...
start...
end...
start...
end...
start...
end...
解析
由于 synchronized (id)
修饰的是 id
,id
是Integer
对象,由于每次传进去的对象100都是同一个对象,所有``synchronized `锁的id也是同一个。所以当有的线程先占有了id这个资源,当别的线程来访问的时候,必须排队等候。
所以第1个线程输出start...
后等待1秒然后输出end...
,释放掉占有的资源,第二个线程类似执行…
4.5 测试锁class对象
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
Data2 data2 = new Data2();
data2.fun();
}).start();
}
}
}
class Data2 {
public void fun() {
synchronized (Data2.class) {
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
}
结果输出
start...
end...
start...
end...
start...
end...
start...
end...
start...
end...
解析
由于锁的是Data2.class
,所以只要是属于这个Data.class类的都是同一个资源。很明显,都是由类 Data2 声明的对象,所以是同一个资源。
总结
1 搞清楚 synchronized
的作用域是啥(锁的是什么元素?)
2 判断是不是多个线程同时访问synchronized
锁的元素?如果是,则后面的线程必须排队等候已经占有资源的线程释放,然后才能有机会执行。如果不是,各个线程互不影响,各走各的阳光大道。