V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
hjchjc1993
V2EX  ›  问与答

求教,为什么这么做是线程不安全的?

  •  
  •   hjchjc1993 · 2019-03-06 14:12:32 +08:00 · 2729 次点击
    这是一个创建于 2093 天前的主题,其中的信息可能已经有所发展或是发生改变。
    public class DeadLock {
    	private Integer a = 0;
    	private Integer b = 0;
    
    	private void createDeadLock() {
    		Runnable first = () -> {
    			for (int i = 0; i < 100000; i++) {
    				synchronized(this.a) {
    					System.out.println("锁住 a");
    					this.a++;
    					synchronized(this.b) {
    						this.b++;
    					}
    					System.out.println("对 a、b 的处理完成:" + a + " " + b);
    				}
    			}
    		};
    
    		Runnable second = () -> {
    			for (int i = 0; i < 100000; i++) {
    				synchronized(this.b) {
    					System.out.println("锁住 b");
    					this.b++;
    					synchronized(this.a) {
    						this.a++;
    					}
    					System.out.println("对 a、b 的处理完成:" + a + " " + b);
    				}
    			}
    		};
    
    		Thread firstThread = new Thread(first);
    		Thread secondThread = new Thread(second);
    		firstThread.start();
    		secondThread.start();
    	}
    
    	public static void main(String[] args) {
    		DeadLock deadLock = new DeadLock();
    		deadLock.createDeadLock();
    	}
    }
    
    19 条回复    2019-03-06 18:40:51 +08:00
    hjchjc1993
        1
    hjchjc1993  
    OP
       2019-03-06 14:27:24 +08:00
    本来想搞一个死锁出来,运行了多次,结果是死锁没有出现,反而 a 和 b 的值表示这么做是线程不安全的,求教啊,别沉
    hjchjc1993
        2
    hjchjc1993  
    OP
       2019-03-06 14:29:33 +08:00
    再顶下~
    gaius
        3
    gaius  
       2019-03-06 14:41:42 +08:00
    输出语句没同步
    geelaw
        4
    geelaw  
       2019-03-06 14:48:04 +08:00   ❤️ 1
    Integer 对象是不可变对象,假设 x 是一个 Integer,则 x++ 等同于 x = new Integer(x.intValue() + 1),但是你锁住的对象是之前的对象,是 x 引用了谁改变了,而不是 x 引用的那个谁改变了。
    gtexpanse
        5
    gtexpanse  
       2019-03-06 14:53:42 +08:00
    ++不是原子操作外加对象引用其实变了,所以没有锁住吧?
    ipwx
        6
    ipwx  
       2019-03-06 14:55:06 +08:00
    你把两条 System.out.println 语句输出的内容做一下细微变化再看看。
    hjchjc1993
        7
    hjchjc1993  
    OP
       2019-03-06 15:32:02 +08:00
    @geelaw synchronized 貌似只能锁对象,那这种情况该怎么修改代码才能线程安全呢?
    gaius
        8
    gaius  
       2019-03-06 15:37:02 +08:00
    首先 Integer a =0; Integer b=0;是同一个对象,一把锁。synchronized 里的对象改变之后,原对象的锁就释放了。
    其实你锁的是数字。
    hjchjc1993
        9
    hjchjc1993  
    OP
       2019-03-06 15:54:54 +08:00
    @gaius 确实应该是没锁住,对 JVM 的内存模型还是不太清楚啊。
    ihavecat
        10
    ihavecat  
       2019-03-06 16:00:06 +08:00
    Integer -128 到 127 是放在缓存里的,你这种写法和 a 和 b 是指向同一地址的
    hjchjc1993
        11
    hjchjc1993  
    OP
       2019-03-06 16:13:50 +08:00
    @ihavecat 改为了
    private Integer a = new Integer(200);
    private Integer b = new Integer(200);
    最后的结果显示仍然不是线程安全的
    Malthael
        12
    Malthael  
       2019-03-06 16:35:11 +08:00
    把 b 改个对象,比如说 Double,锁对象时用 synchronized (Integer.class)和 synchronized (Double.class)
    Yuicon
        13
    Yuicon  
       2019-03-06 16:39:01 +08:00
    @hjchjc1993 写个包装类
    hjchjc1993
        14
    hjchjc1993  
    OP
       2019-03-06 16:47:03 +08:00
    @Malthael 其实用其它的写法是很容易做到线程安全的,我只是对这种写法上锁失败的原因表示好奇。。
    ihavecat
        15
    ihavecat  
       2019-03-06 16:48:07 +08:00
    synchronized 中锁住的对象不能被改变,在循环体内进行++操作后,对象变了,各自没锁住,就没法死锁了,建议用字符串或者 AtomicInteger 试试
    @hjchjc1993
    brainfxxk
        16
    brainfxxk  
       2019-03-06 16:58:13 +08:00   ❤️ 1
    Integer 类的问题 你把锁换成 private final Object 累加的数值分别加到一个 int/Integer 字段上 再试试
    hjchjc1993
        17
    hjchjc1993  
    OP
       2019-03-06 17:11:41 +08:00
    这样写就没问题了。原来的问题可能确实出现在没有锁 final 对象,自增改变了对象,所以没锁住。
    下面这么样写就死锁了。。。

    public class DeadLockProblem {

    private final Counter counter1 = new Counter();
    private final Counter counter2 = new Counter();

    private void createDeadLock() {
    Runnable first = () -> {
    for (int i = 0; i < 100000; i++) {
    synchronized(counter1) {
    System.out.println("锁住 counter1");
    counter1.addOne();
    synchronized(counter2) {
    counter2.addOne();
    }
    System.out.println("对 counter1、counter2 的处理完成:" + counter1.getCount() + " " + counter2.getCount());
    }
    }
    };

    Runnable second = () -> {
    for (int i = 0; i < 100000; i++) {
    synchronized(counter2) {
    System.out.println("锁住 counter2");
    counter2.addOne();
    synchronized(counter1) {
    counter1.addOne();
    }
    System.out.println("对 counter1、counter2 的处理完成:" + counter1.getCount() + " " + counter2.getCount());
    }
    }
    };

    Thread firstThread = new Thread(first);
    Thread secondThread = new Thread(second);
    firstThread.start();
    secondThread.start();
    }

    public static void main(String[] args) {
    DeadLockProblem deadLock = new DeadLockProblem();
    deadLock.createDeadLock();
    }
    }

    class Counter {
    private int count = 0;

    void addOne() {
    count++;
    }

    int getCount() {
    return count;
    }
    }
    hjchjc1993
        18
    hjchjc1993  
    OP
       2019-03-06 17:56:09 +08:00
    @brainfxxk 谢谢 正解
    MachineSpirit
        19
    MachineSpirit  
       2019-03-06 18:40:51 +08:00 via Android
    我一直以为 synchronized 锁的是对象的引用。想了想只锁引用也确实不安全。应该是对象和引用都被锁了吧?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1031 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 20:27 · PVG 04:27 · LAX 12:27 · JFK 15:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.