易错点1:锁一个可变对象
package cn.enjoyedu.ch1.syn; /** * 类说明:错误的加锁和原因分析 */ public class TestIntegerSyn { public static void main(String[] args) throws InterruptedException { Worker worker=new Worker(1); for(int i=0;i<5;i++) { //注意:这里传入的是同1个worker,因此Worker类里面的Integer i 和 Object obj,5个线程是共享的 //不要误认为是5个worker,而是5个线程用的是同1个worker对象 new Thread(worker).start(); } } private static class Worker implements Runnable{ private Integer i;//5个线程共享 private Object obj = new Object();//5个线程共享 public Worker(Integer i) { this.i=i; } @Override public void run() { // 如果锁的是i: // >>>假设线程-0先拿到锁,进入同步代码块。其他4个线程被block在对象i=Integer(1)的锁上。由于i++装箱操作会return一个新的integer对象赋给i,此时被锁的i变为i=Integer(2) // 原本4个线程被block在对象i=Integer(1)的锁上(因为线程-0已经拿到锁),但是此时锁的对象变成了i=Integer(2),因此这4个线程可以重新竞争进入代码块。 // >>>当线程-0 i++后,不等它执行完同步代码块释放掉锁,线程-1就会直接进来操作。此时线程-1 拿的锁是线程-0加一之后的对象。。以此类推,等线程-1加一后,线程-2拿的锁是线程-1加一之后的对象 // >>>又因为5个线程共享i,因此,线程-0 /其他线程 sleep之前和之后输出的i会发生变化。因为在sleep的时候,别的线程进来操作了。 // // 如果锁的是obj: // >>>obj同样是5个线程共享的,但由于没有线程对其操作。因此5个线程始终在竞争同一把锁,可以互斥运行。 synchronized (i) { //obj Thread thread=Thread.currentThread(); System.out.println(thread.getName()+"-------@"+System.identityHashCode(i)); i++; System.out.println(thread.getName()+"-------"+i+"-@"+System.identityHashCode(i)); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(thread.getName()+"-------"+i+"--@"+System.identityHashCode(i)); } } } }
如果锁的是可变对象,i 。结果:5个线程会无法互斥。
Thread-0-------@129460851 Thread-0-------2-@158369013 Thread-2-------@158369013 Thread-2-------3-@1898749074 Thread-3-------@1898749074 Thread-3-------4-@1763156594 Thread-4-------@1763156594 Thread-4-------5-@825455695 Thread-4-------5--@825455695 Thread-2-------5--@825455695 Thread-0-------5--@825455695 Thread-3-------5--@825455695 Thread-1-------@825455695 Thread-1-------6-@1779415244 Thread-1-------6--@1779415244
修改成不可变对象, obj。结果:5个线程必须互斥执行,一个执行完了再执行下一个。
Thread-0-------@1898749074 Thread-0-------2-@2146135239 Thread-0-------2--@2146135239 Thread-4-------@2146135239 Thread-4-------3-@299052996 Thread-4-------3--@299052996 Thread-3-------@299052996 Thread-3-------4-@976006504 Thread-3-------4--@976006504 Thread-2-------@976006504 Thread-2-------5-@658235253 Thread-2-------5--@658235253 Thread-1-------@658235253 Thread-1-------6-@825455695 Thread-1-------6--@825455695
原因:虽然我们对 i 进行了加锁,但是
但是当我们反编译这个类的 class 文件后,可以看到 i++实际是,
本质上是返回了一个新的 Integer 对象。也就是每个线程实际加锁的是不同 的 Integer 对象。