Java 线程安全多通模式
受a的启发,我尝试创建multiton模式的线程安全实现,它依赖于唯一的键并对它们执行锁定(我从JB Nizet的答案中得到了这个想法) 问题 我提供的实现是否可行 我对Multiton(或Singleton)是否通常是好的模式不感兴趣,这将导致一场讨论。我只是想要一个干净的和有效的实现 相反:Java 线程安全多通模式,java,design-patterns,multiton,Java,Design Patterns,Multiton,受a的启发,我尝试创建multiton模式的线程安全实现,它依赖于唯一的键并对它们执行锁定(我从JB Nizet的答案中得到了这个想法) 问题 我提供的实现是否可行 我对Multiton(或Singleton)是否通常是好的模式不感兴趣,这将导致一场讨论。我只是想要一个干净的和有效的实现 相反: 您必须知道在编译时要创建多少实例 专业人士 不锁定整个班级或整个地图。可以同时调用getInstance 通过key对象获取实例,而不仅仅是无界的int或String,因此您可以确保在方法调用后获
- 您必须知道在编译时要创建多少实例
- 不锁定整个班级或整个地图。可以同时调用
getInstance
- 通过key对象获取实例,而不仅仅是无界的
或int
,因此您可以确保在方法调用后获得非空实例String
- 线程安全(至少这是我的印象)
公共类Multiton
{
私有静态最终映射,Multiton>();
私有Multiton(){System.out.println(“已创建实例”);}
/*可以并发调用,因为它只在id上同步*/
publicstatic我不是Java程序员,但是:HashMap
对于并发访问是不安全的。我可以推荐ConcurrentHashMap
private static final ConcurrentHashMap<Object, Multiton> instances = new ConcurrentHashMap<Object, Multiton>();
public static <TYPE extends Object, KEY extends Enum<Keys> & MultitionKey<TYPE>> Multiton getInstance(KEY id)
{
Multiton result;
synchronized (id)
{
result = instances.get(id);
if(result == null)
{
result = new Multiton();
instances.put(id, result);
}
}
System.out.println("Retrieved instance.");
return result;
}
私有静态最终ConcurrentHashMap实例=新ConcurrentHashMap();
公共静态Multiton getInstance(密钥id)
{
多子结果;
已同步(id)
{
result=instances.get(id);
如果(结果==null)
{
结果=新的Multiton();
实例.put(id,result);
}
}
System.out.println(“检索到的实例”);
返回结果;
}
我认为不可行
在id
参数上进行同步充满了危险-如果他们将此enum
用于另一个同步机制会怎么样?当然HashMap
并非如评论所指出的那样是并发的
要演示-请尝试以下操作:
Runnable r = new Runnable() {
@Override
public void run() {
// Added to demonstrate the problem.
synchronized(Keys.KEY_1) {
getInstance(Keys.KEY_1);
}
}
};
这是一个使用原子而不是同步的实现,因此应该更有效。它比您的实现复杂得多,但在Miltiton
中处理所有边缘情况是复杂的
public class Multiton {
// The static instances.
private static final AtomicReferenceArray<Multiton> instances = new AtomicReferenceArray<>(1000);
// Ready for use - set to false while initialising.
private final AtomicBoolean ready = new AtomicBoolean();
// Everyone who is waiting for me to initialise.
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>();
// For logging (and a bit of linguistic fun).
private final int forInstance;
// We need a simple constructor.
private Multiton(int forInstance) {
this.forInstance = forInstance;
log(forInstance, "New");
}
// The expensive initialiser.
public void init() throws InterruptedException {
log(forInstance, "Init");
// ... presumably heavy stuff.
Thread.sleep(1000);
// We are now ready.
ready();
}
private void ready() {
log(forInstance, "Ready");
// I am now ready.
ready.getAndSet(true);
// Unpark everyone waiting for me.
for (Thread t : waiters) {
LockSupport.unpark(t);
}
}
// Get the instance for that one.
public static Multiton getInstance(int which) throws InterruptedException {
// One there already?
Multiton it = instances.get(which);
if (it == null) {
// Lazy make.
Multiton newIt = new Multiton(which);
// Successful put?
if (instances.compareAndSet(which, null, newIt)) {
// Yes!
it = newIt;
// Initialise it.
it.init();
} else {
// One appeared as if by magic (another thread got there first).
it = instances.get(which);
// Wait for it to finish initialisation.
// Put me in its queue of waiters.
it.waiters.add(Thread.currentThread());
log(which, "Parking");
while (!it.ready.get()) {
// Park me.
LockSupport.park();
}
// I'm not waiting any more.
it.waiters.remove(Thread.currentThread());
log(which, "Unparked");
}
}
return it;
}
// Some simple logging.
static void log(int which, String s) {
log(new Date(), "Thread " + Thread.currentThread().getId() + " for Multiton " + which + " " + s);
}
static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
// synchronized so I don't need to make the DateFormat ThreadLocal.
static synchronized void log(Date d, String s) {
System.out.println(dateFormat.format(d) + " " + s);
}
// The tester class.
static class MultitonTester implements Runnable {
int which;
private MultitonTester(int which) {
this.which = which;
}
@Override
public void run() {
try {
Multiton.log(which, "Waiting");
Multiton m = Multiton.getInstance(which);
Multiton.log(which, "Got");
} catch (InterruptedException ex) {
Multiton.log(which, "Interrupted");
}
}
}
public static void main(String[] args) throws InterruptedException {
int testers = 50;
int multitons = 50;
// Do a number of them. Makes n testers for each Multiton.
for (int i = 1; i < testers * multitons; i++) {
// Which one to create.
int which = i / testers;
//System.out.println("Requesting Multiton " + i);
new Thread(new MultitonTester(which+1)).start();
}
}
}
公共类Multiton{
//静态实例。
私有静态最终AtomicReferenceArray实例=新的AtomicReferenceArray(1000);
//准备使用-初始化时设置为false。
private final AtomicBoolean ready=新的AtomicBoolean();
//每一个等待我初始化的人。
private final Queue waiters=new ConcurrentLinkedQueue();
//记录日志(还有一点语言乐趣)。
例如,私人机构;
//我们需要一个简单的构造函数。
专用多音频(例如int){
this.forInstance=例如;
日志(例如,“新”);
}
//昂贵的初始化器。
public void init()引发InterruptedException{
日志(例如,“Init”);
//…大概是很重的东西。
睡眠(1000);
//我们现在准备好了。
ready();
}
私有void ready(){
日志(例如,“就绪”);
//我现在准备好了。
ready.getAndSet(true);
//让每个人都等我。
用于(线程t:服务员){
锁支持。unpark(t);
}
}
//获取该实例。
公共静态Multiton getInstance(int)引发InterruptedException{
//已经有一个了?
Multiton it=instances.get(哪个);
if(it==null){
//懒惰制造。
Multiton newIt=新的Multiton(哪个);
//成功的看跌期权?
if(instances.compareAndSet(which、null、newIt)){
//对!!
it=newIt;
//初始化它。
it.init();
}否则{
//其中一条似乎是魔术般出现的(另一条线先到了那里)。
it=instances.get(which);
//等待它完成初始化。
//把我放在服务员的队伍里。
添加(Thread.currentThread());
日志(即“停车场”);
而(!it.ready.get()){
//让我停下来。
锁支架。驻车();
}
//我不再等了。
it.waiters.remove(Thread.currentThread());
日志(即“未切割”);
}
}
归还它;
}
//一些简单的日志记录。
静态无效日志(整数,字符串s){
日志(新日期(),“线程”+线程.currentThread();
}
静态最终日期格式DateFormat=新的SimpleDataFormat(“yyyy-MM-dd HH:MM:ss.SSS”);
//已同步,因此不需要将DateFormat设置为本地。
静态同步作废日志(日期d,字符串s){
System.out.println(dateFormat.format(d)+“”+s);
}
//tester类。
静态类MultiContester实现可运行{
int其中;
专用多端口(int-which){
this.which=哪个;
}
@凌驾
公开募捐{
试一试{
Multiton.log(即“等待”);
Multiton m=Multiton.getInstance(which);
Multiton.log(其中“Got”);
}捕获(中断异常例外){
Multiton.log(其中“中断”);
}
}
}
公共静态void main(字符串[]args)引发InterruptedException{
int测试员=50;
int多声子=50;
//做一些测试。为每个Multiton制作n个测试仪。
对于(int i=1;i
它绝对不是线程安全的。下面是一个简单的例子,说明了许多可能出错的事情
线程A正试图在键id1
处放置。由于在id2
处放置,线程B正在调整存储桶表的大小。因为这些存储桶具有不同的同步监视器,所以它们将并行地退出比赛
Thread A Thread B
-------- --------
b = key.hash % map.buckets.size
copy map.buckets reference to local var
set map.buckets = new Bucket[newSize]
insert keys from old buckets into new buckets
insert into map.buckets[b]
在本例中,假设线程A
锯t
public class Multiton {
// The static instances.
private static final AtomicReferenceArray<Multiton> instances = new AtomicReferenceArray<>(1000);
// Ready for use - set to false while initialising.
private final AtomicBoolean ready = new AtomicBoolean();
// Everyone who is waiting for me to initialise.
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>();
// For logging (and a bit of linguistic fun).
private final int forInstance;
// We need a simple constructor.
private Multiton(int forInstance) {
this.forInstance = forInstance;
log(forInstance, "New");
}
// The expensive initialiser.
public void init() throws InterruptedException {
log(forInstance, "Init");
// ... presumably heavy stuff.
Thread.sleep(1000);
// We are now ready.
ready();
}
private void ready() {
log(forInstance, "Ready");
// I am now ready.
ready.getAndSet(true);
// Unpark everyone waiting for me.
for (Thread t : waiters) {
LockSupport.unpark(t);
}
}
// Get the instance for that one.
public static Multiton getInstance(int which) throws InterruptedException {
// One there already?
Multiton it = instances.get(which);
if (it == null) {
// Lazy make.
Multiton newIt = new Multiton(which);
// Successful put?
if (instances.compareAndSet(which, null, newIt)) {
// Yes!
it = newIt;
// Initialise it.
it.init();
} else {
// One appeared as if by magic (another thread got there first).
it = instances.get(which);
// Wait for it to finish initialisation.
// Put me in its queue of waiters.
it.waiters.add(Thread.currentThread());
log(which, "Parking");
while (!it.ready.get()) {
// Park me.
LockSupport.park();
}
// I'm not waiting any more.
it.waiters.remove(Thread.currentThread());
log(which, "Unparked");
}
}
return it;
}
// Some simple logging.
static void log(int which, String s) {
log(new Date(), "Thread " + Thread.currentThread().getId() + " for Multiton " + which + " " + s);
}
static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
// synchronized so I don't need to make the DateFormat ThreadLocal.
static synchronized void log(Date d, String s) {
System.out.println(dateFormat.format(d) + " " + s);
}
// The tester class.
static class MultitonTester implements Runnable {
int which;
private MultitonTester(int which) {
this.which = which;
}
@Override
public void run() {
try {
Multiton.log(which, "Waiting");
Multiton m = Multiton.getInstance(which);
Multiton.log(which, "Got");
} catch (InterruptedException ex) {
Multiton.log(which, "Interrupted");
}
}
}
public static void main(String[] args) throws InterruptedException {
int testers = 50;
int multitons = 50;
// Do a number of them. Makes n testers for each Multiton.
for (int i = 1; i < testers * multitons; i++) {
// Which one to create.
int which = i / testers;
//System.out.println("Requesting Multiton " + i);
new Thread(new MultitonTester(which+1)).start();
}
}
}
Thread A Thread B
-------- --------
b = key.hash % map.buckets.size
copy map.buckets reference to local var
set map.buckets = new Bucket[newSize]
insert keys from old buckets into new buckets
insert into map.buckets[b]