使用多线程上锁的实现方式

1)关键字 synchronized

在 方法,代码片段都可使用,示例如下:

public static void needLockBySync() {

synchronized (ReentrantLockTest.class) {

try {

System.out.println(Thread.currentThread().getName() + "开始工作");

TimeUnit.SECONDS.sleep(5);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

2)重入锁 new ReentrantLock

表示该锁能够支持一个线程对资源的重复加锁。示例如下:

public class ReentrantLockTest {

private static final ReentrantLock lock = new ReentrantLock();

 

public static void main(String[] args) throws InterruptedException {

IntStream.range(0, 2).forEach(i -> new Thread(ReentrantLockTest::needLock).start());

}

public static void needLock() {

try {

lock.lock();

System.out.println(Thread.currentThread().getName() + "开始工作");

TimeUnit.SECONDS.sleep(5);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

lock.unlock();

}

}

}

**确保在finally里释放锁,否则容易早成死锁

 

检测当前资源是否被占用,可使用方法tryLock检测,返回类型:boolean

测试:

public class ReentrantLockTest {

private static final ReentrantLock lock = new ReentrantLock();

 

public static void main(String[] args) throws InterruptedException {

Thread thread1 = new Thread(ReentrantLockTest::testTryLock, "thread1");

thread1.start();

TimeUnit.SECONDS.sleep(1);

 

Thread thread2 = new Thread(ReentrantLockTest::testTryLock, "thread2");

thread2.start();

}

public static void testTryLock() {

if (lock.tryLock()) {

try {

System.out.println(Thread.currentThread().getName() + "开始工作");

//进入死循环,不释放锁

while (true) { }

} finally {

lock.unlock();

}

} else {

System.out.println(Thread.currentThread().getName() + "没有获取到锁");

}

}

}

 

ReentrantLock一些别的方法:

方法

含义

getQueueLength()

等待获取锁线程数量

hasQueuedThreads()

是否有在等待获取锁的线程

hasQueuedThread(Thread thread)

等待获取锁的线程队列里是包含指定的线程

isLocked

当前锁是否被任意一个线程获取到了

3)读写锁 ReadWriteLock

读写锁,遵循以下规则:

  • 写的时候不能读
  • 写的时候不能写
  • 读的时候不能写
  • 读的时候可以读

读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

ReadWriteLock为接口,我们使用它的实现类ReentrantReadWriteLock创建读写锁实例:

ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

ReentrantReadWriteLock.ReadLock readLock = lock.readLock();

ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();

使用读写锁创建一个读写的例子:

public class ReadWriteLockTest {

private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

// 读锁

private static ReentrantReadWriteLock.ReadLock readLock = lock.readLock();

// 写锁

private static ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();

// 存放数据

private static List<Long> data = new ArrayList<>();

 

public static void main(String[] args) throws InterruptedException {

new Thread(() -> {

while (true) {

write();

}

}, "writer").start();

new Thread(() -> {

while (true) {

read();

}

}, "reader").start();

 

}

 

public static void write() {

try {

writeLock.lock(); // 写锁

TimeUnit.SECONDS.sleep(1);

long value = System.currentTimeMillis();

data.add(value);

System.out.println(Thread.currentThread().getName() + " 写入value: " + value);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

writeLock.unlock(); // 释放写锁

}

}

 

public static void read() {

try {

readLock.lock(); // 获取读锁

TimeUnit.SECONDS.sleep(1);

String value = data.stream().map(String::valueOf).collect(Collectors.joining(","));

System.out.println(Thread.currentThread().getName() + "读取data: " + value);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

readLock.unlock(); // 释放读锁

}

}

}

4)StampedLock

JDK8 新增了一个锁StampedLock,它是对ReadWriteLock的改进。

使用ReadWriteLock的时候,当读线程数量远大于写线程数量的时候就会出现“写饥饿”现象。因为锁大概率都被读线程抢走了,写线程很难抢到锁,这将使得读写效率非常低下。

JDK8的StampedLock就是为了解决这个问题而设计的,StampedLock包含乐观锁和悲观锁:

  • 乐观锁:每次去拿数据的时候,并不获取锁对象,而是判断标记位(stamp)是否有被修改,如果有修改就再去读一次。
  • 悲观锁:每次拿数据的时候都去获取锁。

通过乐观锁,当写线程没有写数据的时候,标志位stamp并没有改变,所以即使有再多的读线程在读取数据,它们都可以直接去读数据,而无需获取锁,这就不会使得写线程抢不到锁了。

stamp类似一个时间戳的作用,每次写的时候对其+1来改变被操作对象的stamp值。实例:

public class StampedLockTest {

private static StampedLock lock = new StampedLock();

private static List<Long> data = new ArrayList<>();

 

public static void main(String[] args) {

ExecutorService executorService = Executors.newFixedThreadPool(20);

 

Runnable read = StampedLockTest2::read;

Runnable write = StampedLockTest2::write;

 

IntStream.range(0, 19).forEach(i -> executorService.submit(read));

executorService.submit(write);

 

executorService.shutdown();

}

 

private static void read() {

long stamped = lock.tryOptimisticRead(); // 获取乐观锁

try {

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

// 直接读取值

String collect = data.stream().map(String::valueOf).collect(Collectors.joining(","));

 

// 如果戳被改变,方法返回false,说明stamped被修改过了(被write方法修改过了,有新的数据写入),

// 那么重新获取锁并去读取值,否则直接使用上面读取的值。

if (!lock.validate(stamped)) {

try {

stamped = lock.readLock();

TimeUnit.SECONDS.sleep(1);

collect = data.stream().map(String::valueOf).collect(Collectors.joining(","));

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

lock.unlockRead(stamped);

}

}

System.out.println(Thread.currentThread().getName() + " read value: " + collect);

}

 

private static void write() {

long stamped = -1;

try {

stamped = lock.writeLock();

TimeUnit.SECONDS.sleep(1);

long value = System.currentTimeMillis();

data.add(value);

System.out.println(Thread.currentThread().getName() + " write value: " + value);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

lock.unlockWrite(stamped);

}

}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值