ps:本文是转载文章,阅读原文可以获取源码,文章末尾有原文链接
synchronized 翻译过来是“同步”的意思,在多线程使用中相当于一把锁,synchronized的底层是使用操作系统的 mutex lock 实现的;锁具备内存可见性和操作原子性,内存可见性就是一个线程对共享变量值的修改,能够及时的被其他线程看到;操作原子性就是持有同一个锁的两个同步块或者两个以上只能串行地进入;那线程读取锁或者释放锁,JMM 都是做哪些操作呢?是这样的,当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中;当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量;目前synchronized 应用场景有4种,下面对它进行一一列举。
1、修饰一个代码块,其作用的范围是大括号括起来的代码,被修饰的代码块也称为同步语句块,它作用的对象是调用同步语句块的对象;假设当一个线程调用当前对象的同步语句块A时,其他线程调用当前对象同步语句块A**或者其他同步语句块B时要等待,注意这里说的当前对象是同一个对象。
**
ps:代码是在 eclipse 上写的,编码为 UTF-8
(1)新建一个 MyObject 类:
public class MyObject {
public void run(long sleepTime) {
synchronized (this) {
try {
Thread.sleep(sleepTime);
String threadName = Thread.currentThread().getName();
String log = "当前线程为---" + threadName;
System.out.println(log);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run2(long sleepTime) {
synchronized (this) {
try {
Thread.sleep(sleepTime);
String threadName = Thread.currentThread().getName();
String log = "当前线程为---" + threadName;
System.out.println(log);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(2)新建一个线程类 MyThreadA:
public class MyThreadA extends Thread {
private long mSleepTime = 0;
private MyObject myObject = null;
public MyThreadA(String name,long sleepTime,MyObject myObject) {
super(name);
this.mSleepTime = sleepTime;
this.myObject = myObject;
}
@Override
public void run() {
super.run();
if (myObject != null) {
myObject.run(mSleepTime);
}
}
}
(3)再新建一个线程类 MyThreadB:
public class MyThreadB extends Thread{
private long mSleepTime = 0;
private MyObject myObject = null;
public MyThreadB(String name,long sleepTime,MyObject myObject) {
super(name);
this.mSleepTime = sleepTime;
this.myObject = myObject;
}
@Override
public void run() {
super.run();
if (myObject != null) {
myObject.run2(mSleepTime);
}
}
}
(4)在主程序入口启动 MyThreadA 和 MyThreadB 线程:
MyObject myObject = new MyObject();
MyThreadA myThreadA = new MyThreadA("MyThreadA", 3000, myObject);
MyThreadB myThreadB = new MyThreadB("MyThreadB", 500, myObject);
try {
myThreadA.start();
Thread.sleep(100);
myThreadB.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
日志打印如下所示:
当前线程为---MyThreadA
当前线程为---MyThreadB
这里 MyThreadA 和 MyThreadB 2个线程调用同一个对象不同的方法中的同步代码块,MyThreadA 比 MyThreadB 优先调用 MyObject 对象的方法,虽然 MyThreadA 调用 MyObject 对象的方法休眠时间比 MyThreadB 调用 MyObject 对象的方法休眠时间长,但是 MyThreadA 获得 MyObject 对象锁,所以等 MyObject 对象的 run 中的同步语句块执行完后 MyThreadA 才释放锁 ,MyThreadB 才能获得锁执行当前线程的同步语句块,所以是 MyThreadA 线程的日志先打印。
2、修饰一个类,作用的这个类的所有对象,其作用范围是 synchronized 后面括号括起来的部分。
假设修饰的是一个类叫 A,如果一个线程 t1 正在执行 synchronized(A.class){代码段1}且比较耗时,另外一个线程 t2 试图执行 synchronized(A.class){代码段1} 或者synchronized(A.class){代码段2} 都要进行等待,等 t1 中 synchronized 修饰的类的代码块执行后释放锁,t2 才能获取锁执行 synchronized 修饰 A 类的代码块。
(1)在 MyObject 类的基础上添加 run3 和 run4 方法:
public void run3(long sleepTime) {
synchronized (MyObject.class) {
try {
Thread.sleep(sleepTime);
String threadName = Thread.currentThread().getName();
String log = "当前线程为---" + threadName;
System.out.println(log);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run4(long sleepTime) {
synchronized (MyObject.class) {
try {
Thread.sleep(sleepTime);
String threadName = Thread.currentThread().getName();
String log = "当前线程为---" + threadName;
System.out.println(log);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
(2)新建一个线程类 MyThreadA2:
public class MyThreadA2 extends Thread{
private long mSleepTime = 0;
private MyObject myObject = null;
public MyThreadA2(String name,long sleepTime,MyObject myObject) {
super(name);
this.mSleepTime = sleepTime;
this.myObject = myObject;
}
@Override
public void run() {
super.run();
if (myObject != null) {
myObject.run3(mSleepTime);
}
}
}
(3)新建一个线程类 MyThreadB2:
public class MyThreadB2 extends Thread{
private long mSleepTime = 0;
private MyObject myObject = null;
public MyThreadB2(String name,long sleepTime,MyObject myObject) {
super(name);
this.mSleepTime = sleepTime;
this.myObject = myObject;
}
@Override
public void run() {
super.run();
if (myObject != null) {
myObject.run4(mSleepTime);
}
}
}
(4)在主程序入口启动 MyThreadA2 和 MyThreadB2 线程:
MyObject myObject = new MyObject();
MyObject myObject2 = new MyObject();
MyThreadA2 myThreadA2 = new MyThreadA2("MyThreadA2", 3000, myObject);
MyThreadB2 myThreadB2 = new MyThreadB2("MyThreadB2", 100, myObject2);
try {
myThreadA2.start();
Thread.sleep(100);
myThreadB2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
日志打印如下所示:
当前线程为---MyThreadA2
当前线程为---MyThreadB2
这里先用 MyThreadA2 线程调用完 myObject 的 run3 方法,随后再到 MyThreadB2 线程执行 myObject2 的 run4 方法中的代码块;虽然2个线程不同对象中不同方法,但是由于它们访问的方法里都有 synchronized 修饰 MyObject.class,所以 MyThreadB2 执行到 synchronized 修饰 MyObject.class 的代码块时,先由 MyThreadA2 释放锁才能获取锁进行执行 synchronized 修饰 MyObject.class 的代码块。
**3、修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。
**
如果有一个类名叫 A,它有2个静态方法 synchronized static void method1(){} 和 synchronized static void method2(){},一个线程执行 A.method1() 方法时,另一个线程执行 new A().method1() 或者 A.method2() 方法都要进行阻塞。
(1)在 MyObject 类原有的基础上添加3个方法 run5 、run6 和 runPublic:
public synchronized static void run5(long sleepTime) {
runPlublic(sleepTime);
}
public synchronized static void run6(long sleepTime) {
runPlublic(sleepTime);
}
private static void runPlublic(long sleepTime) {
try {
Thread.sleep(sleepTime);
String threadName = Thread.currentThread().getName();
String log = "当前线程为---" + threadName;
System.out.println(log);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
(2)新建一个线程类 MyThreadA3:
public class MyThreadA3 extends Thread{
private long mSleepTime = 0;
public MyThreadA3(String name,long sleepTime) {
super(name);
this.mSleepTime = sleepTime;
}
@Override
public void run() {
super.run();
MyObject.run5(mSleepTime);
}
}
(3)新建一个线程类 MyThreadB3:
public class MyThreadB3 extends Thread{
private long mSleepTime = 0;
private MyObject myObject = null;
public MyThreadB3(String name,long sleepTime,MyObject myObject) {
super(name);
this.mSleepTime = sleepTime;
this.myObject = myObject;
}
@SuppressWarnings("static-access")
@Override
public void run() {
super.run();
if (myObject != null) {
myObject.run5(mSleepTime);
}
}
}
(4)新建一个线程类 MyThreadC3:
public class MyThreadC3 extends Thread{
private long mSleepTime = 0;
public MyThreadC3(String name,long sleepTime) {
super(name);
this.mSleepTime = sleepTime;
}
@Override
public void run() {
super.run();
MyObject.run6(mSleepTime);
}
}
(5)在程序入口调用 MyThreadA3、MyThreadB3 和 MyThreadC3:
MyObject myObject = new MyObject();
Thread myThreadA3 = new MyThreadA3("MyThreadA3", 3000);
Thread myThreadB3 = new MyThreadB3("MyThreadB3", 500,myObject);
Thread myThreadC3 = new MyThreadC3("MyThreadC3", 100);
try {
myThreadA3.start();
Thread.sleep(100);
myThreadB3.start();
Thread.sleep(100);
myThreadC3.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
日志打印如下所示:
当前线程为---MyThreadA3
当前线程为---MyThreadC3
当前线程为---MyThreadB3
这里先开启线程 MyThreadA3 然后每间隔100 ms 开启 MyThreadB3 和 MyThreadC3;线程 MyThreadA3 调用的是 MyObject.run5 方法,休眠时间是最长的,虽然 MyThreadB3 是用 MyObject 对象调用 run5 方法,但对象也属于 MyObject 类,所以 MyThreadB3 会被阻塞;而 MyThreadC3 调用的是 MyObject.run6 方法,该方法用 synchronized 修饰,属于 MyObject 的静态方法,所以 MyThreadC3 线程也会被阻塞。
4、修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
如果有一个类名叫 A,我们把它进行实例化 A a = new A(),它有2个方法 synchronized void method1(){} 和 synchronized void method2(){},当一个线程调用 a.method1() 方法时,另一个线程调用a.method1() 或者a.method2() 方法都会被阻塞。
(1)在 MyObject 类的基础上添加2个方法,名叫 run7 和 run8:
public synchronized void run7(long sleepTime) {
runPlublic(sleepTime);
}
public synchronized void run8(long sleepTime) {
runPlublic(sleepTime);
}
(2)新建一个线程类 MyThreadA4:
public class MyThreadA4 extends Thread{
private long mSleepTime = 0;
private MyObject myObject = null;
public MyThreadA4(String name,long sleepTime,MyObject myObject) {
super(name);
this.mSleepTime = sleepTime;
this.myObject = myObject;
}
@Override
public void run() {
super.run();
if (myObject != null) {
myObject.run7(mSleepTime);
}
}
}
(3)新建一个线程类 MyThreadB4:
public class MyThreadB4 extends Thread{
private long mSleepTime = 0;
private MyObject myObject = null;
public MyThreadB4(String name,long sleepTime,MyObject myObject) {
super(name);
this.mSleepTime = sleepTime;
this.myObject = myObject;
}
@Override
public void run() {
super.run();
if (myObject != null) {
myObject.run7(mSleepTime);
}
}
}
(4)新建一个线程类 MyThreadC4:
public class MyThreadC4 extends Thread{
private long mSleepTime = 0;
private MyObject myObject = null;
public MyThreadC4(String name,long sleepTime,MyObject myObject) {
super(name);
this.mSleepTime = sleepTime;
this.myObject = myObject;
}
@Override
public void run() {
super.run();
if (myObject != null) {
myObject.run8(mSleepTime);
}
}
}
(5)在程序入口调用 MyThreadA4、MyThreadB4 和 MyThreadC4:
MyObject myObject = new MyObject();
Thread myThreadA4 = new MyThreadA4("MyThreadA4", 3000,myObject);
Thread myThreadB4 = new MyThreadB4("MyThreadB4", 500,myObject);
Thread myThreadC4 = new MyThreadC4("MyThreadC4", 100,myObject);
try {
myThreadA4.start();
Thread.sleep(100);
myThreadB4.start();
Thread.sleep(100);
myThreadC4.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
日志打印如下所示:
当前线程为---MyThreadA4
当前线程为---MyThreadC4
当前线程为---MyThreadB4
这里一开始先启动 MyThreadA4 线程,然后每间隔 100ms 启动 MyThreadB4 和 MyThreadC4;MyThreadB4 和 MyThreadA4 调用的是同一个对象的同一个 synchronized 修饰的方法,MyThreadB4 启动在后,所以被阻塞;虽然 MyThreadC4 和 MyThreadA4 调用的是同一个对象的不同的 synchronized 修饰的方法,但由于 synchronized 作用的是同一个对象, MyThreadC4 又启动在后,所以也被阻塞。