或者手写单例,通过静态内部类或者双检索;
双检索实现的单例,主要是因为该实现方式是基于懒汉式,而懒汉又有线程安全问题,比如以下场景:某次活动发放了数张邀请码,每个邀请码只能用一次,在竞态环境下如何保证该邀请码只能被使用一次
package com.git.toolbox.util;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Created by poan on 2017/11/03.
*/
class InviteCode {
/**
* 1 已经使用 0 未使用
*/
private int status;
public InviteCode(int status) {
this.status = status;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
}
public class ConcurrentTest {
private static ReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) {
InviteCode inviteCode = new InviteCode(0);
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
executorService.execute(() -> {
System.out.println(System.currentTimeMillis());
use(inviteCode);
}
);
}
if (executorService.isTerminated() || executorService.isShutdown()) {
System.exit(0);
}
}
public static void use(InviteCode inviteCode) {
// 1 假设有A、B、C三个线程同时执行到这一步,三者都会判断此时邀请码未使用过
if (inviteCode.getStatus() == 0) {
try {
// 2 A、B、C只能有一个来使用邀请码的状态,所以需要加锁,保重其余两者不能修改邀请码的状态
lock.writeLock().lock();
// 3 这里为什么还要判断邀请码的状态?因为该状态可能被A、B、C三者以外的线程先修改过了,而三者还不知情; 或者,ABC其中一个已经有人执行过4步骤,并释放了锁,其余两者并没有进入1的判断而是直接在等着锁的释放,所以需要再判断一次
if (inviteCode.getStatus() == 0) {
// 4 确保邀请码还没人用过,将其更新为使用
inviteCode.setStatus(1);
System.out.println("已激活!");
} else {
System.out.println("卡已经被使用!");
throw new RuntimeException("卡已经被激活");
}
} finally {
lock.writeLock().unlock();
}
}
}
}
上述的例子,实际上说的是双检索的运用;单例模式如果用双检索的话网上有很多,这里记一个实际运用好了;
上述例子的use
方法中,第3步的判断,原因基本就是所写的那些,由于竞态下,可能AB线程停在了2步骤,此时他们认为资源还是可用的;所以当C释放了锁之后,AB线程往下执行时还是要判断一次资源的可用状态;不然就会出现并发问题;
另外,这里使用的是读写锁,读操作不会锁资源,只有写的时候会独占;并没有使用大多数教程写的synchronize轻量同步锁,具体比较另一篇再总结吧。
你看那个人好像一条狗啊