0
点赞
收藏
分享

微信扫一扫

Java多线程编程-线程安全和锁Synchronized概念


线程安全

线程安全概念:多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。

线程安全问题都是由全局变量及静态变量引起的, 局部变量逃逸也可能导致线程安全问题。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

 

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 传入参数为不同对象实例, 可知 不同对象之间的 对象锁 互不影响


举报

相关推荐

0 条评论