java多线程之Lock类的使用

  • Post author:
  • Post category:java



1.ReentrantLock类的使用


1.1ReentrantLock实现线程间同步


public class MyService {
	private Lock lock=new ReentrantLock();
	public void service(){
		lock.lock();
		for(int i=0;i<5;i++){
			System.out.println("ThreadName="+Thread.currentThread().getName()+(" "+(i+1)));
		}
		lock.unlock();
	}
}

public class MyThread extends Thread{
    private MyService myService;
    public MyThread(MyService myService) {
        super();
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.service();
    }    
}

public class Test {
    public static void main(String[] args) {
        MyService myService=new MyService();
        MyThread mt1=new MyThread(myService);
        MyThread mt2=new MyThread(myService);
        MyThread mt3=new MyThread(myService);
        MyThread mt4=new MyThread(myService);
        MyThread mt5=new MyThread(myService);
        mt1.start();
        mt2.start();
        mt3.start();
        mt4.start();
        mt5.start();
    }
}


结果:


ThreadName=Thread-1 1

ThreadName=Thread-1 2

ThreadName=Thread-1 3

ThreadName=Thread-1 4

ThreadName=Thread-1 5

ThreadName=Thread-0 1

ThreadName=Thread-0 2

ThreadName=Thread-0 3

ThreadName=Thread-0 4

ThreadName=Thread-0 5

ThreadName=Thread-2 1

ThreadName=Thread-2 2

ThreadName=Thread-2 3

ThreadName=Thread-2 4

ThreadName=Thread-2 5

ThreadName=Thread-3 1

ThreadName=Thread-3 2

ThreadName=Thread-3 3

ThreadName=Thread-3 4

ThreadName=Thread-3 5

ThreadName=Thread-4 1

ThreadName=Thread-4 2

ThreadName=Thread-4 3

ThreadName=Thread-4 4

ThreadName=Thread-4 5

解释:线程间是竞争的关系,所以线程间执行的顺序是不同的。


1.2通过Condition实现等待通知机制


Condition类可以实现多路通知功能,就是Lock对象可以创建多个Condition,将线程对象注册在指定的Condition中,就可以实现


有通知性地进行线程通知,在调度线程上更加灵活了。


