Java高效并发(五)----jdk并发包的信号量、读写锁、倒计时器、循环栅栏

javautil.concurrent包(J.U.C)

J.U.C核心有5部分组成,atomic包,locks包,collections包,tools包,executors包

这一篇主要讲locks包,上一篇中的重入锁ReentrantLock类就是实现了locks包中的lock接口,。

(1)信号量(java.util.concurrent.Semaphore)

无论是内部锁synchronized和重入锁ReentrantLock都是控制一次只允许一个线程访问一个资源,信号量可以指定多个线程同时访问资源。提供构造函数,第二个可以指定是否公平,创建信号量对象,必须指定信号量的准入数,每次线程申请得到一个许可,直到等于准入数,则下一个申请的线程会等待,在申请许可的时候也支持响应中断或者尝试获得许可

public Semaphore(int permits)
public Semaphore(int permits,boolean fair)

 

方法类似于ReenteantLock中的方法

package xidian.lili.testreentrantlock;

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

public class SemapDemo implements Runnable{
	//指定最大许可数5,并且是公平锁
	public static Semaphore sema=new Semaphore(5,true);
	@Override
	public void run() {
		try {
			sema.acquireUninterruptibly();
			Thread.sleep(2000);
			System.out.println(Thread.currentThread().getName()+" done");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			sema.release();
		}	
	}
	public static void main(String[] args) {
		//妖用线程池创建10个线程
		ExecutorService exec =Executors.newFixedThreadPool(10);
		SemapDemo fd=new SemapDemo();
		for(int i=0;i<10;i++){
			exec.submit(fd);
		}

	}
}

 前5个差不多同时输出,一起获得信号量许可,暂停2000秒,在输出。然后释放信号量。然后到后面5个。

(2)读写锁(java.util.concurrent.locks.ReentrantReadWriteLock) 

ReentrantReadWriteLock这个类跟ReentrantLock没有关系,都是对接口的实现类,ReentrantLock实现的饿是ReentrantReadWriteLock实现了 java.util.concurrent.locks.ReadWriteLock接口,接口中有两个方法Lock readLock()和Lock writeLock();都返回lock对象。

读写锁的原理就是读锁和写锁分离,而读锁是共享锁,写锁是排它锁,使用synchronize和reentrantlock使得读读,读写,写写之间都是同步互斥的关系,但是在读多的情况下,效率低,所以读写锁分离适合读多的情况

任务:读18次数据,写一次数据

  • 首先我么对两个过程都是用重入锁,实验读写过程分开都是用重入锁
package xidian.lili.testreentrantlock;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
	private static Lock lock=new ReentrantLock(); //重入锁
	private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
	private static Lock readlock=readWriteLock.readLock();//读锁
	private static Lock writelock=readWriteLock.writeLock();//写锁
	private int value;
	private long start;
	private long end;
	
	//读锁控制
	public Object handleRead(Lock lock) throws InterruptedException{
		this.start=System.currentTimeMillis();
		try {
			lock.lock();
			Thread.sleep(1000);//模拟读操作,读操作越耗时,读写锁的优势越明显
			return value;
		}finally{
			lock.unlock();
		}
	}
	//写锁控制
	public void handleWrite(Lock lock,int value) throws InterruptedException{
		try {
			lock.lock();
			Thread.sleep(1000);//模拟写操作
			this.value=value;
		}finally{
			this.end=System.currentTimeMillis();
			System.out.println("重入锁读18次写一次用时:"+(end-start)+"ms");
			lock.unlock();
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		final ReadWriteLockDemo demo=new ReadWriteLockDemo();
		Runnable readRunnable=new Runnable(){
			@Override
			public void run() {	
				long start=System.currentTimeMillis();
				try {
					demo.handleRead(lock);//使用重入锁
					//demo.handleRead(readlock);使用读锁
				} catch (InterruptedException e) {
					e.printStackTrace();
				}//获得锁
			}
		};
		
		Runnable writeRunnable=new Runnable(){
			@Override
			public void run() {	
				try {
					//demo.handleWrite(writelock,new Random().nextInt());//获得写锁	
					demo.handleWrite(lock,new Random().nextInt());//获得重入锁锁
				} catch (InterruptedException e) {
					e.printStackTrace();
				}//获得读锁
			}

		};
	
		for(int i=0;i<18;i++){
			new Thread(readRunnable).start();
		}
		for(int i=10;i<11;i++){
			new Thread(writeRunnable).start();
		}
	}
	

}

