Lock锁的使用

在Java多线程中,可以使用synchronized关键字实现线程之间的同步互斥,在jdk1.5后新增的ReentrantLock类同样可达到此效果,且在使用上比synchronized更加灵活。

观察ReentrantLock类可以发现其实现了Lock接口

public class ReentrantLock implements Lock,java.io.Serializable

1、使用ReentrantLock实现同步

lock()方法:上锁

unlock()方法:释放锁

/*
 * 使用ReentrantLock类实现同步
 * */
class MyReenrantLock implements Runnable{
	//向上转型
	private Lock lock = new ReentrantLock();
	public void run() {
		//上锁
		lock.lock();
		for(int i = 0; i < 5; i++) {
			System.out.println("当前线程名: "+ Thread.currentThread().getName()+" ,i = "+i);
		}
		//释放锁
		lock.unlock();
	}
}
public class MyLock {
	public static void main(String[] args) {
		MyReenrantLock myReenrantLock =  new MyReenrantLock();
		Thread thread1 = new Thread(myReenrantLock);
		Thread thread2 = new Thread(myReenrantLock);
		Thread thread3 = new Thread(myReenrantLock);
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

    由此我们可以看出,只有当当前线程打印完毕后,其他的线程才可继续打印,线程打印的数据是分组打印,因为当前线程持有锁,但线程之间的打印顺序是随机的。

    即调用lock.lock()代码的线程就持有了“对象监视器”,其他线程只有等待锁被释放再次争抢。

    2、使用Condition实现等待/通知

    synchronized关键字结合wait()和notify()及notifyAll()方法的使用可以实现线程的等待与通知模式。在使用notify()、notifyAll()方法进行通知时,被通知的线程是JVM随机选择的。

    类ReentrantLock类同样可以实现该功能,需要借助Condition对象,可实现“选择性通知”。Condition类是jdk1.5提供的,且在一个Lock对象中可以创建多个Condition(对象监视器)实例。

    /*
     * 错误的使用Condition实现等待、通知
     * */
    class MyCondition implements Runnable{
    	private Lock lock = new ReentrantLock();
    	public Condition condition = lock.newCondition();
    	public void run() {
    		try {
    			System.out.println("当前线程名:"+Thread.currentThread().getName()+" 开始等待时间:"+System.currentTimeMillis());
    			//线程等待
    			condition.await();
    			System.out.println("我陷入了等待...");
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    }
    public class MyLock{
    	public static void main(String[] args) {
    		MyCondition myCondition = new MyCondition();
    		Thread thread1 = new Thread(myCondition,"线程1");
    		thread1.start();
    	}
    }
    

    在这里插入图片描述
    观察运行结果可以发现,报出监视器出错的异常,解决的办法是我们必须在condition.await()方法调用前用lock.lock()代码获得同步监视器。对上述代码做出如下修改:

    /*
     * 使用Condition实现等待
     * */
    class MyCondition implements Runnable{
    	private Lock lock = new ReentrantLock();
    	public Condition condition = lock.newCondition();
    	public void run() {
    		try {
    			//上锁
    			lock.lock();
    			System.out.println("当前线程名:"+Thread.currentThread().getName()+" 开始等待时间:"+System.currentTimeMillis());
    			//线程等待
    			condition.await();
    			System.out.println("我陷入了等待...");
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}finally {
    			//释放锁
    			lock.unlock();
    			System.out.println("锁释放了!");
    		}
    	}
    }
    public class MyLock{
    	public static void main(String[] args) {
    		MyCondition myCondition = new MyCondition();
    		Thread thread1 = new Thread(myCondition,"线程1");
    		thread1.start();
    	}
    }
    

    在这里插入图片描述
    在控制台只打印出一句,原因是调用了Condition对象的await()方法,是的当前执行任务的线程进入等待状态。

    Condition类的signal():是当前执行任务的线程处于等待状态

    /*
     * 使用Condition实现等待、通知
     * */
    class MyCondition implements Runnable{
    	private Lock lock = new ReentrantLock();
    	public Condition condition = lock.newCondition();
    	public void run() {
    		try {
    			//上锁
    			lock.lock();
    			System.out.println(" 开始等待时间:"+System.currentTimeMillis());
    			System.out.println("我陷入了等待...");
    			//线程等待
    			condition.await();
    			//释放锁
    			lock.unlock();
    			System.out.println("锁释放了!");
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    	//通知方法
    	public void signal(){
    		try {
    			lock.lock();
    			System.out.println("结束等待时间:"+System.currentTimeMillis());
    	     	//通知等待线程
    			condition.signal();
    		} finally {
    			lock.unlock();
    		}
    	}
    }
    public class MyLock{
    	public static void main(String[] args) throws InterruptedException {
    		MyCondition myCondition = new MyCondition();
    		Thread thread1 = new Thread(myCondition,"线程1");
    		thread1.start();
    		Thread.sleep(3000);
    		myCondition.signal();
    	}
    }
    

    在这里插入图片描述
    观察结果我们成功地实现了等待通知。

    可以得知:Object类中的wait()方法等同于Condition类中的await()方法。

                Object类中的wait(long timeout)方法等同于Condition类中的await(long time,TimeUnit unit)方法。
    
                Object类中的notify()方法等同于Condition类中的singal()方法。
    
               Object类中的notifyAll()方法等同于Condition类中的singalAll()方法。
    

    3、生产者消费者模式

    package com.atguigu.java2;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
     * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,
     * 店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了, 店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
     * 
     * @param args
     */
    public class CusAndPro {
    	public static void main(String[] args) {
    		Clerk clerk = new Clerk();
    		Productor productor = new Productor(clerk);
    		Customer customer = new Customer(clerk);
    		Thread t1 = new Thread(customer);
    		Thread t2 = new Thread(productor);
    		t1.start();
    		t2.start();
    	}
    }
    
    class Clerk {
    	private int count = 0;
    	Lock lock = new ReentrantLock();// 获得锁
    	Condition condition = lock.newCondition();// 获得等待或者释放线程的类
    
    	// 生产产品
    	public void set() {
    		try {
    			lock.lock();
    			if (count >= 20) {
    				try {
    					condition.await();// 让线程处于等待状态
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    			} else {
    				count++;
    				System.out.println("生产了第" + count + "个产品");
    				condition.signal();// 释放线程
    			}
    		} finally {
    			lock.unlock();
    		}
    	}
    
    	// 消费产品
    	public void get() {
    		try {
    			lock.lock();
    			if (count <= 0) {
    				try {
    					condition.await();// 让线程处于等待状态
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    			} else {
    				System.out.println("拿走了第" + count + "个产品");
    				count--;
    				condition.signal();// 释放线程
    			}
    		} finally {
    			lock.unlock();
    		}
    	}
    }
    
    class Productor implements Runnable {
    	Clerk clerk;
    
    	public Productor(Clerk clerk) {
    		this.clerk = clerk;
    	}
    
    	@Override
    	public void run() {
    		while (true) {
    			try {
    				Thread.sleep(30);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			clerk.set();
    		}
    	}
    
    }
    
    class Customer implements Runnable {
    	Clerk clerk;
    
    	public Customer(Clerk clerk) {
    		this.clerk = clerk;
    	}
    
    	@Override
    	public void run() {
    		while (true) {
    			try {
    				Thread.sleep(30);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			clerk.get();
    		}
    	}
    
    }
    

    在这里插入图片描述
    4、公平锁与非公平锁

    锁Lock分为“公平锁”和“非公平锁”。

    公平锁:表示线程获取锁的顺序是按照线程加锁的顺序来的进行分配的,即先来先得FIFO先进先出顺序。

    非公平锁:一种获取锁的抢占机制,是随机拿到锁的,和公平锁不一样的是先来的不一定先拿到锁,这个方式可能造成某些线程一直拿不到锁,结果就是不公平的·。

    /*
     * 公平锁
     * */
    class MyService{
    	private ReentrantLock lock;
    	public MyService(boolean isFair) {
    		super();
    		lock = new ReentrantLock(isFair);
    	}
    	public void serviceMethod() {
    		try {
    			lock.lock();
    			System.out.println("线程名:"+Thread.currentThread().getName()+"获得锁定");
    		} finally {
    			lock.unlock();
    		}
    	}
    }
    public class MyLock{
    	public static void main(String[] args) {
    		//设置当前为true公平锁
    		final MyService myService = new MyService(true);
    		Runnable runnable = new Runnable() {
    			public void run() {
    				System.out.println("线程名:"+Thread.currentThread().getName()+"运行了");
    				myService.serviceMethod();
    			}
    		};
    		Thread[] threads = new Thread[10];
    		for(int i = 0;i < 10; i++) {
    			threads[i] = new Thread(runnable);
    		}
    		for(int i = 0;i < 10; i++) {
    			threads[i].start();
    		}
    	}
    }
    

    在这里插入图片描述
    由打印结果可以看出,基本呈现有序的状态,这就是公平锁的特点。

    /*
     * 非公平锁
     * */
    class MyService{
    	private ReentrantLock lock;
    	public MyService(boolean isFair) {
    		super();
    		lock = new ReentrantLock(isFair);
    	}
    	public void serviceMethod() {
    		try {
    			lock.lock();
    			System.out.println("线程名:"+Thread.currentThread().getName()+"获得锁定");
    		} finally {
    			lock.unlock();
    		}
    	}
    }
    public class MyLock{
    	public static void main(String[] args) {
    		//设置当前为true公平锁
    		final MyService myService = new MyService(false);
    		Runnable runnable = new Runnable() {
    			public void run() {
    				System.out.println("线程名:"+Thread.currentThread().getName()+"运行了");
    				myService.serviceMethod();
    			}
    		};
    		Thread[] threads = new Thread[10];
    		for(int i = 0;i < 10; i++) {
    			threads[i] = new Thread(runnable);
    		}
    		for(int i = 0;i < 10; i++) {
    			threads[i].start();
    		}
    	}
    }
    

    在这里插入图片描述
    非公平锁的运行结果基本都是无须的,则可以表明先start()启动的线程并不一定先获得锁。

    5、使用ReentrantReadWriteLock类

    类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后的任务。这样虽然保证了实例变量的线程安全性,但是效率低下。所以在Java中提供有读写锁ReentrantReadWriteLock类,使其效率可以加快。在某些不需要操作实例变量的方法中,完全可以使用ReentrantReadWriteLock来提升该方法代码运行速度。

    读写锁表示两个锁:

    读操作相关的锁,也成为共享锁。

    写操作相关的锁,也叫排他锁。

    多个读锁之间不互斥,读锁与写锁互斥,多个写锁互斥。

    在没有线程Thread进行写入操作时,进行读操作的多个Thread可以获取读锁,但是进行写入操作时的Thread只有获取写锁后才能进行写入操作。

    (1)多个读锁共享

    /*
     * 多个读锁共享
     * */
    class MyService{
    	private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    	public void read() {
    		try {
    			//读锁
    			lock.readLock().lock();
    			System.out.println("线程名: "+Thread.currentThread().getName()+"获取读锁" );
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}finally {
    			//释放读锁
    			lock.readLock().unlock();
    		}
    	}
    }
    //线程1
    class Thread1 extends Thread{
    	private MyService myService;
    	public Thread1(MyService myService) {
    		super();
    		this.myService = myService;
    	}
    	public void run() {
    		myService.read();
    	}
    }
    //线程2
    class Thread2 extends Thread{
    	private MyService myService;
    	public Thread2(MyService myService) {
    		super();
    		this.myService = myService;
    	}
    	public void run() {
    		myService.read();
    	}
    }
    public class MyLock{
    	public static void main(String[] args) {
    		MyService myService = new MyService();
    		Thread1 thread1 = new Thread1(myService);
    		Thread2 thread2 = new Thread2(myService);
    		thread1.start();
    		thread2.start();
    		
    	}
    }
    

    在这里插入图片描述
    从打印结果可以看出,两个线程几乎同时进入lock()方法后面的代码。

    说明在此时使用lock.readLock()读锁可以提高程序运行效率,允许多个线程同时执行lock()方法后的代码。

    (2)多个写锁互斥

    
    /*
     * 多个写锁互斥
     * */
    class MyService{
    	private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    	public void write() {
    		try {
    			//写锁
    			lock.writeLock().lock();
    			System.out.println("线程名: "+Thread.currentThread().getName()+"获取写锁,获得时间:"+System.currentTimeMillis() );
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}finally {
    			//释放写锁
    			lock.writeLock().unlock();
    		}
    	}
    }
    //线程1
    class Thread1 extends Thread{
    	private MyService myService;
    	public Thread1(MyService myService) {
    		super();
    		this.myService = myService;
    	}
    	public void run() {
    		myService.write();
    	}
    }
    //线程2
    class Thread2 extends Thread{
    	private MyService myService;
    	public Thread2(MyService myService) {
    		super();
    		this.myService = myService;
    	}
    	public void run() {
    		myService.write();
    	}
    }
    public class MyLock{
    	public static void main(String[] args) {
    		MyService myService = new MyService();
    		Thread1 thread1 = new Thread1(myService);
    		Thread2 thread2 = new Thread2(myService);
    		thread1.start();
    		thread2.start();
    		
    	}
    }
    

    在这里插入图片描述
    使用写锁代码writeLock.lock()的效果就是同一时间只允许一个线程执行lock()方法后的代码。

    (3)读写/写读互斥

    /*
     * 读写/写读互斥,
     * */
    class MyService{
    	private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    	public void read() {
    		try {
    			//读锁
    			lock.readLock().lock();
    			System.out.println("线程名: "+Thread.currentThread().getName()+"获取读锁,获得时间:"+System.currentTimeMillis() );
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}finally {
    			//释放读锁
    			lock.readLock().unlock();
    		}
    	}
    	public void write() {
    		try {
    			//写锁
    			lock.writeLock().lock();
    			System.out.println("线程名: "+Thread.currentThread().getName()+"获取写锁,获得时间:"+System.currentTimeMillis() );
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}finally {
    			//释放写锁
    			lock.writeLock().unlock();
    		}
    	}
    }
    //线程1
    class Thread1 extends Thread{
    	private MyService myService;
    	public Thread1(MyService myService) {
    		super();
    		this.myService = myService;
    	}
    	public void run() {
    		myService.read();
    	}
    }
    //线程2
    class Thread2 extends Thread{
    	private MyService myService;
    	public Thread2(MyService myService) {
    		super();
    		this.myService = myService;
    	}
    	public void run() {
    		myService.write();
    	}
    }
    public class MyLock{
    	public static void main(String[] args) {
    		MyService myService = new MyService();
    		Thread1 thread1 = new Thread1(myService);
    		Thread2 thread2 = new Thread2(myService);
    		thread1.start();
    		thread2.start();	
    	}
    }
    

    在这里插入图片描述
    此运行结果说明“读写/写读”操作是互斥的。

    由此可表明:只要出现“写”操作,就是互斥的。

    (完)