前言
文接上篇,本文将继续介绍 Synchronized,感兴趣的小伙伴继续跟博主一起讨论下。前两篇文章地址:
「 代码性能优化 」作为一名 Java 程序员,你真的了解 synchronized 吗?(一) https://xie.infoq.cn/article/e8eba8a7bdd7360a9d8c76874
「 代码性能优化 」作为一名 Java 程序员,你真的了解 synchronized 吗?(二)https://xie.infoq.cn/article/2c7c66d9d97cb5f47657eb5d2
您的 关注、点赞、收藏 都将是小编持续创作的动力!
Synchronized 关键字用法
前文中提到 synchronized 的用法可以从两个维度上面分类,下面蒋总结讨论具体用法:
1. 根据修饰对象分类
1.1 修饰代码块
synchronized(this|object) {}
synchronized(类.class) {}
1.2 修饰方法
修饰非静态方法
修饰静态方法
2. 根据获取的锁分类
2.1 获取对象锁
synchronized(this|object) {}
修饰非静态方法
2.2 获取类锁
synchronized(类.class) {}
修饰静态方法
Synchronized 修饰实例方法
示例:
public class SyncDemo {
private int count;
public synchronized void add() {
count++;
}
public static void main(String[] args) throws InterruptedException {
SyncDemo syncDemo = new SyncDemo();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
syncDemo.add();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
syncDemo.add();
}
});
t1.start();
t2.start();
t1.join(); // 阻塞住线程等待线程 t1 执行完成
t2.join(); // 阻塞住线程等待线程 t2 执行完成
System.out.println(syncDemo.count);// 输出结果为 20000
}
}
复制代码
在上面的代码当中的add
方法只有一个简单的count++
操作,因为这个方法是使用synchronized
修饰的因此每一个时刻只能有一个线程执行add
方法,因此上面打印的结果是 20000。如果add
方法没有使用synchronized
修饰的话,那么线程 t1 和线程 t2 就可以同时执行add
方法,这可能会导致最终count
的结果小于 20000,因为count++
操作不具备原子性。
Synchronized 修饰静态方法
示例:
public class SyncDemo {
private static int count;
public static synchronized void add() {
count++;
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
SyncDemo.add();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
SyncDemo.add();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(SyncDemo.count); // 输出结果为 20000
}
}
复制代码
上面的代码最终输出的结果也是 20000,但是与前一个程序不同的是。这里的add
方法用static
修饰的,在这种情况下真正的只能有一个线程进入到add
代码块,因为用static
修饰的话是所有对象公共的,因此和前面的那种情况不同,不存在两个不同的线程同一时刻执行add
方法。
Sychronized 修饰多个方法
示例:
public class AddMinus {
public static int ans;
public static synchronized void add() {
ans++;
}
public static synchronized void minus() {
ans--;
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
AddMinus.add();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
AddMinus.minus();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(AddMinus.ans); // 输出结果为 0
}
}
复制代码
在上面的代码当中我们用synchronized
修饰了两个方法,add
和minus
。这意味着在同一个时刻这两个函数只能够有一个被一个线程执行,也正是因为add
和minus
函数在同一个时刻只能有一个函数被一个线程执行,这才会导致ans
最终输出的结果等于 0。
对于一个实例对象来说:
public class AddMinus {
public int ans;
public synchronized void add() {
ans++;
}
public synchronized void minus() {
ans--;
}
public static void main(String[] args) throws InterruptedException {
AddMinus addMinus = new AddMinus();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
addMinus.add();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
addMinus.minus();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(addMinus.ans);
}
}
复制代码
上面的代码没有使用static
关键字,因此我们需要new
出一个实例对象才能够调用add
和minus
方法,但是同样对于AddMinus
的实例对象来说同一个时刻只能有一个线程在执行add
或者minus
方法,因此上面代码的输出同样是 0。
Synchronized 修饰实例方法代码块
示例:
public class CodeBlock {
private int count;
public void add() {
System.out.println("进入了 add 方法");
synchronized (this) {
count++;
}
}
public void minus() {
System.out.println("进入了 minus 方法");
synchronized (this) {
count--;
}
}
public static void main(String[] args) throws InterruptedException {
CodeBlock codeBlock = new CodeBlock();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
codeBlock.add();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
codeBlock.minus();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(codeBlock.count); // 输出结果为 0
}
}
复制代码
上面的代码当中add
和minus
方法没有使用synchronized
进行修饰,因此一个时刻可以有多个线程执行这个两个方法。在上面的synchronized
代码块当中我们使用了this
对象作为锁对象,只有拿到这个锁对象的线程才能够进入代码块执行,而在同一个时刻只能有一个线程能够获得锁对象。也就是说add
函数和minus
函数用synchronized
修饰的两个代码块同一个时刻只能有一个代码块的代码能够被一个线程执行,因此上面的结果同样是 0。
Synchronized 修饰静态代码块
示例:
public class CodeBlock {
private static int count;
public static void add() {
System.out.println("进入了 add 方法");
synchronized (CodeBlock.class) {
count++;
}
}
public static void minus() {
System.out.println("进入了 minus 方法");
synchronized (CodeBlock.class) {
count--;
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
CodeBlock.add();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
CodeBlock.minus();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(CodeBlock.count);
}
}
复制代码
上面代码的锁对象是CodeBlock.class
,这个时候他不再是锁住一个对象了,而是一个类了,这个时候的并发度就变小了,当锁对象是CodeBlock.class
的时候,实例对象之间时不能够并发的,因为这个时候的锁对象是一个类。
总结
Synchronized 修饰实例方法:不同的对象之间是可以并发的;
Synchronized 修饰静态实例方法:不同的对象是不能并发的,但是不同的类之间可以进行并发;
Sychronized 修饰多个方法:多个方法在同一时刻只能有一个方法被执行,而且只能有一个线程能够执行;
Synchronized 修饰实例方法代码块:同一个时刻只能有一个线程执行代码块;
Synchronized 修饰静态代码块:同一个时刻只能有一个线程执行这个代码块,而且不同的对象之间不能够进行并发;
参考 &致谢
深入Synchronized各种使用方法
莫笑少年江湖梦,谁不少年梦江湖.感谢前人的经验、分享和付出,让我们可以有机会站在巨人的肩膀上眺望星辰大海!
评论 (1 条评论)