相关文章:
在 Java 多线程编程中,我们需要重点关注一个问题,那就是线程安全问题,造成线程安全的原因,有以下两点
-
存在共享数据 (也称为临界资源)
-
存在多个线程共同操作共享数据
为了解决该问题,我们需要引进这么一个方法
- 当存在多个线程共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等待该线程处理完数据后再进行
此时,便引入了互斥锁的概念,其有以下两点特性
-
互斥性
-
即在同一时间内只允许一个线程持有某个锁,通过这种特性来实现多线程的协调机制,这样才能确保在同一时间内只有一个线程对需要同步的代码块 (复合操作) 进行访问
-
互斥性也称为操作的原子性
-
-
可见性
- 必须确保在锁被释放之前,对共享变量所作的修改,对于随后获得该锁的另一个线程是可见的 (即在获得锁时应获得共享变量最新的值),否则另一个线程可能在本地缓存的某个副本上继续操作,从而引起数据的不一致
对于 Java 而言,关键字 synchronized 满足了上述的要求
一、synchronized 的三种应用方式
-
修饰实例方法
-
synchronized method() (对象锁)
-
作用于当前实例对象加锁,进入同步区域需要获得当前实例对象的锁
-
-
修饰代码块
-
synchronized(this) (对象锁)
-
synchronized(实例对象) (对象锁)
-
synchronized(类.class) (类锁)
-
作用于指定对象加锁,进入同步区域需要获得指定对象的锁
-
-
修饰静态方法
-
synchronized static method() (类锁)
-
作用于当前类对象加锁,进入同步区域需要获得当前类对象的锁
-
二、举例说明
-
修饰实例方法
-
synchronized method() (对象锁)
public class SyncThread implements Runnable { @Override public void run() { syncObjectMethod(); } private synchronized void syncObjectMethod() { System.out.println(Thread.currentThread().getName() + "_syncObjectMethod: : " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); try { System.out.println(Thread.currentThread().getName() + "_syncObjectMethod_Start: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + "_syncObjectMethod_End: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } catch (Exception e) { e.printStackTrace(); } } }
public class SyncDemo { public static void main(String[] args) { SyncThread syncThread = new SyncThread(); Thread thread1 = new Thread(syncThread, "thread1"); Thread thread2 = new Thread(syncThread, "thread2"); thread1.start(); thread2.start(); // thread1_syncObjectMethod: : 15:28:36 // thread1_syncObjectMethod_Start: 15:28:36 // thread1_syncObjectMethod_End: 15:28:37 // thread2_syncObjectMethod: : 15:28:37 // thread2_syncObjectMethod_Start: 15:28:37 // thread2_syncObjectMethod_End: 15:28:38 } }
-
如上所示,synchronized 用于修饰实例方法,锁住的是同一个 syncThread 对象
-
整个实例方法内的逻辑会同步执行
-
因此当一个线程访问对象的同步方法时,另外访问该对象同步方法的线程将会被阻塞
-
-
-
修饰代码块
-
synchronized(this) (对象锁)
public class SyncThread implements Runnable { @Override public void run() { syncThisBlock(); } private void syncThisBlock() { System.out.println(Thread.currentThread().getName() + "_syncThisBlock: : " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); synchronized (this) { try { System.out.println(Thread.currentThread().getName() + "_syncThisBlock_Start: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + "_syncThisBlock_End: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } catch (Exception e) { e.printStackTrace(); } } } }
public class SyncDemo { public static void main(String[] args) { SyncThread syncThread = new SyncThread(); Thread thread1 = new Thread(syncThread, "thread1"); Thread thread2 = new Thread(syncThread, "thread2"); thread1.start(); thread2.start(); // thread1_syncThisBlock: : 15:40:24 // thread2_syncThisBlock: : 15:40:24 // thread1_syncThisBlock_Start: 15:40:24 // thread1_syncThisBlock_End: 15:40:25 // thread2_syncThisBlock_Start: 15:40:25 // thread2_syncThisBlock_End: 15:40:26 } }
-
如上所示,synchronized 用于修饰代码块,锁住的是同一个 this (即 syncThread 对象)
-
整个实例方法内,未在同步代码块内的逻辑会异步执行,在同步代码块内的逻辑会同步执行
-
因此当一个线程访问对象的同步代码块时,另外访问该对象同步代码块的线程将会被阻塞
-
-
synchronized(实例对象) (对象锁)
public class SyncThread implements Runnable { private Object lock = new Object(); @Override public void run() { syncObjectBlock(); } private void syncObjectBlock() { System.out.println(Thread.currentThread().getName() + "_syncObjectBlock: : " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); synchronized (lock) { try { System.out.println(Thread.currentThread().getName() + "_syncObjectBlock_Start: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + "_syncObjectBlock_End: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } catch (Exception e) { e.printStackTrace(); } } } }
public class SyncDemo { public static void main(String[] args) { SyncThread syncThread = new SyncThread(); Thread thread1 = new Thread(syncThread, "thread1"); Thread thread2 = new Thread(syncThread, "thread2"); thread1.start(); thread2.start(); // thread2_syncObjectBlock: : 16:11:44 // thread1_syncObjectBlock: : 16:11:44 // thread2_syncObjectBlock_Start: 16:11:44 // thread2_syncObjectBlock_End: 16:11:45 // thread1_syncObjectBlock_Start: 16:11:45 // thread1_syncObjectBlock_End: 16:11:46 } }
-
如上所示,synchronized 用于修饰代码块,锁住的是同一个 lock 对象
-
此外,锁住的 lock 对象必须为同一个对象,否则就会异步执行代码块了
-
整个实例方法内,未在同步代码块内的逻辑会异步执行,在同步代码块内的逻辑会同步执行
-
因此当一个线程访问对象的同步代码块时,另外访问该对象同步代码块的线程将会被阻塞
-
-
synchronized(类.class) (类锁)
public class SyncThread implements Runnable { @Override public void run() { syncClassBlock(); } private void syncClassBlock() { System.out.println(Thread.currentThread().getName() + "_syncClassBlock: : " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); synchronized (SyncThread.class) { try { System.out.println(Thread.currentThread().getName() + "_syncClassBlock_Start: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + "_syncClassBlock_End: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } catch (Exception e) { e.printStackTrace(); } } } }
public class SyncDemo { public static void main(String[] args) { SyncThread syncThread = new SyncThread(); Thread thread1 = new Thread(syncThread, "thread1"); Thread thread2 = new Thread(syncThread, "thread2"); thread1.start(); thread2.start(); // thread1_syncClassBlock: : 16:23:27 // thread2_syncClassBlock: : 16:23:27 // thread1_syncClassBlock_Start: 16:23:28 // thread1_syncClassBlock_End: 16:23:29 // thread2_syncClassBlock_Start: 16:23:29 // thread2_syncClassBlock_End: 16:23:30 } }
public class SyncDemo { public static void main(String[] args) { Thread thread1 = new Thread(new SyncThread(), "thread1"); Thread thread2 = new Thread(new SyncThread(), "thread2"); thread1.start(); thread2.start(); // thread2_syncClassBlock: : 16:24:47 // thread1_syncClassBlock: : 16:24:47 // thread2_syncClassBlock_Start: 16:24:47 // thread2_syncClassBlock_End: 16:24:49 // thread1_syncClassBlock_Start: 16:24:49 // thread1_syncClassBlock_End: 16:24:50 } }
-
如上所示,synchronized 用于修饰代码块,锁住的是同一个 class 对象
-
同一个类的不同对象使用类锁将会是同步的
-
整个实例方法内,未在同步代码块内的逻辑会异步执行,在同步代码块内的逻辑会同步执行
-
因此当一个线程访问对象的同步代码块时,另外访问该对象同步代码块的线程将会被阻塞
-
-
-
修饰静态方法
-
synchronized static method() (类锁)
public class SyncThread implements Runnable { @Override public void run() { syncClassMethod(); } private synchronized static void syncClassMethod() { System.out.println(Thread.currentThread().getName() + "_syncClassMethod: : " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); try { System.out.println(Thread.currentThread().getName() + "_syncClassMethod_Start: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + "_syncClassMethod_End: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } catch (Exception e) { e.printStackTrace(); } } }
public class SyncDemo { public static void main(String[] args) { SyncThread syncThread = new SyncThread(); Thread thread1 = new Thread(syncThread, "thread1"); Thread thread2 = new Thread(syncThread, "thread2"); thread1.start(); thread2.start(); // thread1_syncClassMethod: : 16:30:45 // thread1_syncClassMethod_Start: 16:30:45 // thread1_syncClassMethod_End: 16:30:46 // thread2_syncClassMethod: : 16:30:46 // thread2_syncClassMethod_Start: 16:30:46 // thread2_syncClassMethod_End: 16:30:47 } }
-
如上所示,synchronized 用于修饰静态方法,锁住的是同一个 class 对象
-
整个静态方法内的逻辑会同步执行
-
因此当一个线程访问对象的同步方法时,另外访问该对象同步方法的线程将会被阻塞
-
-
三、对比说明
-
synchronized method() (对象锁) 与 synchronized(this) (对象锁) 进行比较
public class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("A")) { syncThisBlock(); } else if (threadName.startsWith("B")) { syncObjectMethod(); } } private void syncThisBlock() { System.out.println(Thread.currentThread().getName() + "_syncThisBlock: : " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); synchronized (this) { try { System.out.println(Thread.currentThread().getName() + "_syncThisBlock_Start: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + "_syncThisBlock_End: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } catch (Exception e) { e.printStackTrace(); } } } private synchronized void syncObjectMethod() { System.out.println(Thread.currentThread().getName() + "_syncObjectMethod: : " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); try { System.out.println(Thread.currentThread().getName() + "_syncObjectMethod_Start: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + "_syncObjectMethod_End: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } catch (Exception e) { e.printStackTrace(); } } }
public class SyncDemo { public static void main(String[] args) { SyncThread syncThread = new SyncThread(); Thread A_thread = new Thread(syncThread, "A_thread"); Thread B_thread = new Thread(syncThread, "B_thread"); A_thread.start(); B_thread.start(); // A_thread_syncThisBlock: : 17:04:40 // B_thread_syncObjectMethod: : 17:04:40 // B_thread_syncObjectMethod_Start: 17:04:41 // B_thread_syncObjectMethod_End: 17:04:42 // A_thread_syncThisBlock_Start: 17:04:42 // A_thread_syncThisBlock_End: 17:04:43 } }
-
如上所示,synchronized 分别用来修饰 this 和实例方法,即两方法锁住的是同一个 syncThread 对象
-
因此当一个线程访问对象的同步代码块时,另外访问该对象同步方法的线程将会被阻塞,反之亦然
-
-
synchronized(类.class) (类锁) 与 synchronized static method() (类锁) 进行比较
public class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("A")) { syncClassBlock(); } else if (threadName.startsWith("B")) { syncClassMethod(); } } private void syncClassBlock() { System.out.println(Thread.currentThread().getName() + "_syncClassBlock: : " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); synchronized (SyncThread.class) { try { System.out.println(Thread.currentThread().getName() + "_syncClassBlock_Start: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + "_syncClassBlock_End: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } catch (Exception e) { e.printStackTrace(); } } } private synchronized static void syncClassMethod() { System.out.println(Thread.currentThread().getName() + "_syncClassMethod: : " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); try { System.out.println(Thread.currentThread().getName() + "_syncClassMethod_Start: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); TimeUnit.SECONDS.sleep(1); System.out.println(Thread.currentThread().getName() + "_syncClassMethod_End: " + LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"))); } catch (Exception e) { e.printStackTrace(); } } }
public class SyncDemo { public static void main(String[] args) { SyncThread syncThread = new SyncThread(); Thread A_thread = new Thread(syncThread, "A_thread"); Thread B_thread = new Thread(syncThread, "B_thread"); A_thread.start(); B_thread.start(); // A_thread_syncClassBlock: : 17:11:04 // B_thread_syncClassMethod: : 17:11:04 // B_thread_syncClassMethod_Start: 17:11:04 // B_thread_syncClassMethod_End: 17:11:05 // A_thread_syncClassBlock_Start: 17:11:05 // A_thread_syncClassBlock_End: 17:11:06 } }
-
如上所示,synchronized 分别用来修饰 类.class 和静态方法,即两方法锁住的是同一个 Class 对象
-
因此当一个线程访问对象的同步代码块时,另外访问该对象同步方法的线程将会被阻塞,反之亦然
-
四、归纳总结
-
当一个线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块
-
若锁住的是同一个对象,则当一个线程访问对象的同步方法时,另外访问该对象同步方法的线程将会被阻塞
-
若锁住的是同一个对象,则当一个线程访问对象的同步代码块时,另外访问该对象同步代码块的线程将会被阻塞
-
若锁住的是同一个对象,则当一个线程访问对象的同步代码块时,另外访问该对象同步方法的线程将会被阻塞,反之亦然
-
同一个类的不同对象的对象锁互不干扰
-
由于类锁也是一种特殊的对象锁,因此表现和上述 1、2、3、4 点一致;而由于一个类只会有一把对象锁 (类锁),所以同一个类的不同对象使用类锁将会是同步的
-
类锁和对象锁互不干扰