你真的会写单例吗
/**
* 参见: https://mp.weixin.qq.com/s/2UYXNzgTCEZdEfuGIbcczA
*
* @author: zhangjk
* @date: 2018年3月7日 下午2:05:53
*/
public class Singleton {
//一、双检查锁(DCL,即 double-checked locking)
//描述:这种方式称为双重检查锁(Double-Check Locking),需要注意的是,如果使用双重检查锁定来实现懒汉式单例类
// private Singleton() {}
// private volatile static Singleton instance = null; //单例对象;volatile 修饰符阻止了变量访问前后的指令重排序,保证了指令执行顺序。
// public static Singleton getInstance() {
// if (instance == null) {
// synchronized (Singleton.class) {
// if(instance == null) {
// instance = new Singleton();
// }
// }
// }
// return instance;
// }
// 二、用静态内部类实现单例模式:
// 1.从外部无法访问静态内部类LazyHolder,只有当调用Singleton.getInstance方法的时候,才能得到单例对象INSTANCE。
// 2.INSTANCE对象初始化的时机并不是在单例类Singleton被加载的时候,而是在调用getInstance方法,使得静态内部类LazyHolder被加载的时候。
//描述:饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制繁琐,而且性能受影响。
// private static class LazyHolder {
// private static final Singleton INSTANCE = new Singleton();
// }
//
// private Singleton() {}
// public static Singleton getInstance() {
// return LazyHolder.INSTANCE;
// }
//三、饿汉式
//优点:没有加锁,执行效率会提高。
//缺点:类加载时就初始化,浪费内存。
private static Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getIntance() {
return instance;
}
}
并发工具类
CountDownLatch和CyclicBarrier是jdk concurrent包下非常有用的两个并发工具类,它们提供了一种控制并发流程的手段。
CountDownLatch 允许一个或多个线程等待其他线程完成操作。
CyclicBarrier 让一组线程到达一个同步点后再一起继续运行,在其中任意一个线程未达到同步点,其他到达的线程均会被阻塞。
Semaphore(信号量)
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。
一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。
每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可。
/**
* 控制并发线程数
*/
public class SemaphoreDemo {
/**
*执行任务类,获取信号量
*/
class SemaphoreRunnable implements Runnable{
private Semaphore semaphore;
private int user;
public SemaphoreRunnable(Semaphore semaphore, int user) {
this.semaphore = semaphore;
this.user = user;
}
@Override
public void run() {
try {
//获取信号量许可
semaphore.acquire();
System.out.println("用户【" + user + "】进入窗口,准备买票...");
Thread.sleep((long)(Math.random()*10000));
System.out.println("用户【" + user + "】买票完成,即将离开...");
Thread.sleep((long)(Math.random()*10000));
System.out.println("用户【" + user + "】离开售票窗口...");
//释放信号量许可
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void execute(){
//定义窗口个数
final Semaphore semaphore = new Semaphore(3);
//线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
threadPool.execute(new SemaphoreRunnable(semaphore,i + 1));
}
threadPool.shutdown();
}
public static void main(String[] args) {
SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
semaphoreDemo.execute();
}
}
CountDownLatch
源码注释:
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
(一个或者多个线程,等待其他多个线程完成某件事情之后才能执行)
- CountDownLatch声明:
- CountDownLatch的构造函数接受int型参数作为它的计数器,如果想等待N个点完成,就传入N;
- 调用CountDownLatch的countDown方法时,N会减1,CountDownLatch的await方法会阻塞主线程直到N减少到0。
网上有个经典例子,在此抄录一下帮助理解记忆。
/**
* F1赛车每次进站后,车队技师都需要在尽可能短的时间内对赛车做三个工作:加注燃油、更换轮胎、更换刹车片。
* 当然,这三项工作都是同时进行的。只有当这三项工作完成,赛车才能驶出维修站。
*/
public class CountdownlatchDemo {
static class Mechanician implements Runnable{
String work;
CountDownLatch cDownLatch ;
public Mechanician(String work,CountDownLatch cDownLatch) {
this.work = work;
this.cDownLatch = cDownLatch;
}
@Override
public void run() {
try {
int random=new Random().nextInt(7);
TimeUnit.SECONDS.sleep(random);
System.out.println(Thread.currentThread().getName()+"--- "+work+" 完成,此组耗时:"+random+"秒");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
/*当前技师的任务完成,cDownLatch计算器减1
*通常countDown放在finally里中使用*/
cDownLatch.countDown();
}
}
}
public static void main(String[] args) {
CountDownLatch cDownLatch=new CountDownLatch(3);//初始计数器值为3,对应3个维修组
/*1、赛车进站*/
System.out.println("F1赛车进站,时间:"+Calendar.getInstance().get(Calendar.SECOND));
List<Mechanician> mechanician_team=new ArrayList<Mechanician>();
mechanician_team.add(new Mechanician("加注燃油", cDownLatch));
mechanician_team.add(new Mechanician("更换轮胎", cDownLatch));
mechanician_team.add(new Mechanician("更换刹车片", cDownLatch));
for(Mechanician mechanician : mechanician_team){
new Thread(mechanician).start();
}
/*3、等待技师完成三项工作。实际就是等待cDownLatch计数器变成0*/
try {
cDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
/*4、完成维护,出发*/
System.out.println("F1赛车维修完毕,出发!时间:"+Calendar.getInstance().get(Calendar.SECOND));
}
}
Countdownlatch 是一个倒计数器锁。调用CountDownLatch对象的await()方法使线程处于等待状态,调用countDown()方法的线程会将计数器减1,当计数到达0时,所有等待线程(可多个,但通常的应用场景中只有一个等待者)开始继续执行。
CyclicBarrier
源码注释:
A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.(多个线程互相等待,直到到达同一个同步点,再继续一起执行)
- CyclicBarrier(int parties) 默认构造方法,参数表示拦截的线程数量。
- CyclicBarrier(int parties, Runnable barrierAction)
由于线程之前的调度是由CPU决定的,所以默认的构造方法无法设置线程执行优先级,CyclicBarrier提供一个更高级的构造函数(int parties, Runnable barrierAction),用于在线程到达同步点时,优先执行线程barrierAction,这样可以更加方便的处理一些复杂的业务场景。- 创建CyclicBarrier后,每个线程调用await方法告诉CyclicBarrier自己已经到达同步点,然后当前线程被阻塞。
结语
在Java语言内部,java.lang.Runtime对象就是一个使用单例模式的例子。在每一个Java应用程序里面,都有唯一的一个Runtime对象,应用程序可以与其运行环境发生相互作用。