并发编程主要设计两个关键字:一个是synchronized,另一个是volatile。下面主要讲解这两个关键字,并对这两个关机进行比较。
synchronized
synchronized是通过JMV种的monitorenter和monitorexit指令实现同步。monitorenter指令是在编译后插入到同步代码的开始位置,而monitorexit插入到同步代码的结束位置和异常位置。每一个对象都与一个monitor相关联,当monitor被只有后,它将处于锁定状态。
当一个线程试图访问同步代码时,它必须先获得锁;退出或者抛出异常时,必须释放锁。Java中,每一个对象都可以作为锁。具体的表现形式有3种:
- 对于普通的同步方法,锁是当前的实例对象(this对象)
权限修饰符 synchronized 返回值类型 函数名(形参列表..){ //函数体 }
- 对于静态同步方法,锁是当前类的Class对象
权限修饰符 static synchronized 返回值类型 函数名(形参列表..){ //函数体 }
- 对于同步方法块,锁是Synchronized括号中配置的对象
- 锁对象必须是多线程共享的对象,否则锁不住
Synchronized(锁){ //需要同步的代码块 }
注意:在同步代码块/同步方法中调用sleep()不会释放锁对象,调用wait()会释放锁对象
Synchronized提供了一种排他式的数据同步机制,某个线程在获取monitor lock的时候可能会被阻塞,而这种阻塞有两个明显的缺陷:1. 无法控制阻塞时长; 2. 阻塞不能被中断
public class SyncDefect { /** *线程休眠一个小时 */ public synchronized void syncMethod(){ try { TimeUnit.HOURS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { SyncDefect defect = new SyncDefect(); new Thread(defect::syncMethod,"t1").start(); //休眠3毫秒后启动线程t2,确保t1先进入同步方法 TimeUnit.MILLISECONDS.sleep(3); Thread t2 = new Thread(defect::syncMethod, "t2"); t2.start(); //休眠3毫秒后中断线程t2,确保t2已经启动 TimeUnit.MILLISECONDS.sleep(3); t2.interrupt(); System.out.println(t2.isInterrupted()); //true System.out.println(t2.getState()); //BLOCKED } }
针对synchronized的两个缺点,可以使用BooleanLock来解决
public interface Lock { void lock() throws InterruptedException; /** * 指定获取锁的超时时间 * @param mills 等待获取锁的最大时间 * @throws InterruptedException * @throws TimeoutException */ void lock(long mills) throws InterruptedException, TimeoutException; void unlock(); ListgetBlockedThreads(); }
public class BooleanLock implements Lock { /** * 记录取得锁的线程 */ private Thread currentThread; /** * Bollean开关,标志锁是否已经被获取 */ private boolean locked = false; private ListblockedList = new ArrayList<>(); @Override public void lock() { //使用同步代码块的方式获取锁 synchronized (this) { Thread currentThread = Thread.currentThread(); //当锁已经被某个线程获取,将当前线程加入阻塞队列,并使用this.wait()释放thisMonitor while (locked){ try { if(!blockedList.contains(currentThread)){ blockedList.add(currentThread); } this.wait(); } catch (InterruptedException e) { blockedList.remove(currentThread); e.printStackTrace(); } } blockedList.remove(currentThread); this.locked = true; this.currentThread = currentThread; } } @Override public void lock(long mills) throws InterruptedException, TimeoutException { synchronized (this){ if(mills <= 0) {//时间不合法,调用默认的lock() this.lock(); } else { long remainingMills = mills; long endMills = System.currentTimeMillis() + remainingMills; while (locked) { if (remainingMills <= 0) {//在指定的时间内未获取锁或者当前线程被其它线程唤醒,抛出异常 throw new TimeoutException(Thread.currentThread().getName()+" can't get lock during "+mills); } if(!blockedList.contains(Thread.currentThread())){ blockedList.add(Thread.currentThread()); } //等待remainingMills后重新尝试获取锁 this.wait(remainingMills); remainingMills = endMills - System.currentTimeMillis(); } blockedList.remove(Thread.currentThread()); this.locked = true; this.currentThread = Thread.currentThread(); } } } @Override public void unlock() { synchronized (this) { if(Thread.currentThread() == currentThread) { this.locked = false; this.notifyAll(); } } } @Override public List getBlockedThreads() { return Collections.unmodifiableList(blockedList); } }
/** * 测试阻塞中断 */ public class BooleanLockInterruptTest { private final Lock lock = new BooleanLock(); public void syncMethod() { try { lock.lock(); System.out.println(Thread.currentThread().getName()+" get lock."); TimeUnit.HOURS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("BLOCKED THREAD :"+lock.getBlockedThreads()); lock.unlock(); } } public static void main(String[] args) throws InterruptedException { BooleanLockInterruptTest test = new BooleanLockInterruptTest(); new Thread(test::syncMethod,"t1").start(); TimeUnit.MILLISECONDS.sleep(3); Thread t2 = new Thread(test::syncMethod, "t2"); t2.start(); TimeUnit.MILLISECONDS.sleep(3); t2.interrupt(); System.out.println(t2.isInterrupted()); //true System.out.println(t2.getState()); //RUNNABLE } }
/** * 测试超时 */ public class BooleanLockTimeOutTest { private final Lock lock = new BooleanLock(); public void syncTimeOutMethod() { try { lock.lock(1000); System.out.println(Thread.currentThread().getName()+" get lock."); TimeUnit.HOURS.sleep(1); } catch (InterruptedException | TimeoutException e) { e.printStackTrace(); } finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { BooleanLockTimeOutTest test = new BooleanLockTimeOutTest(); new Thread(test::syncTimeOutMethod,"t1").start(); TimeUnit.MILLISECONDS.sleep(3); new Thread(test::syncTimeOutMethod, "t2").start(); } }
针对是synhronized还有一些概念及相关知识点需要补充
-
Monitor
-
每一个对象都与一个Monitor相关联,一个monitor的lock在某一刻只能被一个线程获取。
-
monitor有一个计数器,当为0时,该monitor的lock未被获取;当有线程持获取monitor时,则monitor计数器加一,释放时减一。
-
Monitor分为This Monitor和Class Monitor。This Monitor对应类的实例方法,Class Monitor对应类的静态方法。
-
synchronized关键字实例方法时,争取的是同一个monitor的锁,与之关联的引用是ThisMonitor的实例引用。即: 同一个类中的不同多线程方法,使用的是同一个锁。
-
将静态方法声明为synchronized。该静态方法被调用后,对应的class对象将会被锁住(使用的是ClassMonitor)。其他线程无法调用该class对象的所有静态方法, 直到资源被释放。
-
Synchronized使用wait()进入条件对象的等待集,使用notifyAll()/notify()唤醒等待集中的线程。
public synchronized void transfer(int from , int to, double amount) throws InterruptedException{ while(accounts[from] < amount){ //wait on intrinsic object lock’s single condition wait(); } accounts[from] -= amount; accounts[to] += amount; //notify all threads waiting on the condition notifyAll(); }
volatile
volatile是轻量级的synchronized,它为实例域的同步访问提供了一种免锁机制,不会引起线程上下文的切换和调度。它在多处理器开发中保证了共享变量的“可见性“,如果一个属性被声明成volatile,Java模型会确保所有的线程看到这个变量的值时一致的。【volatile变量不能提供原子性】
volatile主要用来锁住一个属性,在对该属性的值进行写操作时,会将数据写回主存,并将CPU里缓存了该内存地址的数据无效。【线程在对volatile修饰的变量进行读写操作时,会首先检查线程缓存的值是否失效,如果失效,就会从主存中把数据读到线程缓存里】。 volatile本质上就是告诉JVM当前变量的值需要从主存中读取,当前变量的值被修改后直接刷新到主存中,且不需要被编译器优化(即:禁止命令重排)。
使用示范:
private volatile boolean done; public boolean isDone(){ return done; } public void setDone(boolean done){ this.done = done; } // Same as private boolean done; public synchronized boolean isDone(){ return done; } public synchronized void setDone(boolean done){ this.done = done; }
比较synchronized和volatile
|
volatile
|
synchronized
|
作用对象
|
实例变量、类变量
|
方法、代码块
|
原子性
|
不具备
|
具备
|
可见性
|
具备
|
具备
|
可见性原理
|
使用机器指令的方式迫使其它工作内存中的变量失效
|
利用monitor锁的排它性实现
|
是否会指令重排
|
否
|
是
|
是否造成线程阻塞
|
否
|
是
|