多线程 - 错误的加锁场景

Posted frankcui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程 - 错误的加锁场景相关的知识,希望对你有一定的参考价值。

易错点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 对象。

以上是关于多线程 - 错误的加锁场景的主要内容,如果未能解决你的问题,请参考以下文章

并发下常见的加锁及锁的PHP具体实现-转载

关于java多线程任务执行时共享资源加锁的方式思考

java多线程synchronized底层实现

到底什么是分布式锁,进程锁,线程锁

并发下常见的加锁及锁的PHP具体实现-转载

一步一步实现读写锁