Java之多线程之Lock与Condition

本文详细介绍了Java中Lock接口及其实现类ReentrantLock的使用方法,并对比了synchronized关键字。通过具体示例展示了如何利用Lock进行线程同步,包括使用Condition实现线程间的协作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[color=green][size=medium][b]Java之多线程之Lock[/b][/size][/color]

[url=http://lixh1986.iteye.com/blog/2351243]接上文[/url]
在多线程环境中,大部分情况下,使用 synchronized 关键字可以满足需求。
但是其也存在不足。于是 java.util.concurrent.locks 包出现了。

[size=medium][b]一、Lock API 的主要类介绍[/b][/size]

[b]1、Lock 接口 - 实现类 ReentrantLock[/b]
接口类。规定了Lock的基本方法,这些方法可以满足所有 synchronized 的功能,
还提供了更多功能:Lock条件判断、Lock超时判断。

其最主要的方法:
lock():获取锁
unlock():释放锁
tryLock():等待锁一段时间再锁
newCondition():根据条件进行锁

[b]1.1 Condition[/b]

背景知识:
- wait()、notify()
在多线程进行协同工作时,需要用到 wait()、notify() 。
wait()、notify() 只能用在 synchronized 块内部,而且是,synchronized 哪个对象,就得调用哪个对象的 wait()、notify() 方法。

Condition 类与Object类的 wait()、notify() 方法功能差不多。
但是提供了更多:可以创建不同的 wait 集合。
Condition 的实例必须由 Lock 类创建,而不是自己去 new 而产生。

主要方法:
await():类似于 Object.wait()
signal():类似于 Object.notify()
signalAll():类似于 Object.notifyAll()




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

import org.junit.Test;

// 方法一:
// 使用 wait 和 notify 控制线程,
// 使子线程和主线程各交替执行一次。
//=====================================================================

class UseWaitNotify {

private Object dummy = new Object();
private boolean flag = true;

public void subThread() {
synchronized (dummy){
while(!flag){
try {
dummy.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("[UseWaitNotify] sub..");
flag = false;
dummy.notify();
}
}

public void mainThread(){
synchronized (dummy){
while(flag){
try {
dummy.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("[UseWaitNotify] main..");
flag = true;
dummy.notify();
}
}
}

// test method
class WaitNotify{

@Test
public void testUseWaitNotify(){
UseWaitNotify useWaitNotify = new UseWaitNotify();
new Thread(new Runnable(){
@Override
public void run() {
for(int i = 0 ; i< 10; i++){
useWaitNotify.subThread();
}
}
}).start();


for(int i = 0 ; i< 10; i++){
useWaitNotify.mainThread();
}

}
}


//方法二:
//使用 await 和 signal 控制线程,
//使子线程和主线程各交替执行一次。
//=====================================================================

class UseCondition {

private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

private boolean flag = true;

public void subThread(){
lock.lock();
try{
while(!flag){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("[UseCondition] sub..");
flag = false;
condition.signal();
}finally{
lock.unlock();
}
}

public void mainThread(){
lock.lock();
try{
while(flag){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("[UseCondition] main..");
flag = true;
condition.signal();
}finally{
lock.unlock();
}
}
}

//test method
public class LockCondition {

@Test
public void testUseCondition(){
UseCondition useCondition = new UseCondition();
new Thread(new Runnable(){
@Override
public void run() {
for(int i = 0 ; i< 10; i++){
useCondition.subThread();
}
}
}).start();


for(int i = 0 ; i< 10; i++){
useCondition.mainThread();
}

}
}





[b]1.2 ReentrantLock[/b]
该类被使用的最为广泛。它是在功能上实现了 synchronized 的类。
除了实现了从 Lock 接口继承的方法,它还自己有一些方法:
比如让线程等待一段时间再去获取资源的锁。

[b]什么是 reentrant (可重入)?[/b]
其实 synchronized 代码块原本就是可重入(reentrant)的:
例如:
某线程正在执行 synchronized 代码块一,代码块一中需要执行代码块二,
两个代码块锁定的是同一个资源,此时线程一可以顺利执行此两个代码块。
无需重复获取资源的锁,即:资源锁重用。

看下面的例子:

public class Test{

public synchronized foo(){
//do something
bar();
}

public synchronized bar(){
//do some more
}
}

线程在执行 foo()时,需要执行 bar(),此时直接执行即可,无需重复获取锁。
因为这两个 synchronized 代码块锁定的是同一个对象:this


[b]2、ReadWriteLock 接口 - 实现类 ReentrantReadWriteLock[/b]
该类包含了一对相互关联的锁。
一种是:只读锁(Read-Only)。如果没有其它线程在占有写锁,该锁可以被多个线程同时拥有。
一种是:写锁。如果没有线程在占有读锁或写锁,该锁只能被一个线程独占。


[size=medium][b]二、Lock 使用示例[/b][/size]

1、先看看用 synchronized 的写法:

public class Resource {

private Object dummy = new Object();

public void doSomething(){
synchronized(dummy){
System.out.println("do something...");
}
}

public void doLogging(){
System.out.println("do logging...");
}

}


2、使用 java.util.concurrent.locks.Lock 的写法:

public class LockResource {

private Lock lock;

public LockResource(){
this.lock = new ReentrantLock();
}

public void doSomething(){
try {
if(lock.tryLock(10, TimeUnit.SECONDS)){ //时间单位:秒
System.out.println("do something..");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
lock.unlock(); // release the lock.
}
}

public void doLogging(){
System.out.println("do logging...");
}

}


3、ReentrantReadWriteLock


class Queue3{

private Object data = null;
ReadWriteLock lock = new ReentrantReadWriteLock();

/*
Read lock:
- make sure no thread is writing.
*/
public void get(){
lock.readLock().lock();
System.out.println("read data..." + data);
lock.readLock().unlock();
}

/*
Write lock:
- make sure no thread is writing.
- make sure no thread is reading.
*/
public void set(Object data){
lock.writeLock().lock();
System.out.println("write data...");
this.data = data;
lock.writeLock().unlock();
}

}



4. [url=https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html]ReentrantReadWriteLock [/url] 使用Lock 实现缓存。

class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}

try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}





5. 双condition实现的阻塞式消息队列。


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

/**
* 双condition实现的阻塞式消息队列。
* - 队列满时,不存
* - 队列空时,不取
*/
public class LockConditionBlockingQueue {

final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition(); //控制存操作
final Condition notEmpty = lock.newCondition();//控制取操作

final Object[] items = new Object[100];
int putptr, takeptr, count;

public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length)
putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}

public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length)
takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}




6. 使用 3 个 condition,使每个子线程各自交替执行。


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

import org.junit.Test;

public class LockConditionThree {
final int maxLoop = 100;

@Test
public void testUseCondition(){
UseCondition useCondition = new UseCondition();
new Thread(new Runnable(){
@Override
public void run() {
for(int i = 0 ; i< maxLoop; i++){
useCondition.sub1();
}
}
}).start();

new Thread(new Runnable(){
@Override
public void run() {
for(int i = 0 ; i< maxLoop; i++){
useCondition.sub2();
}
}
}).start();

new Thread(new Runnable(){
@Override
public void run() {
for(int i = 0 ; i< maxLoop; i++){
useCondition.sub3();
}
}
}).start();


}


//使用 3 个 condition,使每个子线程各自交替执行。
//=======================================================

static class UseCondition {

private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();

private int shouldSub = 1;

public void sub1(){
lock.lock();
try{
while(shouldSub != 1){
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("sub1..");
shouldSub = 2;
condition2.signal(); //notify thread 2
}finally{
lock.unlock();
}
}

public void sub2(){
lock.lock();
try{
while(shouldSub != 2){
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("sub2..");
shouldSub = 3;
condition3.signal(); //notify thread 3
}finally{
lock.unlock();
}
}
public void sub3(){
lock.lock();
try{
while(shouldSub != 3){
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("sub3..\n");
shouldSub = 1;
condition1.signal(); //notify thread 1
}finally{
lock.unlock();
}
}
}
}





7. Semaphore
Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
比如在Windows下可以设置共享文件的最大客户端访问个数。


import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;

public class LockSemephore {

public static void main(String[] args) {

ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semp = new Semaphore(5); // 同时只能5个线程使用

for (int index = 0; index < 20; index++) { // 模拟20个客户端访问
final int NO = index;
exec.execute(new Runnable() {
public void run() {
try {
semp.acquire();// 获取许可
System.out.println("Accessing: " + NO);
Thread.sleep(2000);
semp.release();// 访问完后,释放
System.out.println("AvailablePermits:---------" + semp.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// 退出线程池
exec.shutdown();
System.out.println("done!");
}

}




java.util.concurrent包之Execuotor系列文章

[url=http://lixh1986.iteye.com/blog/2341898]00_Java之 java.util.concurrent 包之概述[/url]

[url=http://lixh1986.iteye.com/blog/2360304]01_Java之java.util.concurrent包之Executor与ExecutorService[/url]

[url=http://lixh1986.iteye.com/blog/2360306]02_Java之 java.util.concurrent 包之ExecutorService之submit () 之 Future[/url]

[url=http://lixh1986.iteye.com/blog/2351367]03_Java之多线程之Callable与Future[/url]

[url=http://lixh1986.iteye.com/blog/2351294]04_Java之多线程之Lock[/url]


-
转载请注明,
原文出处:http://lixh1986.iteye.com/blog/2351294


引用:
http://www.journaldev.com/2377/java-lock-example-reentrantlock
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值