结果:

  • 在读的时候用读锁,写的时候用分锁
package xidian.lili.testreentrantlock;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
	private static Lock lock=new ReentrantLock(); //重入锁
	private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
	private static Lock readlock=readWriteLock.readLock();//读锁
	private static Lock writelock=readWriteLock.writeLock();//写锁
	private int value;
	private long start;
	private long end;
	
	//读锁控制
	public Object handleRead(Lock lock) throws InterruptedException{
		this.start=System.currentTimeMillis();
		try {
			lock.lock();
			Thread.sleep(1000);//模拟读操作,读操作越耗时,读写锁的优势越明显
			return value;
		}finally{
			lock.unlock();
		}
	}
	//写锁控制
	public void handleWrite(Lock lock,int value) throws InterruptedException{
		try {
			lock.lock();
			Thread.sleep(1000);//模拟写操作
			this.value=value;
		}finally{
			this.end=System.currentTimeMillis();
			System.out.println("读写锁分离18次写一次用时:"+(end-start)+"ms");
			lock.unlock();
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		final ReadWriteLockDemo demo=new ReadWriteLockDemo();
		Runnable readRunnable=new Runnable(){
			@Override
			public void run() {	
				long start=System.currentTimeMillis();
				try {
					//demo.handleRead(lock);//使用重入锁
					demo.handleRead(readlock);//使用读锁
				} catch (InterruptedException e) {
					e.printStackTrace();
				}//获得锁
			}
		};
		
		Runnable writeRunnable=new Runnable(){
			@Override
			public void run() {	
				try {
					demo.handleWrite(writelock,new Random().nextInt());//获得写锁	
					//demo.handleWrite(lock,new Random().nextInt());//获得重入锁锁
				} catch (InterruptedException e) {
					e.printStackTrace();
				}//获得读锁
			}

		};
	
		for(int i=0;i<18;i++){
			new Thread(readRunnable).start();
		}
		for(int i=10;i<11;i++){
			new Thread(writeRunnable).start();
		}
	}
	

}

结果:

 (3)倒计时器(java.util.concurrent.CountDownLatch)

用来控制线程等待,让一个线程等待直到倒计时结束,典型的应用场景是火箭发射,让点火线程等到其他比如安全检查的线程结束在执行

CountDownLatch(int count):count计数器的计数个数

CountDownLatch的await()方法源码分析,无返回值

示例: 

package xidian.lili.testreentrantlock;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo implements Runnable {
	//表示要有10个线程完成任务
	public static CountDownLatch end=new CountDownLatch(10); 	
	static CountDownLatchDemo cdld=new CountDownLatchDemo();
	@Override
	public void run() {
		try {
			//模拟检查任务
			Thread.sleep(new Random().nextInt(10)*1000);
			System.out.println(Thread.currentThread().getName()+"检测安全");
			end.countDown();//没有一个线程过来完成任务,计数器减1
		} catch (InterruptedException e) {		
			e.printStackTrace();
		}	
	}
	public static void main(String[] args) throws InterruptedException {
		ExecutorService exec=Executors.newFixedThreadPool(10);
		for(int i=0;i<10;i++)
		{
			exec.submit(cdld);
		}
		end.await();//主线程等待,等待在end上的线程完成任务,主线程才开始执行下一步
		System.out.print("点火");
		exec.shutdown();
	}
}

 

(4)循环栅栏(java.util.concurrent.CyclicBarrier)

完成的功能类似倒计时器,CountDownLatch,也是控制线程之间循环等待,但是他的计数器是可以循环使用的

  • 构造方法:parties就是设置的计数器,当有parties数目个线程等待在这个栅栏上,就会执行barrierAction的run()方法,然后计数器归零,可以再继续使用

  •  等待方法,会抛出两个异常,一个是中断异常,一个是CyclicBarrier特有的,表示CyclicBarrier破换了,可能等不掉所有线程的到来,避免其他线程永久等待。

 可以看到返回一个整数,每一一次线程执行await的值,返回值就是增大1,当返回值为parties(int是从0开始返回的)

