欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

秋招准备-Java-并发编程-显式锁Lock(三)

程序员文章站 2022-05-05 09:41:42
...

秋招准备-Java-并发编程-显式锁Lock(三)

1.Lock接口与其方法,ReentrantLock为实现类

2.Condition接口与其方法,组合ReentrantLock实现条件队列

3.ReentrantReadWriterLock读写锁


1.Lock接口,ReentrantLock实现类

//java.util.concurrent.locks.Lock接口
public interface Lock 
{
	/**
	 * Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作
	 */
    void lock();
    void lockInterruptibly();
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit);
    void unlock();
    Condition newCondition();
}

    1.lock()普通加锁

	Lock lock = new ReentrantLock();
	lock.lock();
	try {
		……
	}finally {
		lock.unlock();
	}

        作用相当与synchronized同步块,用lock()方法进行获取锁,如果进入阻塞状态,也会无限期等待。需要手动解锁,要在try块中进行获取锁后的操作,然后在finally块中unlock()确保解锁。


    2.trylock()尝试获取锁

	if(lock.trylock())
	{
		try {
			……;
		}finally {
			lock.unlock();
		}
	}
	else {
		……
	}

        trylock()提供尝试获取锁的功能,即trylock()后,如果没有线程持锁,则当前线程获取锁,且返回true,即进入try块,进行获取锁的操作,而如果有线程持锁,则不阻塞阻塞等待,且返回false,因此可以直接进行else块做其他事情。

        trylock(long timeout,TimeUnit unit),提供了一段时间的申请,timeout是时间,unit是单位,即在这一段时间内保持请求锁的状态,时间到了还未申请到锁,就返回false放弃了。


    3.lockInterruptibly()可中断获取锁

public class Main
{
	public static void main(String[] args)throws Exception
	{
		Lock lock = new ReentrantLock();
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				lock.lock();
				try {
					Thread.sleep(10000);
				}catch(Exception e) {}
				finally {
					lock.unlock();
				}
			}
		});
		t1.start();
		Thread.sleep(1000);//让t1拿锁
		Thread t2 = new Thread(new Runnable(){
			public void run() {
				try 
				{
					System.out.println("尝试中");
//					lock.lock();
					lock.lockInterruptibly();
					try 
					{
						System.out.println("一直申请,终于得锁");
					}
					finally
					{
						lock.unlock();
					}
				}
				catch(Exception ex) 
				{
					System.out.println("阻塞跳出");
				}
			}
		});
		t2.start();
		Thread.sleep(1000);
		t2.interrupt();
	}
}

        在synchronized和Lock的普通加锁中,如果一旦进入到等待阻塞,则无法跳出,会在没有持锁前一直等待,而Lock提供了可中断的获取锁功能,如果用lockInterruptibly()来获取锁,当前线程可在阻塞期间通过interrupt()方法来抛出异常并跳出。

        在上述代码中,用lockInterruptibly(),t2会在1秒后跳出,用lock()则会10秒后得锁。


    4.公平锁与非公平锁

        涉及这个“公平”概念的有两点,一是多线程阻塞竞争锁时的顺序,二是等待唤醒队列中单个唤醒的顺序。

        而在synchronized中这两点都是随机的。

        在Lock机制下,默认是非公平锁,

        在竞争锁时,可能发生这样的情况,已经有线程在阻塞等待,而此时,当一个新的线程请求锁时,在这同时,该锁变成可用状态,而阻塞线程又还在启动中,这时这个新线程就会跳过这些阻塞线程获得锁。

        显然这是个性能优化,因此非公平锁的性能是要优于公平锁的(基于情况:请求时间短,线程平均持锁时间短),而公平锁则能满足某些情况的功能需求。

        ReentrantLock(boolean fair)的构造方法中,提供了一个参数可以申明为公平锁。在公平锁下,竞争锁与唤醒锁都是按先后顺序的。

        (底层实现还有点迷,不能把非公平情况的顺序讲得很清楚,未……)



2.Condition条件队列

//java.util.concurrent.locks.Condition接口;
public interface Condition 
{
    void await();
    void signal();
    void signalAll();
}

    1.Condition的使用是这样的,通过Lock的newCondition()方法,返回一个与Lock实例绑定的Condition对象,然后在Lock的加锁代码段,用这个condition对象,通过条件判断筛选出一些需要暂缓的线程,然后再通过另一些条件判断来唤醒这些线程。

        因为可以实例化多个Condition对象,就可以通过不同的条件,筛选出满足不同条件的线程暂缓,因此可以用来实现许多功能,如阻塞队列实际就是通过Condition实现的。

        因为唤醒存在虚假唤醒,所以筛选的时候是用while(exp)来筛选的。


    2.写一段实验代码,来实现存取钱功能。

