Java多线程(三):线程安全问题 (下)Lock

上一篇文章提到通过synchronized实现同步,而在这篇文章中,同步会通过Lock和Condition配合实现,两者都来自jdk1.5并发包

Lock

本文章会使用java.util.concurrent.locks包内的ReentrantLock类(唯一实现Lock接口的类)和Condition接口模拟出synchronized和wait()配合的同步实现,首先介绍Lock类的方法

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

1. lock():用来获取锁,如果锁已被其他线程获取,则进行等待。也就是上锁

2. unlock():释放锁,也就是开锁,与lock()合用

Lock lock = ...;
try{
    //获取锁
    lock.lock();
    //处理任务
}catch(Exception ex){
     
}finally{
    lock.unlock();   //释放锁
}

注:获取锁需要放在try内,为了避免可能会发生的死锁,finally里都必须要释放锁

3. trylock():方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待

Lock lock = ...;
if(lock.tryLock()) {
     try{
         //处理任务
     }catch(Exception ex){
         
     }finally{
         lock.unlock();   //释放锁
     } 
}else {
    //如果不能获取锁,则直接做其他事情
}

4. tryLock(long time, TimeUnit unit):和tryLock()方法类似,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true

5. lockInterruptibly():这个方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

    try {  
        lock.lockInterruptibly();
     //.....
    }
    finally {
        lock.unlock();
    }  

注:当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为本身在前面的文章中讲过单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去

ReentrantLock

ReentrantLock,意思是“可重入锁”。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法

 

ReadWriteLock

ReadWriteLock也是一个接口,在它里面只定义了两个方法:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading.
     */
    Lock readLock();
 
    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing.
     */
    Lock writeLock();
}

一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。下面的ReentrantReadWriteLock实现了ReadWriteLock接口

ReentrantReadWriteLock

ReentrantReadWriteLock里面提供的最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。列举一个读写锁的实例:

public class ReadWriteLock {

	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		// 创建读写可重用锁
		ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();

		new Thread() {
			public void run() {
				try {
					// 对读锁获取锁
					rwlock.readLock().lock();
					for (int i = 0; i < 5; i++) {
						System.out.println(Thread.currentThread().getName() + "正在进行读操作");
					}
					System.out.println(Thread.currentThread().getName() + "读取完毕");
				} catch (Exception e) {
					// TODO: handle exception
				} finally {
					// 读锁释放锁
					rwlock.readLock().unlock();
				}
			};
		}.start();

		new Thread() {
			public void run() {
				try {
					// 对读锁获取锁
					rwlock.readLock().lock();
					for (int i = 0; i < 5; i++) {
						System.out.println(Thread.currentThread().getName() + "正在进行读操作");
					}
					System.out.println(Thread.currentThread().getName() + "读取完毕");
				} catch (Exception e) {
					// TODO: handle exception
				} finally {
					// 读锁释放锁
					rwlock.readLock().unlock();
				}
			};
		}.start();
	}
}

结果:   

可以发现两个线程在读锁被获取的情况下做读入操作时是并行的,可以提高读写效率

不过要注意的是,如果有一个线程已经占用了读锁,其他线程可以并行读取,而如果要申请写锁,则申请写锁的线程会一直等待释放读锁。但是,如果有一个线程已经占用了写锁,此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁

 

Condition

synchronized能和wait()、notify()、notifyAll()配合使用,Lock同样也有对应的等待和唤醒方法,这些方法的实现接口就是Condition。由于这篇主要讲Lock,就不对Condition多做讲解,在此展示一下Condition的API文档

await():对应wait()方法

signal():对应notify()方法

signalAll():对应notifyAll()方法

注:Condition对象创建方法为 lock.newCondition(), lock为ReentrantLock的实例化对象

Lock+Condition实现同步

在 Java多线程(三):线程安全问题 (上)synchronized 中使用synchronized与wait配合实现了同步,下面我用代码演示一下Lock的同步实现,注意Lock锁的实现,要新建的是ReentrantLock对象

public class LockDemo {
	public static void main(String[] args) {

		Res1 res = new Res1();
		InpThread1 inpThread = new InpThread1(res);
		OutThread1 outThread = new OutThread1(res);
		inpThread.start();
		outThread.start();
	}
}

class Res1 {
	public String name;
	public String sex;
	// flag false out线程打印
	// flag true inp线程读取
	public boolean flag = false;

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

class InpThread1 extends Thread {
	public Res1 res;

	public InpThread1(Res1 res) {
		this.res = res;
	}

	@Override
	public void run() {
		int count = 0;
		while (true) {
			// if(res.lock.tryLock()) 判断trylock更保险,如果可以锁,自动会执行lock()
			try {
				// 获取锁
				res.lock.lock();
				// 如果是只读操作,进入休眠状态
				if (res.flag) {
					try {
						res.condition.await();
					} catch (InterruptedException e) {
					}
				}
				if (count == 0) {
					res.name = "ymk";
					res.sex = "男";
				} else {
					res.name = "zyy";
					res.sex = "女";
				}
				count = (count + 1) % 2;

				res.flag = true;
				res.condition.signal();

			} catch (Exception e) {
				// TODO: handle exception
			} finally {
				// 释放锁
				res.lock.unlock(); // 防止出现意外,锁永远不会得到释放
			}
		}
	}
}

class OutThread1 extends Thread {

	public Res1 res;

	public OutThread1(Res1 res) {
		this.res = res;
	}

	@Override
	public void run() {
		while (true) {
			try {
				// 上锁
				res.lock.lock();
				if (!res.flag) {
					res.condition.await();
				}
				System.out.println(res.name + "------" + res.sex);
				res.flag = false;
				res.condition.signal();
			} catch (Exception e) {
				// TODO: handle exception
			} finally {
				// 释放锁
				res.lock.unlock();
			}
		}
	}
}

结果与synchronized实现相同:

 

Lock和synchronized的异同

共同点:

1. 都能解决线程安全问题

2. 都是可重入锁(基于线程的分配,而不是基于方法调用的分配)

区别:

1. synchronized和wait()配合使用,Lock与Condition的await方法配合

2. synchronized是Java内置实现的锁,不可手动开锁、解锁;而Lock是个接口,可以实现手动开锁、解锁

3. synchronized是不可中断了,而Lock的lockInterruptibly()具有可中断性

4. synchronized是非公平锁,Lock可以设置是否公平(公平锁:尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所

文章内容后续可能会进行补充...


笔者水平有限,若有错误欢迎纠正,希望多获得大家的建议

参考:https://www.cnblogs.com/dolphin0520/p/3923167.html

           https://www.jianshu.com/p/be2dc7c878dc

           https://blog.youkuaiyun.com/qq_40409115/article/details/80229188

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值