开篇:在多线程并发编程中synchronized一直是元老级角色,我们在开发过程中可以使用它来解决线程安全问题中提到的原子性,可见性,以及顺序性。很多人都会称呼它为重量级锁。但是,随着Java SE 1.6对synchronized进行了各种优化之后,有些情况下它就并不那么重了,Java SE 1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。
一、synchronized的特性
1.1 原子性
所谓原子性就是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。但是像i++、i+=1等操作字符就不是原子性的,它们是分成读取、计算、赋值几步操作,原值在这些步骤还没完成时就可能已经被赋值了,那么最后赋值写入的数据就是脏数据,无法保证原子性。
被synchronized修饰的类或对象的所有操作都是原子的,因为在执行操作之前必须先获得类或对象的锁,直到执行完才能释放,这中间的过程无法被中断(除了已经废弃的stop()方法),即保证了原子性。
注意!面试时经常会问比较synchronized和volatile,它们俩特性上最大的区别就在于原子性,volatile不具备原子性。
1.2 可见性
可见性是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的。
synchronized和volatile都具有可见性,其中synchronized对一个类或对象加锁时,一个线程如果要访问该类或对象必须先获得它的锁,而这个锁的状态对于其他任何线程都是可见的,并且在释放锁之前会将对变量的修改刷新到主存当中,保证资源变量的可见性,如果某个线程占用了该锁,其他线程就必须在锁池中等待锁的释放。
而volatile的实现类似,被volatile修饰的变量,每当值需要修改时都会立即更新主存,主存是共享的,所有线程可见,所以确保了其他线程读取到的变量永远是最新值,保证可见性。
1.3 有序性
有序性值程序执行的顺序按照代码先后执行。
synchronized和volatile都具有有序性,Java允许编译器和处理器对指令进行重排,但是指令重排并不会影响单线程的顺序,它影响的是多线程并发执行的顺序性。synchronized保证了每个时刻都只有一个线程访问同步代码块,也就确定了线程执行同步代码块是分先后顺序的,保证了有序性。
1.4 可重入性
synchronized和ReentrantLock都是可重入锁。当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁。通俗一点讲就是说一个线程拥有了锁仍然还可以重复申请锁。
问题1 、标准访问先打印邮件还是短信?
答案:先打印邮件,因为邮件有延迟。(如果没有延迟100毫秒,则答案不一定)
/**
* [@author](https://learnku.com/users/65003) zhangxiao
* @qq 490433117
* @create_date 2022/8/9 20:33
*/
package com.example.demomissyou.synchronizedDemo;
import java.util.concurrent.TimeUnit;
/**
* [@author](https://learnku.com/users/65003) zhangxiao
* @date 2022/8/9 20:33
*/
class Phone {
public synchronized void sendEmail() {
System.out.println("*****sendEmail");
}
public synchronized void sendMsg() {
System.out.println("*****sendMsg");
}
}
public class SynchronizedDemo3 {
public static void main(String[] args) throws InterruptedException {
Phone student = new Phone();
new Thread(() -> student.sendEmail()).start();
TimeUnit.MILLISECONDS.sleep(100);
new Thread(() -> student.sendMsg()).start();
}
}
如下图:
问题2 、邮件方法暂停4秒后,问先打印邮件还是短信?
答案:先打印邮件。原因: 解释:对象锁
- 一个对象里面如果有多个synchronized方法,某一个时刻内,只要一个线程去调用其中的一个synchronized方法了,
- 其他的线程都只能等待,换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized方法,
- 锁的是当前对象this,被锁定后,其他的线程都不能进入到当前对象的其他的synchronized方法
package com.example.demomissyou.synchronizedDemo;
import java.util.concurrent.TimeUnit;
class Phone {
public synchronized void sendEmail() {
try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("*****sendEmail");
}
public synchronized void sendMsg() {
System.out.println("*****sendMsg");
}
}
public class SynchronizedDemo3 {
public static void main(String[] args) throws InterruptedException {
Phone student = new Phone();
new Thread(() -> student.sendEmail()).start();
TimeUnit.MILLISECONDS.sleep(100);
new Thread(() -> student.sendMsg()).start();
}
}
### 如下图:
![Java synchronized原理及实操八锁现象记录](https://cdn.learnku.com/uploads/images/202208/11/55807/IujjbXyFxL.png!large)
## 问题3 、新增一个普通方法 sayHello,问先打印邮件还是sayHello?
### 答案:先打印sayHello。原因: 解释:加个普通方法后发现和同步锁无关
```java
/**
* [@author](https://learnku.com/users/65003) zhangxiao
* @qq 490433117
* @create_date 2022/8/9 20:33
*/
package com.example.demomissyou.synchronizedDemo;
import java.util.concurrent.TimeUnit;
/**
* [@author](https://learnku.com/users/65003) zhangxiao
* @date 2022/8/9 20:33
*/
class Phone {
public synchronized void sendEmail() {
try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("*****sendEmail");
}
public synchronized void sendMsg() {
System.out.println("*****sendMsg");
}
public void sayHello(){
System.out.println("*****sayHello");
}
}
public class SynchronizedDemo3 {
public static void main(String[] args) throws InterruptedException {
Phone student = new Phone();
new Thread(() -> student.sendEmail()).start();
TimeUnit.MILLISECONDS.sleep(100);
new Thread(() -> student.sayHello()).start();
}
}
如下图:
问题4 、两步手机,问先打印邮件还是短信?
答案:短信。原因:换成两个对象后,不是同一把锁了,情况立刻变化
/**
* @author zhangxiao
* @qq 490433117
* @create_date 2022/8/9 20:33
*/
package com.example.demomissyou.synchronizedDemo;
import com.example.demomissyou.student;
import java.util.concurrent.TimeUnit;
/**
* @author zhangxiao
* @date 2022/8/9 20:33
*/
class Phone {
public synchronized void sendEmail() {
try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}
System.out.println("*****sendEmail");
}
public synchronized void sendMsg() {
System.out.println("*****sendMsg");
}
public void sayHello(){
System.out.println("*****sayHello");
}
}
public class SynchronizedDemo3 {
public static void main(String[] args) throws InterruptedException {
Phone student1 = new Phone();
Phone student2 = new Phone();
new Thread(() -> student1.sendEmail()).start();
TimeUnit.MILLISECONDS.sleep(100);
new Thread(() -> student2.sendMsg()).start();
}
}
如下图:
问题5 、两个静态同步方法,同一部手机,问先打印邮件还是短信?
答案:邮件。原因:对于静态同步方法,锁是当前类的class对象
/**
* @author zhangxiao
* @qq 490433117
* @create_date 2022/8/9 20:33
*/
package com.example.demomissyou.synchronizedDemo;
import com.example.demomissyou.student;
import java.util.concurrent.TimeUnit;
/**
* @author zhangxiao
* @date 2022/8/9 20:33
*/
class Phone {
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("*****sendEmail");
}
public static synchronized void sendMsg() {
System.out.println("*****sendMsg");
}
public void sayHello() {
System.out.println("*****sayHello");
}
}
public class SynchronizedDemo3 {
public static void main(String[] args) throws InterruptedException {
Phone student1 = new Phone();
// Phone student2 = new Phone();
new Thread(() -> student1.sendEmail()).start();
TimeUnit.MILLISECONDS.sleep(100);
new Thread(() -> student1.sendMsg()).start();
}
}
如下图:
问题6 、两个静态同步方法,两部手机,问先打印邮件还是短信?
答案:邮件。原因:
- 锁的同一个字节码对象
- 全局锁
- synchronized实现同步的基础:java中的每一个对象都可以作为锁。
- 具体表现为一下3中形式。
- 对于普通同步方法,锁是当前实例对象,锁的是当前对象this,
- 对于同步方法块,锁的是synchronized括号里配置的对象。
- 对于静态同步方法,锁是当前类的class对象
/**
* @author zhangxiao
* @qq 490433117
* @create_date 2022/8/9 20:33
*/
package com.example.demomissyou.synchronizedDemo;
import com.example.demomissyou.student;
import java.util.concurrent.TimeUnit;
/**
* @author zhangxiao
* @date 2022/8/9 20:33
*/
class Phone {
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("*****sendEmail");
}
public static synchronized void sendMsg() {
System.out.println("*****sendMsg");
}
public void sayHello() {
System.out.println("*****sayHello");
}
}
public class SynchronizedDemo3 {
public static void main(String[] args) throws InterruptedException {
Phone student1 = new Phone();
Phone student2 = new Phone();
new Thread(() -> student1.sendEmail()).start();
TimeUnit.MILLISECONDS.sleep(100);
new Thread(() -> student2.sendMsg()).start();
}
}
如下图:
问题7 、1个普通同步方法,1个静态同步方法,1部手机,问先打印邮件还是短信?
答案:短信。原因:
/**
* @author zhangxiao
* @qq 490433117
* @create_date 2022/8/9 20:33
*/
package com.example.demomissyou.synchronizedDemo;
import com.example.demomissyou.student;
import java.util.concurrent.TimeUnit;
/**
* @author zhangxiao
* @date 2022/8/9 20:33
*/
class Phone {
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("*****sendEmail");
}
public synchronized void sendMsg() {
System.out.println("*****sendMsg");
}
public void sayHello() {
System.out.println("*****sayHello");
}
}
public class SynchronizedDemo3 {
public static void main(String[] args) throws InterruptedException {
Phone student1 = new Phone();
Phone student2 = new Phone();
new Thread(() -> student1.sendEmail()).start();
TimeUnit.MILLISECONDS.sleep(100);
new Thread(() -> student1.sendMsg()).start();
}
}
如下图:
问题8 、1个普通同步方法,1个静态同步方法,2部手机,问先打印邮件还是短信?
答案:短信。原因:
- 当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。也就是说如果一个实例对象的普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁,
- 可是别的实例对象的普通同步方法因为跟该实例对象的普通同步方法用的是不同的锁,所以无需等待该实例对象已获取锁的普通同步方法释放锁就可以获取他们自己的锁。
- 所有的静态同步方法用的也是同一把锁–类对象本身,这两把锁(this/class)是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有静态条件的。
- 但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间。
- 还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象
/**
* @author zhangxiao
* @qq 490433117
* @create_date 2022/8/9 20:33
*/
package com.example.demomissyou.synchronizedDemo;
import com.example.demomissyou.student;
import java.util.concurrent.TimeUnit;
/**
* @author zhangxiao
* @date 2022/8/9 20:33
*/
class Phone {
public static synchronized void sendEmail() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("*****sendEmail");
}
public synchronized void sendMsg() {
System.out.println("*****sendMsg");
}
public void sayHello() {
System.out.println("*****sayHello");
}
}
public class SynchronizedDemo3 {
public static void main(String[] args) throws InterruptedException {
Phone student1 = new Phone();
Phone student2 = new Phone();
new Thread(() -> student1.sendEmail()).start();
TimeUnit.MILLISECONDS.sleep(100);
new Thread(() -> student2.sendMsg()).start();
}
}