在java中,有一些线程安全操作的常识,在这里我进行总结一下:
1、java.util.concurrent.atomic包中包含了一些原子变量类,用于实现在数值和对象引用上的原子状态转换。通过用AtomicLong来代替long类型的计数器,能够确保所有对计数器状态的访问操作都是原子的。
@ThreadSafe public class Counting{ private final AtomicLong count = new AtomicLong(0); public long getCount(){ return count.get(); } //safe method public yourMathod(){ //do something count.incrementAndGet(); //do something } }
同时合理利用同步代码块的合理大小,需要在各种设计需求中间进行权衡,包括安全性、简单性和性能。注意这里不要和atomic进行混用,因为这样会带来操作和管理上的麻烦。
//@GuardedBy( lock )有以下几种使用形式: //1、@GuardedBy( "this" ) 受对象内部锁保护 //2、@GuardedBy( "fieldName" ) 受 与fieldName引用相关联的锁 保护。 //3、@GuardedBy( "ClassName.fieldName" ) 受 一个类的静态field的锁 保存。 //4、@GuardedBy( "methodName()" ) 锁对象是 methodName() 方法的返值,受这个锁保护。 //5、@GuardedBy( "ClassName.class" ) 受 ClassName类的直接锁对象保护。而不是这个类的某个实例的锁对象。 @GuardeBy("this") private long counts; public synchronized long getCounts(){return counts;} public void yourMethod() { synchronized(this){ ++counts; } }
2、关于可变对象的安全发布,在Java并发实战中,介绍了以下方式安全发布:
- 在静态初始化函数中初始化一个对象引用。
- 将对象的引用保存到volatile类型的域或者AtomicReferance对象中。
- 将对象的引用保存到某个正确构造对象的final类。
- 将对象的引用保存到一个由锁保护的域中。
- 通过将一个键或者值放入Hashtable、synchronizedMap或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程。
- 通过将某个元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或者synchronizedSet中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程。
- 通过将某个元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程。
@NotThreadSafe public class ListHelper<E>{ public List<E> list = Collections.synchronizedList(new ArrayList<E>()); public synchronized boolean putIfAbsent(E x){ boolean absent = !list.contains(x); if(absent){ list.add(x); } return absent; } }正确方式:
@NotThreadSafe public class ListHelper<E>{ public List<E> list = Collections.synchronizedList(new ArrayList<E>()); public boolean putIfAbsent(E x){ synchronized(list){ boolean absent = !list.contains(x); if(absent){ list.add(x); } return absent; } } }5、在使用Vector时也会出现由于方法不是原子操作,导致得到的数据异常,这里的解决办法是:
public void yourMethod(){ synchronized(vector){ //do someThing //vector.get(i); //vector.put(object); } }6、迭代器对容器的修改由于性能的原因是没有考虑并发的,如果数据过于太大,不希望在迭代期间对容器加锁,那么一种替代方法就是‘克隆’容器,并在副本上进行迭代,但是在克隆的期间,是会考虑加锁并发的。 7、与Hashtable和synchronizedMap相比,ConcurrenctHashMap有着更多的优势以及更少的劣势,因此在大多数并发情况下,用ConcurrentHashMap来代替同步Map能进一步提高代码的可伸缩性。只有当应用程序需要加锁Map以进行独占访问时,才放弃使用ConcurrentHashMap。 8.使用Semaphore为容器设置边界,也可以用BlockingQueue来保存池的资源,当资源没有时,会出现阻塞,直到有用资源的释放。
public class BoundedHashSet<T>{ private final Set<T> set; private final Semaphore sem; public BoundedHashSet(int bound){ this.set = Collections.synchronizedSet(new HashSet<T>()); sem = new Semaphore(bound); } public boolean add(T o) throws InterruptedException{ sem.acquire(); boolean wasAdded = false; try{ wasAdded = set.add(o); return wasAdded; } finally{ if(!wasAdded) sem.release(); } } public boolean remove(Object o){ boolean wasRemoved = set.remove(o); if(wasRemoved){ sem.release(); } return wasRemoved; } }9.通过CyclicBarrier协调细胞自动衍生系统中的计算,主要用于把一个问题分成几个子问题,并每个问题分配一个子线程,当所有工作线程都完成到达栅栏时,来判断是否需要进行下一次迭代。另外一种形式是Exchanger,他是两方(Two-party)栅栏,一般用于两个缓冲区进行任务判断交换。
public class CellularAutomata{ private final Board mainBoard; private final CyclicBarrier barrier; private final Worker[] workers; public CellularAutomata(Board board){ this.mainBoard = board; int count = Runtime.getRuntime().availableProcessors(); this.barrier = new CyclicBarrier(count,new Runable(){ public void run(){ mainBoard.commitNewValues(); } }); this.workers = new Worker[count]; for(int i=0;i<count;i++){ workers[i] = new Worker(mainBoard.getSubBoard(count,i)); } } private class Worker implements Runnable{ private final Board board; public Worker(Board board){this.board = board;} public void run(){ while(!board.hasConverged()){ for(int x=0;x < board.getMaxX();x++){ for(int y=0;y<board.getMaxY();y++) board.setNewValue(x,y,computeValue(x,y)); } try{ barrier.await(); } catch(InterruptedException ex){ return; } catch(BrokerBarrierException ex){ return; } } } } public void start(){ for(int i=0;i<workers.lengthl;i++) new Thread(workers[i]).start(); mainBoard.waitForConvergence(); } }