class Bank
{
	public Bank(int num) {account=num;};
	private int account;
	public int Account() {return account;}
	public void save(int num) { account+=num;}
	public void get(int num) { account-=num;}
}
public class Main
{
	public static void main(String[] args)throws Exception
	{
		Bank bank = new Bank(1000);
		Lock lock = new ReentrantLock();
		Condition condition = lock.newCondition();
		//Get
		for(int i=0;i<10;i++)
		{
			int num = (int)(Math.random()*1000);
			Thread t = new Thread(new Runnable() {
				public void run() {
					lock.lock();
					try {
						while(bank.Account()<num)
						{
							condition.await();
						}
						System.out.print("当前余额:"+bank.Account()+" ");
						System.out.print("取出"+num+" ");
						bank.get(num);
						System.out.println("剩余:"+bank.Account()+" ");		
					}catch(Exception ex) {}
					finally{
						lock.unlock();
					}
				}
			});
			t.start();
		}
		for(int i=0;i<10;i++)
		{
			Thread.sleep(1000);
			int num = (int)(Math.random()*1000);
			Thread t = new Thread(new Runnable() {
				public void run() {
					lock.lock();
					try {
						System.out.print("当前余额:"+bank.Account()+" ");
						System.out.print("存入"+num+" ");
						bank.save(num);
						System.out.println("剩余:"+bank.Account()+" ");	
						condition.signal();
					}catch(Exception ex) {}
					finally{
						lock.unlock();
					}
				}
			});
			t.start();
		}
	}
}

        有时候可能觉得知道了大概的用法,但不知道到底能用在哪,要举例好像也举不出,但实际上自己给需求,自己来做能实验的代码,也是一种培养感觉的方式。



3.ReentrantReadWriteLock

    ReentrantReadWriteLock实现的不是Lock接口,而是ReadWriteLock。因此要这样构造对象:

    ReentrantReadWriteLock rwl = new  ReentrantReadWriteLockd();

     ReentrantReadWriteLock的两个内部类ReadLock和WriteLock实现了Lock接口,因此实际的锁这样获得:

    Lock read = rwl.readLock();

    Lock write = rwl.writeLock();

    即readLock()和writeLock()会返回与读写锁对象对应的两把锁,读锁与写锁。

    在只读操作中加读锁read,则允许多个读操作线程同时共享数据。

    在涉及到写操作的块内加写锁write,一旦有线程进入写锁,则读锁和写锁都开始只允许单线程持锁进入其中一块,即允许读读,不允许读写和写写。

    写段实验代码:

class Bank
{
	public Bank(int num) {account=num;};
	private int account;
	public int Account() {return account;}
	public void save(int num) { account+=num;}
	public void get(int num) { account-=num;}
}
public class Main
{
	public static void main(String[] args)throws Exception
	{
		Bank bank = new Bank(10);
		ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
		Lock read = rwl.readLock();
		Lock write = rwl.writeLock();
		int value = 5;
		Thread th1 = new Thread(new Runnable() {
			public void run() {
				read.lock();
				try {					
					Thread.sleep(1000);
					System.out.println(bank.Account());
				}catch(Exception ex) {}
				finally {
					read.unlock();
				}
			}
		});
		Thread th2 = new Thread(new Runnable() {
			public void run() {
				read.lock();
				try {					
					bank.save(2);
				}catch(Exception ex) {}
				finally {
					read.unlock();
				}
			}
		});
		Thread th3 = new Thread(new Runnable() {
			public void run() {
				write.lock();
				try {
					bank.save(3);
				}catch(Exception ex) {}
				finally {
					write.unlock();
				}
			}
		});
		th1.start();
//		th2.start();
		th3.start();
	}
}

    其实只开读锁的时候就相当于没同步,在里面修改共享数据好像也行,因此也得注意。



4.synchronized与Lock的区别

    1.实现层面

        jvm与jdk

    2.性能层面

        synchronized的局限与优化,Lock的稳定

    3.功能层面

        synchronized简单清晰,但阻塞不可中断,只有一个条件队列。

        Lock的功能性强,tryLock(),lockInterruptibly(),提供多个条件队列,公平锁,读写锁。

   
相关标签: Lock ReentrantLock