利用线程隔离简化并发控制

在Java中,为了限制多个不同线程对共享变量或者状态的访问,利用语言提供的同步或者加锁机制是最简单有效的办法。通过加锁或者同步,我们可以控制同一时间只有一个线程能够访问共享变量或者转台,从而保证变量或者状态的在多个线程之间的一致性和完整性。加锁或者同步的方式对于所有需要限制线程对其进行访问的变量或者状态来说都是有效的,但是对于有些场景来说并不是最好的。也就是说,在某些场景下,通过加锁或者同步确实可以保证程序在多线程环境下的正确性,但是也对程序的性能造成了很大的伤害。
下面的代码给出了一个例子。我们定义了一个Cooker类,这个类有一个cook方法,在cook方法中会根据其menu属性执行一些操作。menu中可能包含的信息包括要做什么菜、配料是什么、要不要放辣椒等等。

public class Cooker {
private Menu menu = null;

public void setMenu(Menu menu) {
this.menu = menu;
}

public void cook() {
Menu menu = menu;
//cook according to the menu
}
}


对于这个例子来说,每个线程在使用的都可以创建新的Cooker和Menu类,从而避免任何的并发问题。但是,如果Cooker的创建过程非常耗时且需要占用很多系统资源,而其执行过程除了需要获取menu属性中的信息之外,再没有其他共享的属性或者状态时,每个线程都创建一个Cooker对象就不是一个非常明智的选择了。
这种情况下,我们就需要考虑Cooker对象中menu属性的并发问题了。如果不对多个线程访问menu属性的行为进行限制,就可能会引起很多的问题。因为不同的线程可以自由地设置menu属性,那么某个线程在执行cook方法的时候使用的menu对象可能就不是自己在开始的时候设置的那个menu对象,导致程序的运行结果非常奇怪。
我们可以通过加锁或者同步来解决这个问题,

class Kitchen {
private Cooker cooker = new Cooker();

public void doCook() {
Menu menu = new Menu();
synchronized (cooker) {
cooker.setMenu(menu);
cooker.cook();
}
}
}


我们可以通过上面的形式,将对cooker对象的访问放在同步块中,来限制每次只有一个线程能够设置Cooker对象中的menu属性,并执行cook方法。除此之外,将doCook方法声明为同步方法可以起到同样的效果。另外还可以通过显示加锁的方式来替换同步快。
通过上面的处理,程序又可以正常运行了,但是程序的并发性却大大降低了。那么有没有更好的方式,即能够保证程序的正确性,又能够提高程序的并发性呢?答案就是使用线程隔离技术。先来看下事例代码,

public class Cooker {
Map<Thread, Menu> menuPerThreadMap = new ConcurrentHashMap<Thread, Menu>();

public void setMenu(Menu menu) {
Thread currentThread = getCurrentThread();
menuPerThreadMap.put(currentThread, menu);
}

public void cook() {
Thread currentThread = getCurrentThread();
Menu menu = menuPerThreadMap.get(currentThread);

if (menu != null) {
//cook according to the menu
}
}

private Thread getCurrentThread() {
return Thread.currentThread();
}
}


上面的代码中,在Cooker中定义了一个menuPerThreadMap属性,这个属性的作用是维护一个当前线程到Menu对象的映射关系。在setMenu方法中,我们当前线程和与之对应的Menu对象放到这个map中,在cook方法中,则通过当前线程从这个map中取出对应的Menu对象,然后执行后面的动作。通过这种方式,我们将不同线程所设置的Menu对象隔离开来,使得他们相互之间不受影响,从而保证了程序的正确性。而又由于消除了使用加锁或者同步的必要性,提高了程序的并发能力。
上面的这个方法基本上阐述了线程隔离的本质,一般来说,如果一个类中某个属性会因不同线程的执行上下文不同而被赋予不同的值,从而影响类的行为,但是这个属性的影响范围仅限于当前线程的话(每个线程都会根据自己的情况为来设置这个属性),线程隔离技术是一个不错的选择。
当然,上面给出的实现非常的粗糙,一个显而意见的问题就是没有对menuPerThreadMap中的元素进行垃圾回收,因此menuPerThreadMap会随着程序的执行不断增长,最终会引起java虚拟机的内存溢出。当然,这里是有办法来进行处理的,但是却没有这个必要。因为Java已经为我们提供了相关的类和方法来有效利用线程隔离技术,这个类就是ThreadLocal。
ThreadLocal类的基本思想与本文所说的线程隔离技术完全一只,但是其实现要更加复杂和可靠,不仅考虑到了通用性,同时也很好地解决了垃圾回收问题。相关文档和实现可以参考Java的API以及源代码,这里就不再详细说明了(整个实现也比较简单明了)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值