秋招准备-Java-并发编程-同步工具(七)

1.同步工具


1.同步工具(java.util.concurrent包)

    1.Semaphore

    Semaphore是计数信号量。

    从概念上讲,Semaphore维护了一个许可集。比方说,定义一个有三个许可的Semaphore,那么在这个许可集下,使用acquire()方法前,会检查许可集,如果许可1,2,3都用了,那么就会阻塞acquire(),直到有一个许可可用,然后获得该许可。然后release()方法会让拥有许可者释放许可。

    而实际上,Semaphore中并不会有真正的许可1,2,3.它有的只是一个可用许可的数目,当acquire()时,如果可用许可为0,则阻塞acquire(),而如果可用许可不为0,则许可数-1,相当于获得许可,然后不阻塞,继续acquire()后面的内容。

    release()也是同样,使许可数+1,便相当于释放了许可。

    这样,使用Semaphore可以用于限制访问某些资源的线程数目。(限制线程数目)

class Semaphore
{
	public Semaphore(int permits);//非公平,permits个许可
	public Semaphore(int permits,boolean fair)//可设置公平
	//公平情况下,按线程调用acquire()的顺序,来获得许可,FIFO
	void acquire();//获得许可
	void release();//释放许可
}

    Tips:

    1.调用acquire()时无法保持同步锁.

    2.设置许可数量为1时,也相当与一种互斥同步,只允许一个线程获得许可,其他线程调用acquire()时,都会阻塞。这种实现和Lock与synchronized的区别是,许可不是具体的对象,不归当前获得许可的线程所有,只要有任何线程调用了该Semaphore对象的release()方法,则许可释放,当前线程会继续运行,而acquire()处阻塞的线程会选择一个获得许可,然后恢复运行。

    这种情况的话,可以用来解一些死锁,但会出现一些问题,比如就可能会有两个线程同时运行在需要获得许可的代码区,如果涉及共享数据,就有可能线程不安全。

    并且不是从一开始的线程release()许可,但一开始的线程有可能最后还是会release(),那么这时候代码结构就会很乱。

    3.非公平时,会出现许可刚被释放时,如果有线程刚好在此时申请许可,则会发生插队,而在公平的情况下,申请线程直接插入到等待队列的队尾,因此可以保证获得许可的顺序是线程调用acquire()的顺序。

    简单测试:

public class Main
{
	public static void main(String[] args)throws Exception 
	{
		Semaphore s = new Semaphore(2);//1,3,4,5
		for(int i=0;i<5;i++)
		{
			Thread t = new Thread(new Runnable() {
				public void run() {
					try {
						s.acquire();
						System.out.println(Thread.currentThread()+"begin");
						Thread.sleep(1000);
						System.out.println(Thread.currentThread()+"end");
					}catch(Exception ex) {}
					finally{
						s.release();
					}
				}
			});
			t.start();
		}
	}
}


    2.CyclicBarrier

    CyclicBarrier是一个同步辅助工具:(可循环屏障)

    它允许一组线程互相等待,直到到达某个公共屏障点(common barrier point)。

    实际上即是确认屏障拦截的线程组的数目,然后在每个线程做好准备后,调用该屏障的await()方法,代表线程准备好了,直到做好准备并且调用了await()方法的线程数目达到了确认的拦截数目,则在最后一个线程await()时,打开屏障,所有准备好了的线程同时恢复。

class CyclicBarrier
{
	public CyclicBarrier(int parties);//线程组数
	public CyclicBarrier(int parties,Runnable barrierAction);//开放前执行参数线程
	
	public int await();//拦截
	public void reset();//重新设置组
	public int getNumberWaiting();//返回当前拦截线程数
}

    Tips:

    1.在构造器上,初始化线程组数,并且可选传入一个Runnable对象,最后一个线程到达后,所有线程恢复前,会执行这个对象的run()方法。

    2.对于失败的同步尝试,采取要么全部要么全不,如果在一组拦截中,有一个线程因为超时或者中断离开了屏障点,那么该屏障点处等待的其他线程也会以反常的方式离开。

    3.reset()先中断当前的屏障点所有线程,然后重新开一屏障拦截。

    4.CyclierBarrier是可循环的,因此一个屏障对象在建立好后,可以一直使用。

    简单测试:

public class Main
{
	public static void main(String[] args)throws Exception 
	{
		CyclicBarrier barrier = new CyclicBarrier(5,new Runnable() {
			public void run()
			{
				System.out.println("preparing-开始执行所有等待线程");
			}
		});
		for(int i=0;i<10;i++)
		{
			Thread.sleep(1000);
			Thread t = new Thread(new Runnable() {
				public void run() {
					System.out.println("已有"+barrier.getNumberWaiting()+"线程准备");
					try{
						barrier.await();
					}catch(Exception ex) {}
					System.out.println("execute"+Thread.currentThread());
				}
			});
			t.start();
		}
	}
}


    3.CountDownLatch

    CountDownLatch是一个同步辅助工具,(闭锁)

    在完成一组正在其他线程中执行的操作前,它允许一个或多个线程一直等待。

    用给定的计数count初始化CountDownLatch,通过调用countDown()方法减少计数,在计数达到零前,await()方法会一直阻塞。之后,则会释放所有等待的线程。这种现象只出现一次,计数无法被重置。

    用途:

    1.将计数为1的CountDownLatch作为开关或者入口,在调用countDown()的线程打开入口之前,所有调用await()的线程都会阻塞。

    2.计数为n的CountDownLatch,可以使一个(多个)线程在n个线程完成某个操作,或者一个操作完成n次前一直阻塞。

class CountDownLatch
{
	public CountDownLatch(int count);//初始化计数
	
	public void await()
	public void countDown()
	public long getCount() 
}
    简单测试:
public class Main
{
	public static void main(String[] args)throws Exception 
	{
		CountDownLatch latch = new CountDownLatch(3);
		for(int i=0;i<5;i++)
		{
			Thread t = new Thread(new Runnable() {
				public void run() {
					try {
						latch.await();
					}catch(Exception ex) {}
					System.out.println("execute"+Thread.currentThread());
				}
			});
			System.out.println("线程"+i+"准备");
			t.start();
		}

		for(int i=0;i<3;i++)
		{
			Thread t = new Thread(new Runnable() {
				public void run() {
					try {
						Thread.sleep(1000);
					}catch(Exception ex) {}
					System.out.println("剩余count:"+latch.getCount());
					latch.countDown();
				}
			});
			t.start();
			t.join();
		}
	}
}


    4.Exchanger
class Exchanger<V>
{
	public Exchanger();
	
	public V exchange(V x);
	public V exchange(V x, long timeout, TimeUnit unit);
}

    可以在对中对元素进行配对和交换的线程的同步点。

    两个线程间,通过exchange()进行配对,传入V对象,并接受对方传入的对象。可以被视为SynchronousQueue的双向形式。

    简单测试:

public class Main
{
	public static void main(String[] args)throws Exception 
	{
		Exchanger<String> ex = new Exchanger();
		Thread t1 = new Thread(new Runnable() {
			public void run() {
				try {
					String s = "线程1:"+Thread.currentThread();
					System.out.println(s);
					String product1 = "线程1生产";
					String p = ex.exchange(product1);
					System.out.println(s+" "+p);
				}catch(Exception ex) {}
			}
		});
		Thread t2 = new Thread(new Runnable() {
			public void run() {
				try {
				String s = "线程2:"+Thread.currentThread();
				System.out.println(s);
				String product2 = "线程2生产";
				String p = ex.exchange(product2);
				System.out.println(s+" "+p);
				}catch(Exception ex) {}
			}
		});
		t1.start();
		Thread.sleep(1000);
		t2.start();
	}
}

    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值