JAVA 线程同步之计数器

CountDownLatch

    CountDownLatch是java.util.concurrent(jdk1.5加入并发包)包下的类。CountDownLatch主要提供的机制是当多个(具体的数量等于初始化CountDownLatch时的count参数的值)线程都达到了预期状态或者完成预期工作时触发事件,其他线程可以等待这个事件来触发自己后续的工作。这里需要注意的是,等待的线程可以使多个,即CountDownLatch是可以唤醒多个等待的线程的。到达自己预期状态的线程会调用CountDownLatch的countDown方法,而等待的线程会调用CountDownLatch的await方法



CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1,当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

CountDownLatch中主要方法如下:

  public CountDownLatch(int count),构造函数中的count(计数器)实际上就是闭锁需要等待的线程数量,这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值。

   public void countDown(),每调用一次这个方法,在构造函数中初始化的count值就减1,通知机制是此方法来完成的。

   public void await() throws InterruptedException,调用此方法的当前线程会一直阻塞,直到计时器的值为0。

import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {

	public static void main(String[] args) throws Exception {
		CountDownLatch latch = new CountDownLatch(3);
		Worker worker1 = new Worker("Jack 程序员1", latch);
		Worker worker2 = new Worker("Rose 程序员2", latch);
		Worker worker3 = new Worker("Json 程序员3", latch);
		worker1.start();
		worker2.start();
		worker3.start();

		latch.await();
		System.out.println("Main thread end!");

	}

	static class Worker extends Thread {
		private String workerName;
		private CountDownLatch latch;

		public Worker(String workerName, CountDownLatch latch) {
			this.workerName = workerName;
			this.latch = latch;
		}

		@Override
		public void run() {
			try {
				System.out.println("Worker:" + workerName + " is begin.");
				Thread.sleep(1000L);
				System.out.println("Worker:" + workerName + " is end.");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			latch.countDown();
		}
	}

}

结果

Worker:Json 程序员3 is begin.
Worker:Jack 程序员1 is begin.
Worker:Rose 程序员2 is begin.
Worker:Json 程序员3 is end.
Worker:Jack 程序员1 is end.
Worker:Rose 程序员2 is end.
Main thread end!

CyclicBarrier

    CyclicBarrier :循环屏障,CyclicBarrier 可以协同多个线程,让多个线程在这个屏障前等待,直到所有线程都达到了这个屏障时,再一起继续执行后面的动作



从上图看出,三个线程中各有一个barrier.await(),那么任何一个线程在执行到barrier.await()时就会进入阻塞等待状态,直到三个线程都到了barrier.await时才会同时从await返回,继续后续的工作。此外,如果在构造CyclicBarrier 时设置了一个Runnable实现,那么最后一个到barrier.await的线程会执行这个Runnable的run方法,以完成一些预设的工作

public CyclicBarrier(int parties) 
public CyclicBarrier(int parties, Runnable barrierAction)

实例:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
	public static void main(String args[]) throws Exception {

		CyclicBarrier barrier = new CyclicBarrier(3, new TotalTask());

		BillTask worker1 = new BillTask("111", barrier);
		BillTask worker2 = new BillTask("222", barrier);
		BillTask worker3 = new BillTask("333", barrier);
		worker1.start();
		worker2.start();
		worker3.start();
		System.out.println("Main thread end!");
	}

	static class TotalTask extends Thread {
		public void run() {
			System.out.println("所有子任务都执行完了,就开始执行主任务了。");
		}
	}

	static class BillTask extends Thread {
		private String billName;
		private CyclicBarrier barrier;

		public BillTask(String workerName, CyclicBarrier barrier) {
			this.billName = workerName;
			this.barrier = barrier;
		}

		@Override
		public void run() {
			try {
				System.out.println("市区:" + billName + "运算开始:");
				Thread.sleep(1000L);// 模仿第一次运算;
				System.out.println("市区:" + billName + "运算完成,等待中...");
				barrier.await();// 假设一次运算不完,第二次要依赖第一次的运算结果。都到达这个节点之后后面才会继续执行;
				System.out.println("全部都结束,市区" + billName + "才开始后面的工作。");
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (BrokenBarrierException e) {
				e.printStackTrace();
			}
		}
	}

}

结果:

市区:111运算开始:
Main thread end!
市区:222运算开始:
市区:333运算开始:
市区:111运算完成,等待中...
市区:222运算完成,等待中...
市区:333运算完成,等待中...
所有子任务都执行完了,就开始执行主任务了。//这句话是最后到达wait()方法的那个线程执行的
全部都结束,市区111才开始后面的工作。
全部都结束,市区333才开始后面的工作。
全部都结束,市区222才开始后面的工作。

在这个示例中,构造CyclicBarrier时,传入了内部类TotalTask(TotalTask继承了Thread,是Runnable的实现)的实例对象,其意义在于:当所有的线程都执行到await()方法时,它们会一起返回继续自己的工作,但是最后一个到达await()方法的线程会执行TotalTask的run()方法;如果在构造构造CyclicBarrier时没有传入Runnable的实现对象作为构造参数,则当所有的线程都执行到await()方法时会直接一起返回继续自己的工作。

(3)CyclicBarrier与CountDownLatch的区别

A、CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待;

B、CountDownLatch的计数器无法被重置;而CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。

C、参数parties指定线程数量,当指定的线程值都到达栅栏点时,栅栏打开,线程恢复。需要注意的是,当指定的线程数量大于启动的线程数量,比如修改上例中的代码,只启动9个线程,那么所有的线程将一直处于等待状态。第二种情况是指定的线程数量小于启动的线程,上例代码,启动11个线程,那么当第十个线程到达栅栏点时,那么这十个线程就会恢复继续执行,而第十一个线程将一直处于阻塞状态

Semaphore(信号量)

        Java中的Semaphore用于在线程间传递信号,从概念上讲,信号量维护了一个许可集合,Semaphore只对可用的许可进行计数,并采取相应的行动。信号量常常用于多线程的代码中,比如数据库连接池。

        semaphore 是用于管理信号量的,构造的时候传入可供管理的信号量数值,简单来说,信号量对象管理的信号就像令牌,构造时传入个数,总数是控制并发的数量。我们需要控制并发的代码,执行前先获取信号(通过acquire获取信号许可),执行后归还信号(通过release归还信号许可)。每次acquire成功返回后,semaphore 可用的信号量就会减少一个,如果没有可用的信号,acquire调用就会阻塞,等待有release 调用释放信号后,acquire 才会得到信号并返回

        如果Semaphore 管理的信号量只有一个,那么就退化到互斥锁了;如果对于 1个信号量,则主要用于控制并发数,与通过控制线程数来控制数来控制并发数的方式比,通过Semaphore 来控制并发数可以控制得更加细粒度,因为真正被控制最大并发的代码放到acquire 和release 之间就行了

实例:

假设一个服务器资源有限,任意某一时刻只允许3个人同时进行访问,这时一共来了10个人

import java.util.concurrent.Semaphore;

public class SemaphoreDemo {

	public static void main(String args[]) throws Exception {

		final Semaphore semaphore = new Semaphore(3);// 一次只运行3个人进行访问
		for (int i = 0; i < 10; i++) {
			final int no = i;
			Runnable thread = new Runnable() {
				public void run() {
					try {
						System.out.println("用户" + no + "连接上了:");
						Thread.sleep(300L);
						semaphore.acquire();// 获取接下去执行的许可
						System.out.println("用户" + no + "开始访问后台程序...");
						Thread.sleep(1000L);// 模仿用户访问服务过程
						semaphore.release();// 释放允许下一个线程访问进入后台
						System.out.println("用户" + no + "访问结束。");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			};
			new Thread(thread).start();
		}

		System.out.println("Main thread end!");
	}
}

结果

用户0连接上了:
用户2连接上了:
用户4连接上了:
用户1连接上了:
Main thread end!
用户6连接上了:
用户5连接上了:
用户7连接上了:
用户9连接上了:
用户8连接上了:
用户3连接上了:
用户0开始访问后台程序...
用户2开始访问后台程序...
用户4开始访问后台程序...
用户0访问结束。
用户1开始访问后台程序...
用户6开始访问后台程序...
用户2访问结束。
用户4访问结束。
用户5开始访问后台程序...
用户1访问结束。
用户7开始访问后台程序...
用户6访问结束。
用户9开始访问后台程序...
用户5访问结束。
用户8开始访问后台程序...
用户7访问结束。
用户3开始访问后台程序...
用户9访问结束。
用户8访问结束。
用户3访问结束。

从结果上可以看出来,10个人同时进来,但是只能同时3个人访问资源,释放一个允许进来一个

Exchanger

Exchanger类源于java.util.concurrent包,它可以在两个线程之间传输数据,Exchanger中的public V exchange(V x)方法被调用后等待另一个线程到达交换点(如果当前线程没有被中断),然后将已知的对象传给它,返回接收的对象。如果另外一个线程已经在交换点等待,那么恢复线程计划并接收通过当前线程传给的对象:

实例:

import java.util.concurrent.Exchanger;

public class Car extends Thread {

	private Exchanger<String> exchanger;

	public Car(Exchanger<String> exchanger) {
		super();
		this.exchanger = exchanger;
	}

	@Override
	public void run() {
		try {
			System.out.println(Thread.currentThread().getName() + ": "
					+ exchanger.exchange("Car"));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		Exchanger<String> exchanger = new Exchanger<>();
		Car car = new Car(exchanger);
		Bike bike = new Bike(exchanger);
		car.start();
		bike.start();
		System.out.println("Main end!");
	}

}

class Bike extends Thread {
	private Exchanger<String> exchanger;

	public Bike(Exchanger<String> exchanger) {
		super();
		this.exchanger = exchanger;
	}

	@Override
	public void run() {
		try {
			System.out.println(Thread.currentThread().getName() + ": "
					+ exchanger.exchange("Bike"));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

结果

Main end!
Thread-0: Bike
Thread-1: Car

参考资料:

http://ifeve.com/java-memory-model-1/


猜你喜欢

转载自blog.csdn.net/ronin_88/article/details/80545467