Java:并发编程之ReentrantLock类和Condition类使用介绍
  ndh0xMjNGcR6 2023年11月02日 39 0

从 JDK 1.5 开始,引入了一个高级的处理并发的java.util.concurrent包,它提供了大量更高级的并发功能,能大大的简化多线程程序的编写。

java.util.concurrent.locks包提供的ReentrantLock类,一个可重入的互斥锁,它具有与使用synchronized加锁一样的特性,并且功能更加强大。

(目录)

Counter接口

package com.example.demo.thread;

public interface Counter {
    void add();

    int getCount();
}

不加锁的 Counter实现

package com.example.demo.thread.impl;

import com.example.demo.thread.Counter;

/**
 * 不加锁实现
 */
public class NonLockCounter implements Counter {
    private int count;

    public void add() {
        // 延迟操作
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        count++;
    }

    public int getCount() {
        return this.count;
    }
}

测试类

package com.example.demo.thread;

import com.example.demo.thread.impl.SynchronizedCounter;
import org.junit.Test;

public class ThreadTests {
    @Test
    public void testCounter() throws InterruptedException {
        Counter counter = new NonLockCounter();

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    counter.add();
                }
            }).start();
        }

        Thread.sleep(1000);

        System.out.println(counter.getCount());
    }
}

多次测试结果

97
98
100
99
97

synchronized 加锁实现

package com.example.demo.thread.impl;

import com.example.demo.thread.Counter;

/**
 * synchronized 加锁实现
 */
public class SynchronizedCounter implements Counter {
    private int count;

    public synchronized void add() {
        // 延迟操作
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
    }

    public int getCount() {
        return this.count;
    }
}

测试类

package com.example.demo.thread;

import com.example.demo.thread.impl.NonLockCounter;
import com.example.demo.thread.impl.SynchronizedCounter;
import org.junit.Test;

public class ThreadTests {
    @Test
    public void testSynchronizedCounter() throws InterruptedException {
        Counter counter = new SynchronizedCounter();

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    counter.add();
                }
            }).start();
        }

        Thread.sleep(1000);

        System.out.println(counter.getCount());
    }


}

多次测试结果

100
100
100
100
100

ReentrantLock 加锁实现

package com.example.demo.thread.impl;

import com.example.demo.thread.Counter;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock 加锁实现
 */
public class ReentrantLockCounter implements Counter {
    private int count;

    private final Lock lock = new ReentrantLock();

    public void add() {
        // 加锁
        lock.lock();

        try {
            // 延迟操作
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            count++;
        } finally {
            // 释放锁
            lock.unlock();
        }


    }

    public int getCount() {
        return this.count;
    }
}

测试类

package com.example.demo.thread;

import com.example.demo.thread.impl.NonLockCounter;
import com.example.demo.thread.impl.ReentrantLockCounter;
import com.example.demo.thread.impl.SynchronizedCounter;
import org.junit.Test;

public class ThreadTests {
    @Test
    public void testReentrantLockCounter() throws InterruptedException {
        Counter counter = new ReentrantLockCounter();

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    counter.add();
                }
            }).start();
        }

        Thread.sleep(1000);

        System.out.println(counter.getCount());
    }

}

多次测试结果

100
100
100
100
100

总结

我们可以总结出synchronized和ReentrantLock有以下几点不一样。

ReentrantLock需要手动调用加锁方法; synchronized不需要,它采用了隐藏的加锁方式,借助 jvm 来实现;

synchronized不需要考虑异常; 而ReentrantLock获取锁之后,要在finally中正确的释放锁,否则会影响其它线程

ReentrantLock拥有尝试获取锁的超时机制,利用它可以避免无限等待; 而synchronized不具备

synchronized是 Java 语言层面提供的语法; 而ReentrantLock是 Java 代码实现的可重入锁

因此,在并发编程中,使用ReentrantLock比直接使用synchronized更灵活、更安全,采用tryLock 方法,即使未获取到锁也不会导致死锁。

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

Condition

Condition提供的await()、signal()、signalAll()原理和synchronized锁对象的wait()、notify()、notifyAll()是一致的,并且其行为也是一样的。

简单的示例

package com.example.demo.thread.impl;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionCounter {
    private final Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    public void await()  {
        // 加锁
        lock.tryLock();

        try {
            System.out.println("await开始等待");
            condition.await();
            System.out.println("await结束等待");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }


    public void signal() {
        // 加锁
        lock.tryLock();

        try {

            // 延迟操作
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 唤醒某个等待线程
            condition.signal();
            System.out.println("signal 唤醒通知完毕");
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}

package com.example.demo.thread;

import com.example.demo.thread.impl.ConditionCounter;
import org.junit.Test;

public class ThreadTests {

    @Test
    public void testConditionCounter() throws InterruptedException {
        ConditionCounter counter= new ConditionCounter();

        // 先启动执行等待的线
        new Thread(new Runnable() {
            @Override
            public void run() {
                counter.await();
            }
        }).start();

        Thread.sleep(3000);

        // 过3秒,再启动执行通知的线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                counter.signal();
            }
        }).start();
    }
}

运行结果

await开始等待
signal 唤醒通知完毕
await结束等待

相比wait/notify/notifyAll的等待/通知模型,Condition更加灵活,理由有以下几点:

notify()方法唤醒线程时,被通知的线程由 Java 虚拟机随机选择; 而采用ReentrantLock结合Condition可以实现有选择性地通知,这一特性在实际编程中非常实用 一个Lock里面可以创建多个Condition实例,实现多路通知,使用多个Condition的应用场景很常见,比如ArrayBlockingQueue

参考文章

  1. 并发编程之ReentrantLock类使用介绍
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
ndh0xMjNGcR6