前言:
在并发编程中会存在线程安全问题, 因为存在多线程共同操作共享数据的可能性。关键字 Synchronized 可以保证在同一个程序,同一时刻,只有一个线程可以执行某个方法或某个代码块,同时 Synchronized 可以保证一个线程的变化可见(可见性),可以代替 volatile。
基本概念
- Synchronized 是JAVA 中解决并发问题最常用的方法,也是最简单的一种方法。
- Synchronized 的作用共有三个:
a. 原子性: 确保线程互斥的访问同步代码;
b. 可见性:保证共享变量的修改能够及时可见,其实是通过java 内存模型中的 “对一个变量 unlock 操作之前,必须同步到主内存中;如果对一个变量进行 lock 操作,则将会清空工作内存中此变量的值,在这行引擎使用此变量钱,需要重新从主内存中 load 操作或 assign 操作初始化变量值” 来保证的。
c. 有序性: 有效解决重排序问题,即 “一个 unlock 操作先行发生于后面对用一个loack操作” - 从语法上将, Synchronized 可以把任何一个非 null 对象作为"锁", 在 HotSpot JVM 实现中,锁有个专门的名字: 对象监视器(Object Monitor)
基本使用:
Synchronized 共有三种用法:
- 当作用在对象方法时:监视器(monitor)便是对象实例(this);
- 当作用在静态方法时:监视器(monitor)便是对象的 class 实例,因为 class 数据存在于永久代,因此静态方法锁相当于该类的一个全局锁。
- 当作用在某一个对象实例时: 监视器(monitor)便是括号括起来的对象实例。
作用在对象方法时:
- Synchronized 用于类的普通方法上时,监视器(monitor)便是对象实例(this):当多个线程同时调用对象的被 Synchronized修饰的普通方法时,需要先获取当前的对象锁,才可以继续往下执行,当一个线程获取到对象锁时,其他线程就不能访问Synchronized示例对象,但是可以访问非 Synchronized 修饰的方法。
- 示例:
public class SynchronizedTest {
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
// 线程一
new Thread(new Runnable() {
public void run() {
test.methodA();
}
}).start();
// 线程二
new Thread(new Runnable() {
public void run() {
test.methodA();
}
}).start();
// 线程三
new Thread(new Runnable() {
public void run() {
test.methodB();
}
}).start();
// 线程四
new Thread(new Runnable() {
public void run() {
test.methodC();
}
}).start();
}
// synchronized
public synchronized void methodA(){
try {
System.out.println(Thread.currentThread().getName() +"开始执行 MethodA");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() +"结束执行 MethodA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// synchronized
public synchronized void methodB(){
try {
System.out.println(Thread.currentThread().getName() +"开始执行 MethodB");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() +"结束执行 MethodB");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 普通方法
public void methodC(){
try {
System.out.println(Thread.currentThread().getName() +"开始执行 MethodC");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() +"结束执行 MethodC");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
Thread-0开始执行 MethodA
Thread-3开始执行 MethodC
Thread-0结束执行 MethodA
Thread-2开始执行 MethodB
Thread-3结束执行 MethodC
Thread-2结束执行 MethodB
Thread-1开始执行 MethodA
Thread-1结束执行 MethodA
解释:
- 线程 Thread-0、1、2 分别调用同一个实例对象 test 中 synchronized 修饰的 methodA、methodA、methodB 线程1,2,3 需要依次获取到对象锁,然后执行对应的方法。
- 线程 Thread-3 由于执行的是普通方法,因此 Thread-0、1、2 对其执行并没有产生影响。
作用在静态方法时:
- 作用在静态方法上时,监视器作用的是 Class ,锁是Class,多线程运行时需要获取到当前的 Class 锁才可以继续执行。
- 例子:
public class SynchronizedTest {
public static void main(String[] args) {
// 线程一
new Thread(new Runnable() {
public void run() {
SynchronizedTest.methodA();
}
}).start();
// 线程二
new Thread(new Runnable() {
public void run() {
SynchronizedTest.methodA();
}
}).start();
// 线程三
new Thread(new Runnable() {
public void run() {
SynchronizedTest.methodB();
}
}).start();
// 线程四
new Thread(new Runnable() {
public void run() {
SynchronizedTest.methodC();
}
}).start();
}
// synchronized 静态方法
public static synchronized void methodA(){
try {
System.out.println(Thread.currentThread().getName() +"开始执行 static MethodA");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() +"结束执行 static MethodA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// synchronized 静态方法
public static synchronized void methodB(){
try {
System.out.println(Thread.currentThread().getName() +"开始执行 static MethodB");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() +"结束执行 static MethodB");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 普通静态方法
public static void methodC(){
try {
System.out.println(Thread.currentThread().getName() +"开始执行 static MethodC");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() +"结束执行 static MethodC");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 打印结果:
Thread-0开始执行 static MethodA
Thread-3开始执行 static MethodC
Thread-0结束执行 static MethodA
Thread-3结束执行 static MethodC
Thread-2开始执行 static MethodB
Thread-2结束执行 static MethodB
Thread-1开始执行 static MethodA
Thread-1结束执行 static MethodA
解释:
- 线程 Thread-0、1、2 分别调用同一个 SynchronizedTest 类中 synchronized 修饰的静态 methodA、methodA、methodB 线程1,2,3 需要依次获取到类锁,然后执行对应的方法。
- 线程 Thread-3 由于执行的是普通静态方法,因此 Thread-0、1、2 对其执行并没有产生影响。
作用在某一个对象实例 (比较常用):
- 当 synchronized 作用于到某个对象时,多个线程调用时需要获取到作用的对象锁才可以执行,其他线程进入等待待其释放对象锁后,获取到并执行。
- 例子:
public class SynchronizedTest {
// 对象锁
private Object lock = new Object();
public static void main(String[] args) {
final SynchronizedTest test = new SynchronizedTest();
// 线程一
new Thread(new Runnable() {
public void run() {
test.methodA();
}
}).start();
// 线程二
new Thread(new Runnable() {
public void run() {
test.methodB();
}
}).start();
}
public void methodA(){
synchronized (lock){
try {
System.out.println(Thread.currentThread().getName() +"开始执行 MethodA");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() +"结束执行 MethodA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void methodB(){
synchronized (lock) {
try {
System.out.println(Thread.currentThread().getName() + "开始执行 MethodB");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "结束执行 MethodB");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.执行结果:
Thread-0开始执行 MethodA
Thread-0结束执行 MethodA
Thread-1开始执行 MethodB
Thread-1结束执行 MethodB
- 解释,实例中定义了对象Object,线程一二 在执行到代码 synchronized (lock) 回去获取 或者 等待获取对象 lock 锁才可以继续往下执行。
- 在并发编程中,此方法较为常用,用于给几行比较核心的代码进行加锁,实现并发安全的问题。
synchronized 实现的原理: 待完成。
End: