1.同步器Synchronizer
synchronizer是一个对象,它根据自身状态调节线程的控制流。阻塞队列可以扮演一个synchronizer,其他类型的synchronizer包括:信号量(semaphore)、关卡(barrier)和闭锁(latch)。
1.1闭锁(latch)
闭锁是可以延迟线程的进度直到线程到达终点状态的一种同步器。每个闭锁就相当于是一道大门,在闭锁到达重点状态之前,门一直是关闭的,没有线程可以通过,当到达终点状态时,门打开了,允许所有的线程通过。一旦闭锁到达终点状态,就不能再改变状态了,也就是永远保持开放的状态。闭锁用来确保特定活动直到其他活动都完成后才发生。
TestHarness使用两个阀门,开始阀门将计数器初始化为1,结束阀门将计数器初始化为工作线程的数量。每个工作线程要做的第一件事是等待阀门打开。每个线程最后一件事是将结束阀门减一,这样做使得工作线程有效的等待,直到最后一个工作线程完成了自己的任务,这个就可以计算整个过程的耗时。
1.2 FutureTask
FutureTask也可以作为闭锁。FutureTask描述了一个抽象的可携带结果的计算。FutureTask的计算是通过Callable实现的,它等价于一个可以携带结果的Runnable。它有三个状态:等待、运行和完成。完成包括:正常结束、取消和异常结束。一旦FutureTask进入完成状态,他就会永远停止在这个状态上。
Future.get的行为依赖于任务的状态。如果它已经完成,get立刻返回得到结果,否则会阻塞直到任务进入完成状态。FutureTask把计算的结果从当前的线程传送到需要该结果的线程。
利用FutureTask来完成异步任务,并在真正需要的计算结果之前就启动异步任务,这样可以减少等待的时间。
1.3 信号量(semaphore)
信号量用来控制能够同时访问某特定资源的活动的数量,或者是同时执行某一给定操作的数量。信号量可以用来实现资源池或者某一容器的限定的边界。
一个信号量管理一个有效的许可集合,活动能够获得许可,并在使用完后,释放许可。如果没可用的许可了,那么acquire方法会被阻塞,只有有可用的许可为止。Release方法向信号量返回一个许可。
信号量可以用来实现资源池,比如数据库连接池。每个池子有固定的长度,当池子为空,向池子申请资源将会失败,需要阻塞请求,一旦池子非空,解除阻塞。
用信号量将容器转化为有界容器。信号量被初始化为指定的大小,每次添加操作,向底层容器执行add操作之前,获取一个许可。同样的,每次删除的时候,在底层容器删除之后,释放一个许可。
1.4 关卡(barrier)
关卡类似于闭锁,它能阻塞一组线程,知道某些事件的发生。关卡与闭锁的关键不同在于,所有的线程必须同时到达关卡点,才能继续处理。闭锁等待的是事件,关卡等待的是其他线程。就是说闭锁用来等待的事件就是countDown事件,只有该countDown事件执行后所有之前在等待的线程才有可能继续执行;而栅栏没有类似countDown事件控制线程的执行,只有线程的await方法能控制等待的线程执行。CyclicBarrier强调的是n个线程,大家相互等待,只要有一个没完成,所有人都得等着。
当线程到达关卡点,调用await,await会被阻塞,直到所有线程都到达关卡点。如果所偶线程都到达了关卡点,关卡被成功突破,这样所有的被阻塞的线程会被释放,此时关卡被重置,以备下一次使用。如果调用await超时,或者阻塞中的线程被中断,那么关卡就被置为失败。所有的对await未完成的调用,都会通过抛出BrokenBarrierException而终止。
10个人去春游,规定达到一个地点后才能继续前行:
synchronizer是一个对象,它根据自身状态调节线程的控制流。阻塞队列可以扮演一个synchronizer,其他类型的synchronizer包括:信号量(semaphore)、关卡(barrier)和闭锁(latch)。
1.1闭锁(latch)
闭锁是可以延迟线程的进度直到线程到达终点状态的一种同步器。每个闭锁就相当于是一道大门,在闭锁到达重点状态之前,门一直是关闭的,没有线程可以通过,当到达终点状态时,门打开了,允许所有的线程通过。一旦闭锁到达终点状态,就不能再改变状态了,也就是永远保持开放的状态。闭锁用来确保特定活动直到其他活动都完成后才发生。
/**
* @Title: TestHarnessTest.java
* @Package synchronizer
* @Description: TODO
* @author 落叶飞翔的蜗牛
* @date 2017年12月16日 上午10:40:11
* @version V1.0
*/
package synchronizer;
import java.util.concurrent.CountDownLatch;
/**
* @author 落叶飞翔的蜗牛
* @date 2017年12月16日 上午10:40:11
*/
public class TestHarness {
public static long timeTasks(int nThread, final Runnable task) {
long start = System.currentTimeMillis();
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThread);
for (int i = 0; i < nThread; i++) {
Thread thread = new Thread() {
@Override
public void run() {
try {
startGate.await();
try {
task.run();
} finally {
System.out.println("单个任务结束");
endGate.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
}
startGate.countDown();
try {
endGate.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有任务结束");
long end = System.currentTimeMillis();
return end - start;
}
}
/**
* @Title: TestHarnessTest.java
* @Package synchronizer
* @Description: TODO
* @author 落叶飞翔的蜗牛
* @date 2017年12月16日 上午10:46:05
* @version V1.0
*/
package synchronizer;
import org.junit.Test;
/**
* @author 落叶飞翔的蜗牛
* @date 2017年12月16日 上午10:46:05
*/
public class TestHarnessTest {
@Test
public void test() {
int nThread = 2;
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("单个任务开始");
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
long time = TestHarness.timeTasks(nThread, task);
System.out.println("运行耗时:" + time);
}
}
TestHarness使用两个阀门,开始阀门将计数器初始化为1,结束阀门将计数器初始化为工作线程的数量。每个工作线程要做的第一件事是等待阀门打开。每个线程最后一件事是将结束阀门减一,这样做使得工作线程有效的等待,直到最后一个工作线程完成了自己的任务,这个就可以计算整个过程的耗时。
1.2 FutureTask
FutureTask也可以作为闭锁。FutureTask描述了一个抽象的可携带结果的计算。FutureTask的计算是通过Callable实现的,它等价于一个可以携带结果的Runnable。它有三个状态:等待、运行和完成。完成包括:正常结束、取消和异常结束。一旦FutureTask进入完成状态,他就会永远停止在这个状态上。
Future.get的行为依赖于任务的状态。如果它已经完成,get立刻返回得到结果,否则会阻塞直到任务进入完成状态。FutureTask把计算的结果从当前的线程传送到需要该结果的线程。
利用FutureTask来完成异步任务,并在真正需要的计算结果之前就启动异步任务,这样可以减少等待的时间。
/**
* @Title: PreLoader.java
* @Package synchronizer.future
* @Description: TODO
* @author 落叶飞翔的蜗牛
* @date 2017年12月16日 下午2:24:46
* @version V1.0
*/
package synchronizer.future;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author 落叶飞翔的蜗牛
* @date 2017年12月16日 下午2:24:46
*/
public class PreLoader {
/**
* future:FutureTask<Object> TODO(模拟异步任务).
* @author: 落叶飞翔的蜗牛
* @date: 2017年12月16日 下午3:17:41
*/
private final FutureTask<Object> future = new FutureTask<>(new Callable<Object>() {
/**
* TODO 异步任务,睡眠5s
* @see java.util.concurrent.Callable#call()
*/
@Override
public Object call() throws Exception {
Thread.sleep(5000);
return "SUCCESS";
}
});
private final Thread thread = new Thread(future);
public void start() {
thread.start();
}
/**
* get:(获取异步线程执行结果). <br/>
* @author 落叶飞翔的蜗牛
* @date:2017年12月16日 下午3:24:52
* @return Object
*/
public Object get() {
try {
return future.get();
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
/**
* @Title: PreLoaderTest.java
* @Package synchronizer.future
* @Description: TODO(用一句话描述该文件做什么)
* @author 落叶飞翔的蜗牛
* @date 2017年12月16日 下午3:34:56
* @version V1.0
*/
package synchronizer.future;
import org.junit.Test;
/**
* ClassName: PreLoaderTest <br/>
* Function: 测试PreLoader. <br/>
* date: 2017年12月16日 下午3:34:56 <br/>
* @author 落叶飞翔的蜗牛
* @version
* @since JDK 1.6
*/
public class PreLoaderTest {
/**
* test:(测试PreLoader). <br/>
* @author 落叶飞翔的蜗牛
* @date:2017年12月16日 下午3:47:29
*/
@Test
public void test() {
PreLoader preLoader = new PreLoader();
preLoader.start();
long start = System.currentTimeMillis();
/**
* 当程序需要用到异步数据时,可以调用get方法.
* 如果异步任务完成,这直接返回.
* 否则等待加载结束后返回
*/
System.out.println("获取结果:" + preLoader.get());
long end = System.currentTimeMillis();
System.out.println("执行耗时: " + (end - start));
}
}
1.3 信号量(semaphore)
信号量用来控制能够同时访问某特定资源的活动的数量,或者是同时执行某一给定操作的数量。信号量可以用来实现资源池或者某一容器的限定的边界。
一个信号量管理一个有效的许可集合,活动能够获得许可,并在使用完后,释放许可。如果没可用的许可了,那么acquire方法会被阻塞,只有有可用的许可为止。Release方法向信号量返回一个许可。
信号量可以用来实现资源池,比如数据库连接池。每个池子有固定的长度,当池子为空,向池子申请资源将会失败,需要阻塞请求,一旦池子非空,解除阻塞。
用信号量将容器转化为有界容器。信号量被初始化为指定的大小,每次添加操作,向底层容器执行add操作之前,获取一个许可。同样的,每次删除的时候,在底层容器删除之后,释放一个许可。
/**
* @Title: BoundedHashSet.java
* @Package synchronizer.semaphore
* @Description: TODO(有界HashSet)
* @author 落叶飞翔的蜗牛
* @date 2017年12月16日 下午4:56:20
* @version V1.0
*/
package synchronizer.semaphore;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;
/**
* ClassName: BoundedHashSet <br/>
* @author 落叶飞翔的蜗牛
* date: 2017年12月16日 下午4:56:20 <br/>
* Function: TODO 有界HashSet. <br/>
* Reason: TODO ADD REASON(可选). <br/>
* @version V1.0
*/
public class BoundedHashSet<T> {
private Set<T> set = null;
private Semaphore semaphore = null;
public BoundedHashSet(int bound) {
this.set = Collections.synchronizedSet(new HashSet<>());
semaphore = new Semaphore(bound);
}
/**
* add:(添加元素). <br/>
* TODO (添加元素之前,先获取一个许可,如果没有剩余许可,阻塞).<br/>
* @author 落叶飞翔的蜗牛
* @date: 2017年12月16日 下午5:11:14
* @param t
* @return
* @throws InterruptedException
*/
public boolean add(T t) throws InterruptedException {
semaphore.acquire();
boolean isAdded = false;
try {
isAdded = set.add(t);
return isAdded;
} finally {
if(!isAdded) {
semaphore.release();
}
}
}
/**
* remove:(删除操作). <br/>
* TODO (删除成功后释放许可).<br/>
* @author 落叶飞翔的蜗牛
* @date: 2017年12月16日 下午5:14:35
* @param t
* @return
*/
public boolean remove(T t) {
boolean isRemoved = set.remove(t);
if(isRemoved) {
semaphore.release();
}
return isRemoved;
}
}
/**
* @Title: BoundedHashSetTest.java
* @Package synchronizer.semaphore
* @Description: 测试BoundedHashSet
* @author 落叶飞翔的蜗牛
* @date 2017年12月16日 下午5:18:12
* @version V1.0
*/
package synchronizer.semaphore;
import org.junit.Test;
/**
* @ClassName: BoundedHashSetTest
* @Description: (测试BoundedHashSet)
* @author 落叶飞翔的蜗牛
* @date 2017年12月16日 下午5:18:12
*/
public class BoundedHashSetTest {
/**
* testAdd:(测试添加). <br/>
* @Description: (第四个元素加入失败).<br/>
* @author 落叶飞翔的蜗牛
* @date: 2017年12月16日 下午5:27:06
* @throws InterruptedException
*/
@Test
public void testAdd() throws InterruptedException {
BoundedHashSet<String> boundedHashSet = new BoundedHashSet<>(3);
boundedHashSet.add("1");
System.out.println("加入第1个元素成功");
boundedHashSet.add("2");
System.out.println("加入第2个元素成功");
boundedHashSet.add("3");
System.out.println("加入第3个元素成功");
boundedHashSet.add("4");
System.out.println("加入第4个元素成功");
}
/**
* testAddAndRemove:(测试添加删除). <br/>
* @Description: (删除元素1后,元素4就可以加入).<br/>
* @author 落叶飞翔的蜗牛
* @date: 2017年12月16日 下午5:29:45
* @throws InterruptedException
*/
@Test
public void testAddAndRemove() throws InterruptedException {
BoundedHashSet<String> boundedHashSet = new BoundedHashSet<>(3);
boundedHashSet.add("1");
System.out.println("加入第1个元素成功");
boundedHashSet.add("2");
System.out.println("加入第2个元素成功");
boundedHashSet.add("3");
System.out.println("加入第3个元素成功");
boundedHashSet.remove("1");
System.out.println("删除第1个元素成功");
boundedHashSet.add("4");
System.out.println("加入第4个元素成功");
}
}
1.4 关卡(barrier)
关卡类似于闭锁,它能阻塞一组线程,知道某些事件的发生。关卡与闭锁的关键不同在于,所有的线程必须同时到达关卡点,才能继续处理。闭锁等待的是事件,关卡等待的是其他线程。就是说闭锁用来等待的事件就是countDown事件,只有该countDown事件执行后所有之前在等待的线程才有可能继续执行;而栅栏没有类似countDown事件控制线程的执行,只有线程的await方法能控制等待的线程执行。CyclicBarrier强调的是n个线程,大家相互等待,只要有一个没完成,所有人都得等着。
当线程到达关卡点,调用await,await会被阻塞,直到所有线程都到达关卡点。如果所偶线程都到达了关卡点,关卡被成功突破,这样所有的被阻塞的线程会被释放,此时关卡被重置,以备下一次使用。如果调用await超时,或者阻塞中的线程被中断,那么关卡就被置为失败。所有的对await未完成的调用,都会通过抛出BrokenBarrierException而终止。
10个人去春游,规定达到一个地点后才能继续前行:
/**
* @Title: CyclicBarrierWorker.java
* @Package synchronizer.barrier
* @Description: (用一句话描述该文件做什么)
* @Author 落叶飞翔的蜗牛
* @Date 2017年12月16日 下午6:19:50
* @Version V1.0
*/
package synchronizer.barrier;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @ClassName: CyclicBarrierWorker
* @Description: (这里用一句话描述这个类的作用)
* @Author 落叶飞翔的蜗牛
* @Date 2017年12月16日 下午6:19:50
*/
public class CyclicBarrierWorker implements Runnable {
private int id;
private CyclicBarrier barrier;
public CyclicBarrierWorker(int id, final CyclicBarrier barrier) {
this.id = id;
this.barrier = barrier;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
System.out.println("第" + id + "个人到达,并等待");
barrier.await(); // 大家等待最后一个线程到达
} catch (InterruptedException | BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* @Title: CyclicBarrierWorkerTest.java
* @Package synchronizer.barrier
* @Description: (测试类)
* @Author 落叶飞翔的蜗牛
* @Date 2017年12月16日 下午6:27:04
* @Version V1.0
*/
package synchronizer.barrier;
import java.util.concurrent.CyclicBarrier;
import org.junit.Test;
/**
* @ClassName: CyclicBarrierWorkerTest
* @Description: (测试类)
* @Author 落叶飞翔的蜗牛
* @Date 2017年12月16日 下午6:27:04
*/
public class CyclicBarrierWorkerTest {
@Test
public void test() {
int num = 10;
CyclicBarrier barrier = new CyclicBarrier(num, new Runnable() {
@Override
public void run() {
//关卡通过后执行以下
System.out.println("一起走啊!");
}
});
for (int i = 1; i <= num; i++) {
new Thread(new CyclicBarrierWorker(i, barrier)).start();
}
}
}