public class MyService {
	private Lock lock=new ReentrantLock();
	public Condition condition=lock.newCondition();
	public void awaitService(){
		try{
			lock.lock();
			System.out.println("await时间:   "+System.currentTimeMillis());
			condition.await();
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public void signalService(){
		try{
			lock.lock();
			System.out.println("signal时间:"+System.currentTimeMillis());
			condition.signal();
		}finally{
			lock.unlock();
		}
	}
}

public class ThreadA extends Thread{
    private MyService myService;
    public ThreadA(MyService myService) {
        super();
        this.myService = myService;
    }
    @Override
    public void run() {
        myService.awaitService();
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyService myService=new MyService();
        ThreadA a=new ThreadA(myService);
        a.start();
        Thread.sleep(3000);
        myService.signalService();
    }
} 

结果:


await时间 :  1474879823968

signal时间:  1474879826978


1.3实现一生产一消费

public class MyService {
	private Lock lock=new ReentrantLock();
	private Condition condition=lock.newCondition();
	private boolean hasValue=true;
	public void set(){
		try{
			lock.lock();
			while(hasValue){
				condition.await();
			}
			System.out.println("打印#");
			hasValue=true;
			condition.signal();
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public void get(){
		try{
			lock.lock();
			while(!hasValue){
				condition.await();
			}
			System.out.println("打印&");
			hasValue=false;
			condition.signal();
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
}

public class ThreadA extends Thread{
    private MyService myService;
    public ThreadA(MyService myService) {
        super();
        this.myService = myService;
    }
    @Override
    public void run() {
        for(int i=0;i<3;i++){
            myService.set();
        }
    }
}

public class ThreadB extends Thread{
    private MyService myService;
    public ThreadB(MyService myService) {
        super();
        this.myService = myService;
    }
    @Override
    public void run() {
        for(int i=0;i<3;i++){
            myService.get();
        }
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyService myService=new MyService();
        ThreadA a=new ThreadA(myService);
        a.start();
        ThreadB b=new ThreadB(myService);
        b.start();
    }
}

结果:


打印&

打印#

打印&

打印#

打印&

打印#


1.4.实现多生产多消费


程序照上面一生产一消费没有区别,只是Test测试时,里面new了多个生产者和多个消费者。上篇线程通信中了解到当出现多个生产者和多个消费者会出现所谓的线程假死现象,最终导致项目停掉。在我看来其原因是生产者和消费者共用了同一个condition,


以至于将消费者的锁和生产者的锁用了同一把锁,以至于有时候唤醒的是同类,就是生产者唤醒生产者,而没有唤醒消费者,以至于生产者没有对应的消费者,产生线程的假死。解决此办法就是将signal();唤醒方法换成signalAll();方法。将所有的线程都唤醒,这样每时每刻都有生产者和消费者相互对应了。


1.5公平锁与非公平锁


公平锁:从字面来看 “公平”两个字体现,线程获取锁的顺序是按照线程加锁的顺序来分配的,也就是队列中常说的先进先出的顺序。


非公平锁:就是以一种抢占的方式来获取锁,是随机获得锁,这样靠抢占的方式肯定会出现有的线程会获不到锁,所以说叫非公平的。


1.5.1实现:



先看下ReentrantLock为我们提供的API,是通过构造传参的方式来实现的。








public class Service {
	private ReentrantLock lock;

	public Service(boolean isFair) {
		lock=new ReentrantLock(isFair);
	}
	public void toService(){
		try{
			lock.lock();
			System.out.println("ThreadName="+Thread.currentThread().getName()+"获得锁定!");
		}finally{
			lock.unlock();
		}
	}
}

public class Test {
    public static void main(String[] args) {
        final Service service=new Service(true);
        Runnable runnable=new Runnable(){
            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("线程"+Thread.currentThread().getName()+"运行了!");
                service.toService();
            }    
        };
        Thread[] threadArr=new Thread[10];
        for(int i=0;i<10;i++){
            threadArr[i]=new Thread(runnable);
        }
        for(int i=0;i<10;i++){
            threadArr[i].start();
        }
    }
}

结果:


线程Thread-0运行了!

ThreadName=Thread-0获得锁定!

线程Thread-8运行了!

线程Thread-2运行了!

ThreadName=Thread-8获得锁定!

ThreadName=Thread-2获得锁定!

线程Thread-1运行了!

线程Thread-3运行了!

线程Thread-7运行了!

线程Thread-4运行了!

线程Thread-5运行了!

ThreadName=Thread-1获得锁定!

ThreadName=Thread-3获得锁定!

线程Thread-9运行了!

线程Thread-6运行了!

ThreadName=Thread-7获得锁定!

ThreadName=Thread-4获得锁定!

ThreadName=Thread-5获得锁定!

ThreadName=Thread-9获得锁定!

ThreadName=Thread-6获得锁定!


解释:查看结果,发现也不是完全呈有序状态,所以说公平锁 “基本” 呈有序状态。也就是说基本公平吧。


对于非公平锁,只要在Test中写Service service=new Service(false);测试发现基本上是乱序的,也就是说基本不公平吧。


1.6常用方法介绍:


<1>方法int getHoldCount();的作用是查询当前线程保持此锁定的个数,也就是表示调用lock();方法的次数。


public class Service {
	private ReentrantLock lock=new ReentrantLock();
	public void toService1(){
		try{
			lock.lock();
			System.out.println("toService1 getHoldCount="+lock.getHoldCount());
			toService2();
		}finally{
			lock.unlock();
		}
	}
	
	public void toService2(){
		try{
			lock.lock();
			System.out.println("toService2 getHoldCount="+lock.getHoldCount());
		}finally{
			lock.unlock();
		}
	}
}

public class Test {
    public static void main(String[] args) {
        Service service=new Service();
        service.toService1();
    }
} 

结果:


toService1 getHoldCount=1


toService2 getHoldCount=2


<2>方法int getQueueLength();的作用是返回正等待获取此锁定的线程估计数。通过英文名叫获得队列的长度,我在上一篇多线程通信的博文中提到了那个阻塞队列,如果当前线程等待了,就是被wait了,那么就会进入阻塞队列,而这个方法正是获取这个阻塞队列的长度,获取队列中有多少个正在等待的线程。


<3>方法int getWaitQueueLength(Condition condition);作用就是返回等待与此锁定相关的给定条件Condition的线程估计数。假如我有3个线程,都执行了同一个Condition对象的await();方法,而调用此方法就会返回3。


<4>方法boolean hasQueuedThread();方法作用是查询是否有线程正在等待获取此锁定。


<5>方法boolean hasQueuedThread(Thread thread);方法的作用是查询指定线程是否正在等待获取此锁定。


<6>方法boolean hasWaiters(Condition condition);的作用是查询是否有线程正在等待与此锁定有关的condition条件。


<7>方法boolean isFair();的作用是判断是不是公平锁。


<8>方法boolean isHeldByCurrentThread()的作用是查询当前线程是否保持锁定,就是当前线程是否在new Lock().lock与


new Lock().unlock();之间。


<9>方法boolean isLock();作用是查询此锁定是否由任意线程保持。


<10>方法 void lockInterruptibly();的作用是如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常。


<11>方法 boolean tryLock();作用仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定。


public class Service {
	private ReentrantLock lock=new ReentrantLock();
	public void waitMethod(){
		if(lock.tryLock()){
			System.out.println(Thread.currentThread().getName()+"获得锁!");
		}else{
			System.out.println(Thread.currentThread().getName()+"未获得锁!");
		}
	}
}

public class Test {
    public static void main(String[] args) {
        final Service service=new Service();
        Runnable runnable=new Runnable(){
            @Override
            public void run() {
                // TODO Auto-generated method stub
                service.waitMethod();
            }    
        };
        Thread threadA=new Thread(runnable);
        threadA.setName("A");
        threadA.start();
        Thread threadB=new Thread(runnable);
        threadB.setName("B");
        threadB.start();
    }
}

结果:


A获得锁!

B未获得锁!


<12>boolean tryLock(long timeout,TimeUnit unit);作用是如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。


<13>awaitUtil();方法:作用是造成当前线程在接到信号之前,被中断、或到达指定最后期限之前一直处于等待状态。


1.7Condition的执行顺序



原来我们都是Lock对象可以创建1个Condition,将不同线程对象注册在指定的Condition中,这样其实对于线程的规划很不灵活。我们可以通过Lock对象创建多个Condition,然后再去对线程规划,如改变执行顺序就很方便。




public class Test {
	//由于这个标志位作为线程之间是否等待的条件,所以线程间应当是可见的,才用volatile关键字修饰。
	private volatile static int nextThread=1;
	private static ReentrantLock lock=new ReentrantLock();
	final private static Condition conditionA=lock.newCondition();
	final private static Condition conditionB=lock.newCondition();
	final private static Condition conditionC=lock.newCondition();
	public static void main(String[] args) {
		Thread threadA=new Thread(){
			@Override
			public void run() {
				try{
					lock.lock();
					while(nextThread !=1){
						conditionA.await();
					}
					for(int i=0;i<3;i++){
						System.out.println("ThreadA"+(i+1));
					}
					nextThread=2;
					conditionB.signalAll();
				}catch(InterruptedException e){
					e.printStackTrace();
				}finally{
					lock.unlock();
				}
			}	
		};
		
		Thread threadB=new Thread(){
			@Override
			public void run() {
				try{
					lock.lock();
					while(nextThread !=2){
						conditionB.await();
					}
					for(int i=0;i<3;i++){
						System.out.println("ThreadB"+(i+1));
					}
					nextThread=3;
					conditionC.signalAll();
				}catch(InterruptedException e){
					e.printStackTrace();
				}finally{
					lock.unlock();
				}
			}	
		};
		
		Thread threadC=new Thread(){
			@Override
			public void run() {
				try{
					lock.lock();
					while(nextThread !=3){
						conditionC.await();
					}
					for(int i=0;i<3;i++){
						System.out.println("ThreadC"+(i+1));
					}
					nextThread=1;
					conditionA.signalAll();
				}catch(InterruptedException e){
					e.printStackTrace();
				}finally{
					lock.unlock();
				}
			}
		};
		Thread[] aArr=new Thread[2];
		Thread[] bArr=new Thread[2];
		Thread[] cArr=new Thread[2];
		for(int i=0;i<2;i++){
			aArr[i]=new Thread(threadA);
			bArr[i]=new Thread(threadB);
			cArr[i]=new Thread(threadC);
			aArr[i].start();
			bArr[i].start();
			cArr[i].start();
		}
	}
}

结果:


ThreadA1

ThreadA2

ThreadA3

ThreadB1

ThreadB2

ThreadB3

ThreadC1

ThreadC2

ThreadC3

ThreadA1

ThreadA2

ThreadA3

ThreadB1

ThreadB2

ThreadB3

ThreadC1

ThreadC2

ThreadC3



2.ReentrantReadWriteLock类的使用




ReentrantLock实现了互斥排他的效果了。意思就是同一时间只有一个线程在执行ReentrantLock.lock();方法。效率不高。JDK因此提供了一个ReentrantReadWriteLock类来提升效率。




搞读写锁就关键这几句话:读操作的锁叫共享锁,写操作的锁叫排他锁。就是遇见写锁就需互斥。那么以此可得出读读共享,写写互斥,读写互斥,写读互斥。




2.1读读共享


public class Service {
	private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
	public void read(){
		try{
			try{
				lock.readLock().lock();
				System.out.println("获取读锁"+Thread.currentThread().getName()+", "+System.currentTimeMillis());
				Thread.sleep(10000);
			}finally{
				lock.readLock().unlock();
			}
		}catch(InterruptedException e){
			e.printStackTrace();
		}	
	}
}

public class ThreadA extends Thread{
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.read();
    }
}

public class ThreadB extends Thread{
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.read();
    }
}


public class Test {
    public static void main(String[] args) {
        Service service=new Service();
        ThreadA a=new ThreadA(service);
        a.setName("A");
        ThreadB b=new ThreadB(service);
        b.setName("B");
        a.start();
        b.start();
    }
}

结果:


获取读锁A, 1474902942515

获取读锁B, 1474902942515


解释:由程序可以看出线程进行读操作,是同时进入lock();的,不存在互斥,就更没什么同步了。这样减少了等待的时间,提高的运行的效率。所以说是读读共享。


2.2写写互斥


service中提供的是写方法,然后线程调用写方法,其他的都和上面是一样测试。


public void write(){
		try{
			try{
				lock.writeLock().lock();
				System.out.println("获取写锁"+Thread.currentThread().getName()+", "+System.currentTimeMillis());
				Thread.sleep(10000);
			}finally{
				lock.writeLock().unlock();
			}
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}

结果:


获取写锁A, 1474903347768

获取写锁B, 1474903357769


解释:通过程序结果可以看出来,线程A获取写锁,然后执行代码睡眠10秒后,才释放锁。然后线程B才获取锁,然后睡眠10秒,释放锁。意思就是写锁同一时间只允许一个线程执行lock();方法后面的代码。体现了写锁是互斥的。


2.3写读互斥


public class Service {
	private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
	public void read(){
		try{
			try{
				lock.readLock().lock();
				System.out.println("获取读锁"+Thread.currentThread().getName()+", "+System.currentTimeMillis());
				Thread.sleep(10000);
			}finally{
				lock.readLock().unlock();
			}
		}catch(InterruptedException e){
			e.printStackTrace();
		}	
	}
	
	public void write(){
		try{
			try{
				lock.writeLock().lock();
				System.out.println("获取写锁"+Thread.currentThread().getName()+", "+System.currentTimeMillis());
				Thread.sleep(10000);
			}finally{
				lock.writeLock().unlock();
			}
		}catch(InterruptedException e){
			e.printStackTrace();
		}
	}
}

public class ThreadA extends Thread{
    private Service service;
    public ThreadA(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.write();
    }
}

public class ThreadB extends Thread{
    private Service service;
    public ThreadB(Service service) {
        super();
        this.service = service;
    }
    @Override
    public void run() {
        service.read();
    }
}

public class Test {
    public static void main(String[] args) {
        Service service=new Service();
        ThreadA a=new ThreadA(service);
        a.setName("A");
        ThreadB b=new ThreadB(service);
        b.setName("B");
        a.start();
        b.start();
    }
} 

结果:


获取写锁A, 1474903606138

获取读锁B, 1474903616145


解释:线程A先获得写锁,然后睡眠10秒,释放锁。然后线程B才去获得读锁,然后睡眠10秒,释放锁。可以看出来写锁的时候,读锁是不可获取锁的。所以说读写是互斥的。也证明了之前总结的,只要见到写锁就是互斥的。


2.4读写互斥


其他的都不变,就是先写再读,程序还是上面的。只是Test中先写线程,然后再读线程。


public class Test {
    public static void main(String[] args) throws InterruptedException {
        Service service=new Service();
        ThreadB b=new ThreadB(service);
        b.setName("B");
        b.start();
        Thread.sleep(1000);
        ThreadA a=new ThreadA(service);
        a.setName("A");
        a.start();
    }
}

结果:


获取读锁B, 1474904025583

获取写锁A, 1474904035586


解释:从程序结果也看出来了读写也是互斥的。







版权声明:本文为ya_1249463314原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。