1. 栅栏
栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生,CyclicBarrier可以使一定数量的参与方反复的在栅栏处汇集。
当线程到达栅栏处,将调用CyclicBarrier.await方法,这个方法一直阻塞直到所有线程到达栅栏处,当所有线程到达了栅栏处,栅栏将打开,所有线程被释放,而栅栏被重置以便下次使用。
栅栏与闭锁区别
所有线程必须全部到达栅栏处,才能继续执行;闭锁结束前,不允许线程执行,结束时,允许所有线程执行
栅栏等待线程;闭锁等待事件
/**
* 栅栏理解代码示例
*/
public class CyclicBarrierWorker {
class Worker implements Runnable {
private int id;
private CyclicBarrier cyclicBarrier;
public Worker(int id, CyclicBarrier cyclicBarrier) {
this.id = id;
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
System.out.println("线程" + id + "已到达栅栏处");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(10, () -> {
System.out.println("所有线程已到达栅栏处, 2333");
});
for (int i = 0; i < 10; i++) {
new Thread(new CyclicBarrierWorker().new Worker(i, cyclicBarrier)).start();
}
}
}
运行结果
线程0已到达栅栏处
线程4已到达栅栏处
线程3已到达栅栏处
线程2已到达栅栏处
线程1已到达栅栏处
线程6已到达栅栏处
线程8已到达栅栏处
线程7已到达栅栏处
线程5已到达栅栏处
线程9已到达栅栏处
所有线程已到达栅栏处, 2333
2. 信号量
计数信号量(Counting Semaphore)用来控制同时访问某个资源的操作数量。信号量还可以实现资源池,为容器设置边界。
Semaphore管理着一组虚拟的许可,许可的初始数量可以通过构造函数来指定。acquire获取许可,没有许可则阻塞直到有许可,release将返回一个许可给信号量。
/**
* 使用Semaphore为容器设置边界
*/
public class BoundedHashSet<T> {
private final Set<T> set;
private final Semaphore semaphore;
public BoundedHashSet(int bound) {
set = Collections.synchronizedSet(new HashSet<>());
semaphore = new Semaphore(bound);
}
public boolean add(T e) throws InterruptedException {
semaphore.acquire();
boolean wasAdded = false;
try {
wasAdded = set.add(e);
return wasAdded;
} finally {
if (!wasAdded) {
semaphore.release();
}
}
}
public boolean remove(Object o) {
boolean wasRemoved = set.remove(o);
if (wasRemoved) {
semaphore.release();
}
return wasRemoved;
}
}
3. 闭锁
闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。
闭锁相当于一扇门,在闭锁结束之前,这扇门一直关闭,没有任何线程能够通过;当闭锁结束时,这扇门打开,所有的线程均可通过
闭锁状态包括一个计数器,该计数器初始化为一个正数,表示需要等待的事件数量。countDown方法递减计数器,表示一个事件已经发生,如果计数器非零,则await方法等待计数器为零,或等待中的线程中断或者等待超时。
闭锁是一次性对象,一旦进入终止状态,就不能被重置。
import java.util.concurrent.CountDownLatch;
/**
* 闭锁实现统计多个线程执行时间
*/
public class TestHarness {
public long timeTasks(int nThreads, final Runnable task) throws InterruptedException {
CountDownLatch startGate = new CountDownLatch(1);
CountDownLatch endGate = new CountDownLatch(nThreads);
for (int i = 0 ; i < nThreads; i++) {
new Thread(() -> {
try {
startGate.await();
try {
task.run();
} finally {
endGate.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
long startTime = System.nanoTime();
startGate.countDown();
endGate.await();
long endTime = System.nanoTime();
return endTime - startTime;
}
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
for (int i = 0; i < 10000; i++) {
}
};
long time = new TestHarness().timeTasks(4, runnable);
System.out.println(time);
}
}
4. JAVA程序启动至少会启动几个线程
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println(threadInfo.getThreadId() + ":" + threadInfo.getThreadName());
}
JDK1.8 执行结果:
6:Monitor Ctrl-Break
5:Attach Listener 接收外部JVM命令
4:Signal Dispatcher 分发到不同的模块进行处理外部JVM命令,并且返回处理结结果
3:Finalizer 调用对象的finalize方法的线程,即垃圾回收的线程
2:Reference Handler 清除reference的线程
1:main 主线程
查看以上线程所属线程组及优先级
public class Test {
public static void main(String[] args) {
Thread threadOne = new Thread(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threadOne.setName("threadOne");
threadOne.start();
ThreadGroup threadGroup = threadOne.getThreadGroup();
System.out.println(threadGroup);
ThreadGroup systemThreadGroup = threadGroup.getParent();
System.out.println(systemThreadGroup);
systemThreadGroup.list();
}
}
执行结果:
java.lang.ThreadGroup[name=main,maxpri=10]
java.lang.ThreadGroup[name=system,maxpri=10]
java.lang.ThreadGroup[name=system,maxpri=10]
Thread[Reference Handler,10,system]
Thread[Finalizer,8,system]
Thread[Signal Dispatcher,9,system]
Thread[Attach Listener,5,system]
java.lang.ThreadGroup[name=main,maxpri=10]
Thread[main,5,main]
Thread[Monitor Ctrl-Break,5,main]
Thread[threadOne,5,main]
由此看来,当一个应用程序启动时,至少创建6个线程,两个线程组(system和main线程组),其中system线程组是main线程组的父线程组,main与Monitor Ctrl-Break处于main线程组,其他线程处于system线程组。