package xidian.lili.testreentrantlock;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static class Soldier implements Runnable{
    	private final CyclicBarrier cyclic;
    	private String name;
		public Soldier(CyclicBarrier cyclic, String name) {
			super();
			this.cyclic = cyclic;
			this.name = name;
		}
		@Override
		public void run() {
			try {
				System.out.println(name+"报道");
				System.out.println("计数器的值"+cyclic.await());//等到cyclic的计数器达到设定的值往下执行
				doWork();
				//System.out.println(name+"任务回来去集合了");
				cyclic.await();//等到cyclic的计数器达到设定的值往下执行,循环使用cyclic
			} catch (BrokenBarrierException e) {
					e.printStackTrace();
				} catch (InterruptedException e) {
				e.printStackTrace();
				}
		}
		private void doWork() {
			try {
				//模拟执行任务
				Thread.sleep(Math.abs(new Random().nextInt()%10000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(name+"完成任务");
		}  	
    }
    
    public static class BarrierRun implements Runnable{
    	int N;
    	Boolean flag;
    	
		public BarrierRun(int n, Boolean flag) {
			super();
			N = n;
			this.flag = flag;
		}

		@Override
		public void run() {
			// 每次cyclic计数完成执行的动作
			if(flag){
				System.out.println("司令:"+N+"个士兵集合完毕");
				flag=false;
			}else{
				System.out.println("司令:"+N+"个士兵完成任务");
			}			
		}    	
    }
	
	public static void main(String[] args) {
		final int N=5;
		boolean flag=true;
		//5个士兵线程
		Thread [] soliders=new Thread[5];
		CyclicBarrier cyclic=new CyclicBarrier(5,new BarrierRun(N,flag));
		System.out.println("士兵集合");
		for(int i=0;i<N;i++){
			//System.out.println("士兵"+i+"报道");
			soliders[i]=new Thread(new Soldier(cyclic,"士兵"+i));
			soliders[i].start();			
		}

	}

}

结果:

(5)线程阻塞工具类(java.util.concurrent.locks.LockSupport)

 是一个非常实用的可以在任何位置阻塞线程的工具,LockSupport.park()用来阻塞线程,umpark()方法唤醒线程。LockSupport阻塞和唤醒线程的功能是依赖于sun.misc.Unsafe

它是用相比于Thread.suspend(),可以避免resume()方法先执行造成死锁,相比于wait方法可以不同获得对象的锁还可以时间限时等待。而且park()方法阻塞线程支持中断响应,它不会抛出InterruptedException异常,而是默默返回

这里的阻塞和唤醒采用类似信号量的机制来实现,每一个线程都有一个许可(只有一个许可),执行LockSupport.park()方法,如果线程的许可还在会立即返回,如果许可不可用,则阻塞,而unpark()方法唤醒线程,也就是将线程的许可置为可用,所以就算unpark()方法先执行了,在调用park方法阻塞的时候,也会消费掉unpark()带来的许可,然后返回。区别于如果resume()方法先执行了,在执行suspend()方法就会造成阻塞的线程永久阻塞。

 实例对比:park,unpark程序顺利结束

package xidian.lili.testreentrantlock;
import java.util.concurrent.locks.LockSupport;
public class LockSupportDemo{
	public static  Object u=new Object();
	public static class changeObjectThread extends Thread{
		String name;
		public changeObjectThread(String name) {
			super.setName(name);
		}
		@Override
		public void run() {
			synchronized(u){
				System.out.println("in "+Thread.currentThread().getName());
				LockSupport.park();
                //LockSupport.park(this);//查看因为哪个对象挂起
				//Thread.currentThread().suspend();
			}
		}		
	}
	public static void main(String[] args) throws InterruptedException {
		changeObjectThread t1=new changeObjectThread("t1");
		changeObjectThread t2=new changeObjectThread("t2");
		t1.start();
		Thread.sleep(1000);
		t2.start();
		//t1.resume();
		//t2.resume();
		LockSupport.unpark(t1);
		LockSupport.unpark(t2);
		t1.join();
		t2.join();//
	}
}

使用jstack 查看线程运行情况,我们可以看到t1和t2在过程中因为park被挂起,等待


 而且我们也可以产看因为哪个对象挂起,LockSupport.park(this);

用jstack查看线程运行状态:可以看到是在等待哪个对象


 但是用suspend和resume:

 我们知道由于t2线程先执行了resume方法,所以t2应该是被挂起的,但是这里显示它是Runnable状态

猜你喜欢

转载自blog.csdn.net/wangdongli_1993/article/details/81233885