锁规则:解锁必然发生在随后的加锁之前
) Synchronized
和 Lock
线程A释放锁后,会将共享变更操作刷新到主内存中
线程B获取锁时,JMM会将该线程的本地内存置为无效,被监视器保护的临界区代码必须从主内存中读取共享变量
补充: 使用同步代码块的好处在于其他线程仍可以访问非synchronized(this)的同步代码块
/** * 先定义一个测试模板类 * 这里补充一个知识点:Thread.sleep(long)不会释放锁 * 读者可参见笔者的`并发番@Thread一文通` */ public class SynchronizedDemo { public static synchronized void staticMethod(){ System.out.println(Thread.currentThread().getName() + "访问了静态同步方法staticMethod"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束访问静态同步方法staticMethod"); } public static void staticMethod2(){ System.out.println(Thread.currentThread().getName() + "访问了静态同步方法staticMethod2"); synchronized (SynchronizedDemo.class){ System.out.println(Thread.currentThread().getName() + "在staticMethod2方法中获取了SynchronizedDemo.class"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void synMethod(){ System.out.println(Thread.currentThread().getName() + "访问了同步方法synMethod"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束访问同步方法synMethod"); } public synchronized void synMethod2(){ System.out.println(Thread.currentThread().getName() + "访问了同步方法synMethod2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束访问同步方法synMethod2"); } public void method(){ System.out.println(Thread.currentThread().getName() + "访问了普通方法method"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "结束访问普通方法method"); } private Object lock = new Object(); public void chunkMethod(){ System.out.println(Thread.currentThread().getName() + "访问了chunkMethod方法"); synchronized (lock){ System.out.println(Thread.currentThread().getName() + "在chunkMethod方法中获取了lock"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public void chunkMethod2(){ System.out.println(Thread.currentThread().getName() + "访问了chunkMethod2方法"); synchronized (lock){ System.out.println(Thread.currentThread().getName() + "在chunkMethod2方法中获取了lock"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public void chunkMethod3(){ System.out.println(Thread.currentThread().getName() + "访问了chunkMethod3方法"); //同步代码块 synchronized (this){ System.out.println(Thread.currentThread().getName() + "在chunkMethod3方法中获取了this"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public void stringMethod(String lock){ synchronized (lock){ while (true){ System.out.println(Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
当一个线程进入同步方法时,其他线程可以正常访问其他非同步方法
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread thread1 = new Thread(() -> { //调用普通方法 synDemo.method(); }); Thread thread2 = new Thread(() -> { //调用同步方法 synDemo.synMethod(); }); thread1.start(); thread2.start(); } --------------------- //输出: Thread-1访问了同步方法synMethod Thread-0访问了普通方法method Thread-0结束访问普通方法method Thread-1结束访问同步方法synMethod //分析:通过结果可知,普通方法和同步方法是非阻塞执行的
当一个线程执行同步方法时,其他线程不能访问任何同步方法
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread thread1 = new Thread(() -> { synDemo.synMethod(); synDemo.synMethod2(); }); Thread thread2 = new Thread(() -> { synDemo.synMethod2(); synDemo.synMethod(); }); thread1.start(); thread2.start(); } --------------------- //输出: Thread-0访问了同步方法synMethod Thread-0结束访问同步方法synMethod Thread-0访问了同步方法synMethod2 Thread-0结束访问同步方法synMethod2 Thread-1访问了同步方法synMethod2 Thread-1结束访问同步方法synMethod2 Thread-1访问了同步方法synMethod Thread-1结束访问同步方法synMethod //分析:通过结果可知,任务的执行是阻塞的,显然Thread-1必须等待Thread-0执行完毕之后才能继续执行
当同步代码块都是同一个锁时,方法可以被所有线程访问,但同一个锁的同步代码块同一时刻只能被一个线程访问
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread thread1 = new Thread(() -> { //调用同步块方法 synDemo.chunkMethod(); synDemo.chunkMethod2(); }); Thread thread2 = new Thread(() -> { //调用同步块方法 synDemo.chunkMethod(); synDemo.synMethod2(); }); thread1.start(); thread2.start(); } --------------------- //输出: Thread-0访问了chunkMethod方法 Thread-1访问了chunkMethod方法 Thread-0在chunkMethod方法中获取了lock ...停顿等待... Thread-1在chunkMethod方法中获取了lock ...停顿等待... Thread-0访问了chunkMethod2方法 Thread-0在chunkMethod2方法中获取了lock ...停顿等待... Thread-1访问了chunkMethod2方法 Thread-1在chunkMethod2方法中获取了lock //分析可知: //1.对比18行和19行可知,即使普通方法有同步代码块,但方法的访问是非阻塞的,任何线程都可以自由进入 //2.对比20行、22行以及25行和27行可知,对于同一个锁的同步代码块的访问一定是阻塞的
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread thread1 = new Thread(() -> { //调用同步块方法 synDemo.chunkMethod(); synDemo.chunkMethod2(); }); Thread thread2 = new Thread(() -> { //调用同步块方法 synDemo.chunkMethod2(); synDemo.chunkMethod(); }); thread1.start(); thread2.start(); } --------------------- //输出: Thread-0访问了chunkMethod方法 Thread-1访问了chunkMethod2方法 Thread-0在chunkMethod方法中获取了lock ...停顿等待... Thread-0访问了chunkMethod2方法 Thread-1在chunkMethod2方法中获取了lock ...停顿等待... Thread-1访问了chunkMethod方法 Thread-0在chunkMethod2方法中获取了lock ...停顿等待... Thread-1在chunkMethod方法中获取了lock //分析可知: //现象:对比20行、22行和24行、25行可知,虽然是同一个lock对象,但其不同代码块的访问是非阻塞的 //原因:根源在于锁的释放和重新竞争,当Thread-0访问完chunkMethod方法后会先释放锁,这时Thread-1就有机会能获取到锁从而优先执行,依次类推到24行、25行时,Thread-0又重新获取到锁优先执行了 //注意:但有一点是必须的,对于同一个锁的同步代码块的访问一定是阻塞的 //补充:同步方法之所有会被全部阻塞,是因为synDemo对象一直被线程在内部把持住就没释放过,论把持住的重要性!
3.2.3
原则 3.2.2
和 3.2.3
原则 public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread thread1 = new Thread(() -> synDemo.chunkMethod() ); Thread thread2 = new Thread(() -> synDemo.chunkMethod3()); Thread thread3 = new Thread(() -> staticMethod()); Thread thread4 = new Thread(() -> staticMethod2()); thread1.start(); thread2.start(); thread3.start(); thread4.start(); } --------------------- //输出: Thread-1访问了chunkMethod3方法 Thread-1在chunkMethod3方法中获取了this Thread-2访问了静态同步方法staticMethod Thread-0访问了chunkMethod方法 Thread-0在chunkMethod方法中获取了lock Thread-3访问了静态同步方法staticMethod2 ...停顿等待... Thread-2结束访问静态同步方法staticMethod Thread-3在staticMethod2方法中获取了SynchronizedDemo.class //分析可知: //现象:对比16行、18行和24行、25行可知,虽然是同一个lock对象,但其不同代码块的访问是非阻塞的 //原因:根源在于锁的释放和重新竞争,当Thread-0访问完chunkMethod方法后会先释放锁,这时Thread-1就有机会能获取到锁从而优先执行,依次类推到24行、25行时,Thread-0又重新获取到锁优先执行了
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread thread1 = new Thread(() -> { synDemo.synMethod(); synDemo.synMethod2(); }); Thread thread2 = new Thread(() -> { synDemo.synMethod2(); synDemo.synMethod(); }); thread1.start(); thread2.start(); } --------------------- //输出: Thread-0访问了同步方法synMethod Thread-0结束访问同步方法synMethod Thread-0访问了同步方法synMethod2 Thread-0结束访问同步方法synMethod2 Thread-1访问了同步方法synMethod2 Thread-1结束访问同步方法synMethod2 Thread-1访问了同步方法synMethod Thread-1结束访问同步方法synMethod //分析:对比16行和18行可知,在代码块中继续调用了当前实例对象的另外一个同步方法,再次请求当前实例锁时,将被允许,进而执行方法体代码,这就是重入锁最直接的体现
public static void main(String[] args) { SynchronizedDemo synDemo = new SynchronizedDemo(); Thread thread1 = new Thread(() -> synDemo.stringMethod("sally")); Thread thread2 = new Thread(() -> synDemo.stringMethod("sally")); thread1.start(); thread2.start(); } --------------------- //输出: Thread-0 Thread-0 Thread-0 Thread-0 ...死循环... //分析:输出结果永远都是Thread-0的死循环,也就是说另一个线程,即Thread-1线程根本不会运行 //原因:同步块中的锁是同一个字面量
public class SynchronizedDemo { static Integer i = 0; //Integer是final Class public static void main(String[] args) throws InterruptedException { Runnable runnable = new Runnable() { @Override public void run() { for (int j = 0;j<10000;j++){ synchronized (i){ i++; } } } }; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(i); } } --------------------- //输出: 14134 //分析:跟预想中的20000不一致,当使用Integer作为对象锁时但还有计算操作就会出现并发问题
我们通过反编译发现执行i++操作相当于执行了i = Integer.valueOf(i.intValue()+1)
通过查看Integer的valueOf方法实现可知,其每次都new了一个新的Integer对象,锁变了有木有!!!
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); //每次都new一个新的锁有木有!!! }
public static void main(String[] args) { Object lock = new Object(); Object lock2 = new Object(); Thread thread1 = new Thread(() -> { synchronized (lock){ System.out.println(Thread.currentThread().getName() + "获取到lock锁"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2){ System.out.println(Thread.currentThread().getName() + "获取到lock2锁"); } } }); Thread thread2 = new Thread(() -> { synchronized (lock2){ System.out.println(Thread.currentThread().getName() + "获取到lock2锁"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock){ System.out.println(Thread.currentThread().getName() + "获取到lock锁"); } } }); thread1.start(); thread2.start(); } --------------------- //输出: Thread-1获取到lock2锁 Thread-0获取到lock锁 ..... //分析:线程0获得lock锁,线程1获得lock2锁,但之后由于两个线程还要获取对方已持有的锁,但已持有的锁都不会被双方释放,线程"假死",无法往下执行,从而形成死循环,即死锁,之后一直在做无用的死循环,严重浪费系统资源
我们用 jstack 查看一下这个任务的各个线程运行情况,可以发现两个线程都被阻塞 BLOCKED
我们很明显的发现,Java-level=deadlock,即死锁,两个线程相互等待对方的锁
Synchronized一文通(1.8版) 由 黄志鹏kira 创作,采用 知识共享 署名-非商业性使用 4.0 国际 许可协议 进行许可。