关于synchronized的那些事
关于synchronized的那些事
一、简介
Synchronized一句话来解释其作用就是:能够保证同一时刻最多只有一个线程执行该段代码,以达到并发安全的效果。也就是说Synchronized就好比是一把锁,某个线程把资源锁住了之后,别人就不能使用了,只有当这个线程用完了别人才能用。对于Synchronized关键字来说,它是并发编程中一个元老级角色,也就是说你只要学习并发编程,就必须要学习Synchronized关键字。由此可见其地位。
二、使用
对于synchronized关键字来说,一共可以分为两类:对象锁和类锁。
1、对象锁-同步代码块
对象锁-同步代码块:表示同一时刻只有一个线程能够进入代码块
public class SynchronizedCode {
public static void main(String[] args) {
SynTest01 synTest01 = new SynTest01();
Thread thread1 = new Thread(() -> synTest01.test(), "A");
Thread thread2 = new Thread(() -> synTest01.test(), "B");
thread1.start();
thread2.start();
}
}
class SynTest01 {
Object object = new Object();
public void test() {
// 该段代码位于同步代码块之外,不会受锁的限制,任何线程随时随地都可以执行当前这段代码
System.out.println("线程-" + Thread.currentThread().getName() + "进入test方法!");
synchronized (object) {
try {
System.out.println(Thread.currentThread().getName() + "开始执行test方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "执行test方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
当分别两个不同的对象锁不同的代码块的时候,执行步骤:
假如此时有两个线程-A、B
- 1、A先获取object1锁,执行代码块-1,B进来后只能等待;
- 2、A执行完代码块-1后释放object1,B才能获取资源并执行代码块-1,但是A会同时去获取object2并执行代码块-2;
- 3、A执行完代码块-2,释放object2,B执行完代码块-1,获取object-2,继续执行代码块-2。
public class SynchronizedCode {
public static void main(String[] args) {
SynTest02 synTest02 = new SynTest02();
Thread thread1 = new Thread(() -> synTest02.test(), "A");
Thread thread2 = new Thread(() -> synTest02.test(), "B");
thread1.start();
thread2.start();
}
}
class SynTest02 {
Object object1 = new Object();
Object object2 = new Object();
public void test() {
// 代码块-1
synchronized (object1) {
try {
System.out.println(Thread.currentThread().getName() + "开始执行object1方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "执行object1方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 代码块-2
synchronized (object2) {
try {
System.out.println(Thread.currentThread().getName() + "开始执行object2方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "执行object2方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2、对象锁-方法锁
相比较同步代码块锁就简单很多了,就是在普通方法上添加synchronized关键字修饰即可。
public class SynchronizedCode {
public static void main(String[] args) {
SynTest03 synTest03 = new SynTest03();
Thread thread1 = new Thread(() -> synTest03.test(), "A");
Thread thread2 = new Thread(() -> synTest03.test(), "B");
thread1.start();
thread2.start();
}
}
class SynTest03 {
public synchronized void test() {
System.out.println("线程-" + Thread.currentThread().getName() + "进入test方法!");
try {
System.out.println(Thread.currentThread().getName() + "开始执行test方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "执行test方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程-" + Thread.currentThread().getName() + "退出test方法!");
}
}
功能与同步代码块相似,但是它的锁对象是this,即当前对象,
我们可以通过一下方法进行验证:同一时刻只能一个线程进入同步方法,普通方法不受此限制。
public class SynchronizedCode {
public static void main(String[] args) {
SynTest04 synTest04 = new SynTest04();
Thread thread1 = new Thread(() -> synTest04.test1(), "A");
Thread thread2 = new Thread(() -> synTest04.test2(), "B");
Thread thread3 = new Thread(() -> synTest04.test3(), "B");
thread1.start();
thread2.start();
thread3.start();
}
}
class SynTest04 {
public synchronized void test1() {
System.out.println("线程-" + Thread.currentThread().getName() + "进入test1方法!");
try {
System.out.println(Thread.currentThread().getName() + "开始执行test1方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "执行test1方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程-" + Thread.currentThread().getName() + "退出test方法!");
}
public synchronized void test2() {
System.out.println("线程-" + Thread.currentThread().getName() + "进入test2方法!");
try {
System.out.println(Thread.currentThread().getName() + "开始执行test2方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "执行test2方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程-" + Thread.currentThread().getName() + "退出test2方法!");
}
public void test3() {
System.out.println("线程-" + Thread.currentThread().getName() + "进入test3方法!");
try {
System.out.println(Thread.currentThread().getName() + "开始执行test3方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "执行test3方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程-" + Thread.currentThread().getName() + "退出test3方法!");
}
}
3、类锁-static方法锁
在java中,java的类对象可能有无数个,但是类却只有一个,同一时刻只能一个线程执行类下的静态同步方法。
public class SynchronizedCode {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> SynTest05.test1(), "A");
Thread thread2 = new Thread(() -> SynTest05.test1(), "B");
thread1.start();
thread2.start();
}
}
class SynTest05 {
public static synchronized void test1() {
System.out.println("线程-" + Thread.currentThread().getName() + "进入test1静态方法!");
try {
System.out.println(Thread.currentThread().getName() + "开始执行test1静态方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "执行test1静态方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程-" + Thread.currentThread().getName() + "退出test静态方法!");
}
}
4、类锁-class锁
无论多少个对象实例,但是同一时间只能一个线程获取class锁并执行相关的同步代码。
public class SynchronizedCode {
public static void main(String[] args) {
SynTest06 synTest06 = new SynTest06();
Thread thread1 = new Thread(() -> synTest06.test1(), "A");
Thread thread2 = new Thread(() -> synTest06.test1(), "B");
thread1.start();
thread2.start();
}
}
class SynTest06 {
public void test1() {
System.out.println("线程-" + Thread.currentThread().getName() + "进入test1静态方法!");
synchronized (SynTest06.class) {
try {
System.out.println(Thread.currentThread().getName() + "开始执行test1静态方法");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "执行test1静态方法完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程-" + Thread.currentThread().getName() + "退出test静态方法!");
}
}
}
三、使用总结
四、性质
对于synchronized关键字主要有两个性质:可重入性质和不可中断性质。我们分别来看。
1、可重入性
什么是可重入呢?指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。我们举一个例子来说明,一句话吃着碗里的看着锅里的。嘴里面还没吃完就继续再去拿吃的。这就是可重入。不可重入的意思正好相反,你吃完了这碗饭才能盛下一碗。
可重入的程度可以细分为三种情况,我们分别测试一下:
(1)同一个方法中是可重入的。就好比是递归调用同步方法。
(2)不同的方法是可重入的。就好比是一个同步方法调用另外一个同步方法。
(3)不同的类方法是可重入的。
2、不可中断性质
不可中断的意思你可以这样理解,别人正在打游戏,你也想玩,你必须要等别人不想玩了你才能去。在java中表示一旦这个锁被别人抢走了,你必须等待。等别的线程释放了锁,你才可以拿到。否则就一直等下去。
这一点看起来是个有点但其实在某些场景下弊端超级大,因为假如拿到锁得线程永远的不释放,那你就要永远的等下去。
五、缺陷
synchronized关键字既有优点也有缺点,而且缺点贼多,所以后来出现了比他更好的锁。下面我们就来分析一下。
1、效率低
我们之前曾经分析过synchronized关键字是不可中断的,这也就意味着一个等待的线程如果不能获取到锁将会一直等待,而不能再去做其他的事了。
这里也说明了对synchronized关键字的一个改进措施,那就是设置超时时间,如果一个线程长时间拿不到锁,就可以去做其他事情了。
2、不够灵活
加锁和解锁的时候,每个锁只能有一个对象处理,这对于目前分布式等思想格格不入。
3、无法知道是否成功获取到锁
也就是我们的锁如果获取到了,我们无法得知。既然无法得知我们也就很不容易进行改进。
既然synchronized有这么多缺陷。所以才出现了各种各样的锁。
本文地址:https://blog.csdn.net/qq_40722604/article/details/109624829