线程安全
线程安全概念:多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的, 局部变量逃逸也可能导致线程安全问题。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
synchronized 关键字总结
- 静态方法的锁属于类锁, 一个类中所有加锁的静态方法共用该锁, 多个 Share 类之间锁共用
- 非静态方法属于对象锁, 一个对象中所有加锁的非静态方法共用该锁, 多个 Share 对象实例之间互不影响
- 对于同一个类, 类锁和对象(类的实例)锁互不影响
- 一个加锁的静态方法执行不会影响一个加锁的非静态方法的执行.
- 一个加锁的静态方法执行, 另一个加锁的静态方法不能执行, 要等待持有锁的线程释放.
- 一个加锁的非静态方法执行, 另一个加锁的非静态方法不能执行, 要等待持有锁的线程释放.
程序验证
执行入口
public class AnswerApp {
public static void main(String[] args) throws InterruptedException {
Sharer sharer = new Sharer();
ThreadA threadA = new ThreadA(sharer);
threadA.setName("ThreadA");
ThreadB threadB = new ThreadB(sharer);
threadB.setName("ThreadB");
ThreadC threadC = new ThreadC(sharer);
threadC.setName("ThreadC");
threadA.start();
threadB.start();
threadC.start();
}
}
class ThreadA extends Thread {
private Sharer sharer;
public ThreadA(Sharer sharer) {
this.sharer = sharer;
}
@Override
public void run() {
sharer.printA();
}
}
class ThreadB extends Thread {
private Sharer sharer;
public ThreadB(Sharer sharer) {
this.sharer = sharer;
}
@Override
public void run() {
sharer.printB();
}
}
class ThreadC extends Thread {
private Sharer sharer;
public ThreadC(Sharer sharer) {
this.sharer = sharer;
}
@Override
public void run() {
sharer.printC();
}
}
synchronized 类锁
public class Sharer {
private final static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// Sharer 类锁
synchronized
public static void printA() {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入同步 printA 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开同步 printA 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
// Sharer 类锁
synchronized
public static void printB() {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入同步 printB 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开同步 printB 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
// Sharer 类锁
public void printC() {
synchronized (Sharer.class) {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入同步 printC 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开同步 printC 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
}
}
运行结果输出
2019-10-15 11:19:08 线程=[ThreadA]进入 printA 方法
2019-10-15 11:19:10 线程=[ThreadA]离开 printA 方法
2019-10-15 11:19:10 线程=[ThreadC]进入 printC 方法
2019-10-15 11:19:12 线程=[ThreadC]离开 printC 方法
2019-10-15 11:19:12 线程=[ThreadB]进入 printB 方法
2019-10-15 11:19:14 线程=[ThreadB]离开 printB 方法
结论
public synchronized static void myFun() {
// ...
}
// 等价于
public void myFun() {
synchronized (类名.class) {
// ...
}
}
由程序运行结果输出得:
printA、printC、printB 先后执行, 可知 三个方法共用一个锁(类锁)
synchronized 类锁 和对象锁
public class Sharer {
private final static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// Sharer 类锁
synchronized
public static void printA() {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printA 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printA 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
// Sharer 对象锁
synchronized
public void printB() {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printB 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printB 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
// Sharer 对象锁
public void printC() {
synchronized (this) {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printC 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printC 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
}
}
程序运行结果
2019-10-15 11:21:01 线程=[ThreadB]进入 printB 方法
2019-10-15 11:21:01 线程=[ThreadA]进入 printA 方法
2019-10-15 11:21:03 线程=[ThreadB]离开 printB 方法
2019-10-15 11:21:03 线程=[ThreadC]进入 printC 方法
2019-10-15 11:21:03 线程=[ThreadA]离开 printA 方法
2019-10-15 11:21:05 线程=[ThreadC]离开 printC 方法
结论
public synchronized void myFun() {
// ...
}
// 等价于
public void myFun() {
synchronized (this) {
// ...
}
}
由程序运行结果输出得:
printA、printB 方法同时执行, 可知 printB 和 printC 共用一个锁, 即 Sharer 对象锁
synchronized 类锁和多对象锁
public class Sharer {
private final static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// Object 可用其他对象, 如: Sharer 等
private final static Object OBJECT = new Object();
// Sharer 类锁
synchronized
public static void printA() {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printA 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printA 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
// Sharer 对象锁
synchronized
public void printB() {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printB 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printB 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
// OBJECT 对象锁
public void printC() {
synchronized (OBJECT) {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printC 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printC 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
}
}
程序运行结果
2019-10-15 11:25:33 线程=[ThreadB]进入 printB 方法
2019-10-15 11:25:33 线程=[ThreadC]进入 printC 方法
2019-10-15 11:25:33 线程=[ThreadA]进入 printA 方法
2019-10-15 11:25:35 线程=[ThreadC]离开 printC 方法
2019-10-15 11:25:35 线程=[ThreadB]离开 printB 方法
2019-10-15 11:25:35 线程=[ThreadA]离开 printA 方法
结论
由程序运行结果输出得:
printA、printB、printC 三个函数同时执行, 可知三个锁属于不同锁
不同对象实例共用类锁
public class AnswerApp {
public static void main(String[] args) throws InterruptedException {
// ThreadA、ThreadB、ThreadC 传入参数为不同对象
ThreadA threadA = new ThreadA(new Sharer());
threadA.setName("ThreadA");
ThreadB threadB = new ThreadB(new Sharer());
threadB.setName("ThreadB");
ThreadC threadC = new ThreadC(new Sharer());
threadC.setName("ThreadC");
threadA.start();
threadB.start();
threadC.start();
}
}
public class Sharer {
private final static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// Sharer 类锁
synchronized
public static void printA() {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printA 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printA 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
// Sharer 类锁
synchronized
public static void printB() {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printB 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printB 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
// Sharer 类锁
public void printC() {
synchronized (Sharer.class) {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printC 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printC 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
}
}
程序运行结果
2019-10-15 11:44:13 线程=[ThreadA]进入 printA 方法
2019-10-15 11:44:15 线程=[ThreadA]离开 printA 方法
2019-10-15 11:44:15 线程=[ThreadC]进入 printC 方法
2019-10-15 11:44:17 线程=[ThreadC]离开 printC 方法
2019-10-15 11:44:17 线程=[ThreadB]进入 printB 方法
2019-10-15 11:44:19 线程=[ThreadB]离开 printB 方法
结论
由程序运行结果输出得:
由于ThreadA、ThreadB、ThreadC 传入参数为不同对象实例, 可知不同对象实例共用一个类锁
不同对象实例不共用对象锁
public class Sharer {
private final static DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// Sharer 对象锁
synchronized
public void printA() {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printA 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printA 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
// Sharer 对象锁
synchronized
public void printB() {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printB 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printB 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
// Sharer 对象锁
public void printC() {
synchronized (this) {
System.out.println(MessageFormat.format("{0} 线程=[{1}]进入 printC 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(MessageFormat.format("{0} 线程=[{1}]离开 printC 方法",
LocalDateTime.now().format(DATE_FORMATTER), Thread.currentThread().getName()));
}
}
}
程序运行结果
2019-10-15 11:48:29 线程=[ThreadC]进入 printC 方法
2019-10-15 11:48:29 线程=[ThreadA]进入 printA 方法
2019-10-15 11:48:29 线程=[ThreadB]进入 printB 方法
2019-10-15 11:48:31 线程=[ThreadC]离开 printC 方法
2019-10-15 11:48:31 线程=[ThreadA]离开 printA 方法
2019-10-15 11:48:31 线程=[ThreadB]离开 printB 方法
结论
由程序运行结果输出得:
由于ThreadA、ThreadB、ThreadC 传入参数为不同对象实例, 可知 不同对象之间的 对象锁 互不影响