总结了并发编程面试中可能遇到的大部分编程题,写出答案供大家参考,如果问题请指出,谢过。
使用CyclicBarrier 在多个关口处将多个线程执行结果汇总,
CountDownLatch 在各线程执行完毕后向总线程汇报结果。
CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。
CyclicBarrier : N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个线程”, 是它在等待,而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。
从api上理解就是CountdownLatch有主要配合使用两个方法countDown()和await(),countDown()是做事的线程用的方法,await()是等待事情完成的线程用个方法,这两种线程是可以分开的(下面例子:CountdownLatchTest2),当然也可以是同一组线程;CyclicBarrier只有一个方法await(),指的是做事线程必须大家同时等待,必须是同一组线程的工作。
CountdownLatch例子:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 各个线程执行完成后,主线程做总结性工作的例子
* @author xuexiaolei
* @version 2017年11月02日
*/
public class CountdownLatchTest2 {
private final static int THREAD_NUM = 10;
public static void main(String[] args) {
CountDownLatch lock = new CountDownLatch(THREAD_NUM);
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < THREAD_NUM; i++) {
exec.submit(new CountdownLatchTask(lock, "Thread-"+i));
}
try {
lock.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("大家都执行完成了,做总结性工作");
exec.shutdown();
}
static class CountdownLatchTask implements Runnable{
private final CountDownLatch lock;
private final String threadName;
CountdownLatchTask(CountDownLatch lock, String threadName) {
this.lock = lock;
this.threadName = threadName;
}
@Override public void run() {
System.out.println(threadName + " 执行完成");
lock.countDown();
}
}
}
CyclicBarrier例子:
import java.util.concurrent.*;
/**
*
* @author xuexiaolei
* @version 2017年11月02日
*/
public class CyclicBarrierTest {
private final static int THREAD_NUM = 10;
public static void main(String[] args) {
CyclicBarrier lock = new CyclicBarrier(THREAD_NUM, new Runnable() {
@Override public void run() {
System.out.println("这阶段大家都执行完成了,我总结一下,然后开始下一阶段");
}
});
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < THREAD_NUM; i++) {
exec.submit(new CountdownLatchTask(lock, "Task-"+i));
}
exec.shutdown();
}
static class CountdownLatchTask implements Runnable{
private final CyclicBarrier lock;
private final String threadName;
CountdownLatchTask(CyclicBarrier lock, String threadName) {
this.lock = lock;
this.threadName = threadName;
}
@Override public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(threadName + " 执行完成");
try {
lock.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
虽然Exectors可以生成一些很常用的线程池,但毕竟在什么情况下使用还是开发者最清楚的。在某些自己很清楚的使用场景下,java线程池还是推荐自己配置的。下面是java线程池的配置类的参数,我们逐一分析一下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
至于线程池应当配置多大的问题,一般有如下的经验设置:
1. 如果是CPU密集型应用,则线程池大小设置为N+1。
2. 如果是IO密集型应用,则线程池大小设置为2N+1。
用有界队列好还是无界队列好?这种问题的答案肯定是视情况而定:
1. 有界队列有助于避免资源耗尽的情况发生。但他带来了新的问题:当队列填满后,新的任务怎么办?所以有界队列适用于执行比较耗资源的任务,同时要设计好相应的饱和策略。
2. 无界队列和有界队列刚好相反,在资源无限的情况下可以一直接收新任务。适用于小任务,请求和处理速度相对持平的状况。
3. 其实还有一种同步移交的队列 SynchronousQueue ,这种队列不存储任务信息,直接将任务提交给线程池。可以理解为容量只有1的有界队列,在特殊场景下有特殊作用,同样得设计好相应的饱和策略。
用有界的BlockingQueue来实现,其实BlockingQueue已经将很多调用细节隐去了,实现很简单了。
/**
* 用阻塞队列快速实现生产者-消费者
* @author xuexiaolei
* @version 2017年11月01日
*/
public class ProduceAndConsumer {
public static void main(String[] args) {
final BlockingQueue list = new ArrayBlockingQueue(10);
Procude procude = new Procude(list);
Consumer consumer = new Consumer(list);
procude.start();
consumer.start();
}
static class Procude extends Thread{
private final BlockingQueue list;
Procude(BlockingQueue list) {
this.list = list;
}
@Override public void run() {
while(true){
try {
Integer take = list.take();
System.out.println("消费数据:" + take);
// Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Consumer extends Thread{
private final BlockingQueue list;
Consumer(BlockingQueue list) {
this.list = list;
}
@Override public void run() {
while (true){
try {
int i = new Random().nextInt(100);
list.put(i);
System.out.println("生产数据:" + i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
用CopyOnWriteArrayList、CopyOnWriteArraySet。
CopyOnWriteArrayList特性:
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
搜索引擎黑名单缓存实现:(自己复查了很多遍,如有问题,请指出)
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.*;
/**
* 做了一个搜索引擎,搜索引擎每次搜索前需要判断搜索关键词是否在黑名单里,黑名单每天更新一次。
* @author xuexiaolei
* @version 2017年11月03日
*/
public class SearchEngineBlackListCache {
private final Set blackList = new CopyOnWriteArraySet<>();//黑名单集合
private final Set todayBlackList = new ConcurrentSkipListSet<>();//当天添加的黑名单
/******内部类单例写法 start******/
private SearchEngineBlackListCache(){}
private static class Holder {
private static SearchEngineBlackListCache singleton = new SearchEngineBlackListCache();
}
public static SearchEngineBlackListCache getInstance(){
return Holder.singleton;
}
/******内部类单例写法 end******/
/**
* 获取黑名单列表
* @return
*/
public Set getBlackList() {
return Collections.unmodifiableSet(blackList);
}
/**
* 判断是否在黑名单内
* @param name
* @return
*/
public boolean isBlack(String name){
return blackList.contains(name);
}
/**
* 将今天的黑名单加入到黑名单内,外部系统可以定时每天执行这个方法
*/
public void mergeBlackList(){
synchronized (todayBlackList){
blackList.addAll(todayBlackList);
todayBlackList.clear();
}
}
/**
* 加入黑名单
* @param name
*/
public void addBlackList(String name){
synchronized (todayBlackList) {
todayBlackList.add(name);
}
}
/**
* 随机生成50个线程来测试
* @param args
*/
public static void main(String[] args) {
SearchEngineBlackListCache cache = SearchEngineBlackListCache.getInstance();
final int COUNT = 50;
CountDownLatch countDownLatch = new CountDownLatch(COUNT);
ExecutorService exec = Executors.newFixedThreadPool(COUNT);
for (int i = 0; i < COUNT; i++) {
exec.execute(new Runnable() {
@Override public void run() {
try {
countDownLatch.countDown();
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
cache.addBlackList(UUID.randomUUID().toString());//随机增加黑名单字符串
cache.mergeBlackList();
System.out.println(cache.getBlackList().size());
}
});
}
exec.shutdown();
}
}
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”这其实和乐观锁的冲突检查+数据更新的原理是一样的。
这里再强调一下,乐观锁是一种思想。CAS是这种思想的一种实现方式。
ABA问题:
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
解决方法:通过版本号(version)的方式来解决,每次比较要比较数据的值和版本号两项内容即可。
比如网上共享白板,共享文档等都适用。
读写锁逻辑:
1. 当读写锁是写加锁状态时, 在这个锁被解锁之前, 所有试图对这个锁加锁的线程都会被阻塞。
2. 当读写锁在读加锁状态时, 所有试图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行枷锁的线程将阻塞。
3. 当读写锁在读模式锁状态时, 如果有另外线程试图以写模式加锁, 读写锁通常会阻塞随后的读模式锁请求, 这样可以避免读模式锁长期占用, 而等待的写模式锁请求长期阻塞。
举例来说明锁的可重入性
public class UnReentrant{
Lock lock = new Lock();
public void outer(){
lock.lock();
inner();
lock.unlock();
}
public void inner(){
lock.lock();
//do something
lock.unlock();
}
}
outer中调用了inner,outer先锁住了lock,这样inner就不能再获取lock。其实调用outer的线程已经获取了lock锁,但是不能在inner中重复利用已经获取的锁资源,这种锁即称之为 不可重入可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码块。
synchronized、ReentrantLock都是可重入的锁,可重入锁相对来说简化了并发编程的开发。
状态标志:把简单地volatile变量作为状态标志,来达成线程之间通讯的目的,省去了用synchronized还要wait,notify或者interrupt的编码麻烦。
替换重量级锁:如果某个变量仅是单次读或者单次写操作,没有复合操作(i++,先检查后判断之类的)就可以用volatile替换synchronized。
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* 阻塞访问的线程,直到获取了访问令牌
* @author xuexiaolei
* @version 2017年11月15日
*/
public class FlowControl2 {
private final static int MAX_COUNT = 10;
private final Semaphore semaphore = new Semaphore(MAX_COUNT);
private final ExecutorService exec = Executors.newCachedThreadPool();
public void access(int i){
exec.submit(new Runnable() {
@Override public void run() {
semaphore.acquireUninterruptibly();
doSomething(i);
semaphore.release();
}
});
}
public void doSomething(int i){
try {
Thread.sleep(new Random().nextInt(100));
System.out.println(String.format("%s 通过线程:%s 访问成功",i,Thread.currentThread().getName()));
} catch (InterruptedException e) {
}
}
public static void main(String[] args) {
FlowControl2 web = new FlowControl2();
for (int i = 0; i < 2000; i++) {
web.access(i);
}
}
}
A. x=y;
B. x++;
C. ++x;
D. x=1;
答案:D
前三个都至少需要先读取,再操作,非原子操作。而D的话,直接赋值。
A:栈公有, 堆私有
B:栈公有,堆公有
C:栈私有, 堆公有
D:栈私有,堆私有
答案:C
int tally = 0;//glable
void ThreadProc()
{
for(int i = 1; i <= 50; i++)
tally += 1;
}
答案:50到100
tolly+=1,要分为三个指令(读tolly,tolly+1,写回tolly)
50的一种情况是:线程一读x,线程二也读x,线程一寄存器加一,线程二寄存器加一,放回x,线程二放加x,这种情况虽然二个线程都对x加1,但显然只加了一次。所以到最后只加50次。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
/**
*
* @author xuexiaolei
* @version 2017年11月06日
*/
public class Interview4_2 {
private static final int COUNT = 50;
private static final Object lock = new Object();
private static AtomicBoolean permit = new AtomicBoolean(true);//控制当前执行线程的权力
public static void main(String[] args) {
ExecutorService exec = Executors.newSingleThreadExecutor();
exec.execute(new Runnable() {
@Override public void run() {
for (int i = 0; i < COUNT; i++) {
synchronized (lock) {
while (!permit.get()) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("子线程循环十次");
permit.set(false);
lock.notifyAll();
}
}
System.out.println("子线程结束");
}
});
for (int i = 0; i < COUNT; i++) {
synchronized (lock) {
while (permit.get()) {
try {
System.out.println("主线程等待");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程循环一百次");
permit.set(true);
lock.notifyAll();
}
}
System.out.println("主线程结束");
exec.shutdown();
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*5.(迅雷笔试题):编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。
* @author xuexiaolei
* @version 2017年11月10日
*/
public class Interview5 {
private static final int COUNT = 10;
private static final Object lock = new Object();
private static volatile String permit = "A";//控制当前执行的线程标识
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(3);
exec.execute(new Task("A"));
exec.execute(new Task("B"));
exec.execute(new Task("C"));
exec.shutdown();
}
static class Task implements Runnable {
private final String name;
Task(String name) {
this.name = name;
}
@Override public void run() {
for (int i = 0; i < COUNT; i++) {
synchronized (lock){
while (!this.name.equals(permit)){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(this.name);
permit = nextPermit(permit);
lock.notifyAll();
}
}
}
private String nextPermit(String permit) {
if (permit.equals("A")) return "B";
if (permit.equals("B")) return "C";
if (permit.equals("C")) return "A";
throw new RuntimeException("hello");
}
}
}
A:1 2 3 4 1 2….
B:2 3 4 1 2 3….
C:3 4 1 2 3 4….
D:4 1 2 3 4 1….
请设计程序。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
/**
*
(Google面试题)有四个线程1、2、3、4。线程1的功能就是输出1,线程2的功能就是输出2,以此类推.........现在有四个文件ABCD。初始都为空。现要让四个文件呈如下格式:
A:1 2 3 4 1 2....
B:2 3 4 1 2 3....
C:3 4 1 2 3 4....
D:4 1 2 3 4 1....
请设计程序。
版本一:设计的四个线程,线程间完全独立。和轮询的思想十分相似,线程各自尝试去获取文件锁,然后再看是否能写入当前文件。
当前十分低效,但如果 写操作 消耗的时间越多,效率就越高
一次输出例子如下:
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1
2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1
3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3
4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3
* @author xuexiaolei
* @version 2017年11月14日
*/
public class Interview6 {
private static final AtomicInteger count = new AtomicInteger(0);
///模拟四个文件
private static final StringBuffer fileA = new StringBuffer();
private static final StringBuffer fileB = new StringBuffer();
private static final StringBuffer fileC = new StringBuffer();
private static final StringBuffer fileD = new StringBuffer();
//四个文件的锁
private static final ReentrantLock lockA = new ReentrantLock();
private static final ReentrantLock lockB = new ReentrantLock();
private static final ReentrantLock lockC = new ReentrantLock();
private static final ReentrantLock lockD = new ReentrantLock();
//四个文件的书写位置
private static final AtomicInteger numberA = new AtomicInteger(1);
private static final AtomicInteger numberB = new AtomicInteger(2);
private static final AtomicInteger numberC = new AtomicInteger(3);
private static final AtomicInteger numberD = new AtomicInteger(4);
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(4);
exec.execute(new writeTask(1));
exec.execute(new writeTask(2));
exec.execute(new writeTask(3));
exec.execute(new writeTask(4));
exec.shutdown();
Thread.sleep(5000);//等待线程池结束后输出文件内容
System.out.println(fileA);
System.out.println(fileB);
System.out.println(fileC);
System.out.println(fileD);
}
static class writeTask implements Runnable{
private final int wirteContent;//输出的内容,线程1的功能就是输出1,线程2的功能就是输出2
writeTask(int wirteContent) {
this.wirteContent = wirteContent;
}
@Override public void run() {
while (count.get() < 1000) {//1000为多个线程总共大概尝试的次数
//尝试写入A文件
try {
boolean a = lockA.tryLock(1, TimeUnit.MILLISECONDS);
if (a) {
if (numberA.get() % 4 == wirteContent%4) {
fileA.append(wirteContent + " ");
numberA.incrementAndGet();
}
lockA.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//尝试写入文件
try {
boolean a = lockB.tryLock(1, TimeUnit.MILLISECONDS);
if (a) {
if (numberB.get() % 4 == wirteContent%4) {
fileB.append(wirteContent + " ");
numberB.incrementAndGet();
}
lockB.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//尝试写入C文件
try {
boolean a = lockC.tryLock(1, TimeUnit.MILLISECONDS);
if (a) {
if (numberC.get() % 4 == wirteContent%4) {
fileC.append(wirteContent + " ");
numberC.incrementAndGet();
}
lockC.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//尝试写入D文件
try {
boolean a = lockD.tryLock(1, TimeUnit.MILLISECONDS);
if (a) {
if (numberD.get() % 4 == wirteContent%4) {
fileD.append(wirteContent + " ");
numberD.incrementAndGet();
}
lockD.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
count.incrementAndGet();
}
}
}
}
线程1: 1
线程1: 2
线程1: 3
线程1: 4
线程1: 5
线程2: 6
线程2: 7
线程2: 8
线程2: 9
线程2: 10
…
线程3: 71
线程3: 72
线程3: 73
线程3: 74
线程3: 75
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* 7.启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印16,17,18,19,20....以此类推, 直到打印到75. 程序的输出结果应该为:
线程1: 1
线程1: 2
线程1: 3
线程1: 4
线程1: 5
线程2: 6
线程2: 7
线程2: 8
线程2: 9
线程2: 10
...
线程3: 71
线程3: 72
线程3: 73
线程3: 74
线程3: 75
处理边界条件有点烦,其他还是不错的
* @author xuexiaolei
* @version 2017年11月14日
*/
public class Interview7 {
private static final Object lock = new Object();
private static final AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(3);
exec.execute(new Task("线程1", 0));
exec.execute(new Task("线程2", 1));
exec.execute(new Task("线程3", 2));
exec.shutdown();
}
static class Task implements Runnable {
private final String threadName;
private final int count;
Task(String threadName, int count) {
this.threadName = threadName;
this.count = count;
}
@Override public void run() {
do {
synchronized (lock) {
while ((counter.get()/5)%3 != count) {//counter每次输出完成肯定是5的倍数,除以5然后对3取余判断是否是当前线程来写
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 5; i++) {
System.out.println(threadName+":"+counter.incrementAndGet());
}
lock.notifyAll();
}
} while (counter.get() < 65);
}
}
}
一共五种写法:
/**
* 饿汉式写法
*
* 就是在第一次引用该类的时候就创建对象实例,而不管实际是否需要创建。
* 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
*
* @author xuexiaolei
* @version 2017年11月15日
*/
public class Singleton01 {
private static Singleton01 instance = new Singleton01();
private Singleton01(){}
public static Singleton01 getInstance() {
return instance;
}
}
/**
* 懒汉式写法
*
* 线程安全,但是getInstance方法效率十分低
*
* @author xuexiaolei
* @version 2017年11月15日
*/
public class Singleton02 {
private static Singleton02 instance = null;
private Singleton02(){}
public static synchronized Singleton02 getInstance(){
if (instance == null){
instance = new Singleton02();
}
return instance;
}
}
/**
* 双重检查锁写法
*
* getInstance()方法中,进行两次null检查。看似多此一举,但实际上却极大提升了并发度,进而提升了性能。为什么可以提高并发度呢?在单例中new的情况非常少,绝大多数都是可以并行的读操作。因此在加锁前多进行一次null检查就可以减少绝大多数的加锁操作,执行效率提高的目的也就达到了。
*
* 注意volatile的语义:可见性和禁止指令重排。可见性是jdk一直支持的,禁止指令重拍在jdk1.5之后才开始支持,所以此方法在jdk1.5以上才可运行。
*
* @author xuexiaolei
* @version 2017年11月15日
*/
public class Singleton03 {
private static volatile Singleton03 instance = null;
private Singleton03(){}
public static Singleton03 getInstance(){
if(instance == null){
synchronized (Singleton03.class){
if (instance == null){
instance = new Singleton03();
}
}
}
return instance;
}
}
/**
* 静态内部类法
*
* Singleton实例放到一个静态内部类中,这样就避免了静态实例在Singleton类加载的时候就创建对象,并且由于静态内部类只会被加载一次,所以这种写法也是线程安全的。
*
* @author xuexiaolei
* @version 2017年11月15日
*/
public class Singleton04 {
private static class Inner{
private static Singleton04 instance = new Singleton04();
}
private Singleton04(){}
public static Singleton04 getInstance(){
return Inner.instance;
}
}
/**
* 枚举写法
*
* 优雅。使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,Effective Java推荐尽可能地使用枚举来实现单例。
*
* @author xuexiaolei
* @version 2017年11月15日
*/
public enum Singleton05 {
INSTANCE;
/****任意方法直接用****/
public void method(){
}
}
代码在 https://gitee.com/xuea/alltest/tree/master/concurrency/src/main/java/com/leo/interview 均有体现。
欢迎各位大佬评论指出问题,谢过。