Java之多线程的同步和死锁

设计模式中的单例模式的懒汉方式会存在多线程的安全问题;通过以下测试代码可以看到两个线程中得到的并不是同一个单例对象;

    @Test
    public void unsafeSingleInstanceTest() throws InterruptedException {
        AtomicReference<UnSafeSingleInstance> s1 = new AtomicReference<>();
        AtomicReference<UnSafeSingleInstance> s2 = new AtomicReference<>();
        var t1 = new Thread(()->{
            s1.set(UnSafeSingleInstance.getInstance());
        });

        var t2 = new Thread(()->{
            s2.set(UnSafeSingleInstance.getInstance());
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("(s1 == s2) = " + (s1.get() == s2.get()));
        System.out.println("s1 = " + s1.get());
        System.out.println("s2 = " + s2.get());

//        (s1 == s2) = false
//        s1 = UnSafeSingleInstance@43a25848
//        s2 = UnSafeSingleInstance@3ac3fd8b
    }
    
    
class UnSafeSingleInstance{
    private static UnSafeSingleInstance instance = null;

    private UnSafeSingleInstance(){}

    public static UnSafeSingleInstance getInstance()  {
        if(instance == null){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            instance = new UnSafeSingleInstance();
        }

        return instance;
    }
}

我们可以通过在getInstance方法中使用synchronized添加同步代码块,同时进行双重的null检查避免无谓的锁占用;

    @Test
    public void safeSingleInstanceTest() throws InterruptedException {
        AtomicReference<SafeSingleInstance> s1 = new AtomicReference<>();
        AtomicReference<SafeSingleInstance> s2 = new AtomicReference<>();
        var t1 = new Thread(()->{
            s1.set(SafeSingleInstance.getInstance());
        });

        var t2 = new Thread(()->{
            s2.set(SafeSingleInstance.getInstance());
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("(s1 == s2) = " + (s1.get() == s2.get()));
        System.out.println("s1 = " + s1.get());
        System.out.println("s2 = " + s2.get());

//        (s1 == s2) = true
//        s1 = SafeSingleInstance@2133c8f8
//        s2 = SafeSingleInstance@2133c8f8
    }
    
class SafeSingleInstance{
    private static SafeSingleInstance instance = null;

    private SafeSingleInstance(){}

    public static SafeSingleInstance getInstance()  {
        if(instance == null){
            synchronized (SafeSingleInstance.class){
                if(instance == null){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                    instance = new SafeSingleInstance();
                }
            }
        }

        return instance;
    }
}

除了使用synchronized之外,也可以使用JUC提供的Lock来实现线程的同步;

    @Test
    public void safeSingleInstanceWithLockTest() throws InterruptedException {
        AtomicReference<SafeSingleInstanceWithLock> s1 = new AtomicReference<>();
        AtomicReference<SafeSingleInstanceWithLock> s2 = new AtomicReference<>();
        var t1 = new Thread(()->{
            s1.set(SafeSingleInstanceWithLock.getInstance());
        });

        var t2 = new Thread(()->{
            s2.set(SafeSingleInstanceWithLock.getInstance());
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("(s1 == s2) = " + (s1.get() == s2.get()));
        System.out.println("s1 = " + s1.get());
        System.out.println("s2 = " + s2.get());

//        (s1 == s2) = true
//        s1 = SafeSingleInstanceWithLock@43a25848
//        s2 = SafeSingleInstanceWithLock@43a25848
    }
    
class SafeSingleInstanceWithLock{
    private static volatile SafeSingleInstanceWithLock instance = null;

    private static Lock lock = new ReentrantLock();

    private SafeSingleInstanceWithLock(){}

    public static SafeSingleInstanceWithLock getInstance()  {
        if(instance == null){
            lock.lock();
            if(instance == null){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                instance = new SafeSingleInstanceWithLock();
            }
            lock.unlock();
        }

        return instance;
    }
}

多线程的同步可能会出现死锁;死锁的出现是由于发生死锁的两个线程持有彼此需要的锁资源,导致双方都无法执行只能等待;
以下测试代码以构成一个商品的两个组件为例,组装商品我们可以从APart开始,也可以从BPart开始;

    @Test
    public void deadThreadTest() throws InterruptedException {
        APart a = new APart();
        BPart b = new BPart();
        var aPartWorker =new Thread(){
            @Override
            public void run() {
                a.doWork(b);
            }
        };

        var bpartWorker =new Thread(){
            @Override
            public void run() {
                b.doWork(a);
            }
        };

        aPartWorker.start();
        bpartWorker.start();
        aPartWorker.join();
        bpartWorker.join();

//        APart do self step.
//        BPart do self step.
//        waiting a.doSelfStep.
//        waiting b.doSelfStep.
    }
    
class APart{
    public synchronized  void doWork(BPart b) {
        doSelfStep();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("waiting b.doSelfStep.");
        b.doSelfStep();
    }

    public synchronized  void doSelfStep(){
        System.out.println(this.getClass().getName() + " do self step.");
    }
}

class BPart{

    public synchronized  void doWork(APart a)  {
        doSelfStep();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("waiting a.doSelfStep.");
        a.doSelfStep();
    }

    public synchronized  void doSelfStep(){
        System.out.println(this.getClass().getName() + " do self step.");
    }
}
posted @ 2023-07-11 21:37  无风听海  阅读(6)  评论(0编辑  收藏  举报