Month: March 2013

避免死锁的一招: 按同样的顺序加锁

当进程A正在把钱从甲账户转到乙账户时,进程B也正在把钱从乙账户到甲账户;转账时需要锁住账户,如果A锁住甲等待乙时,B已锁住乙并等待甲,那么双方就会陷入死锁。 要避免这种死锁,有一个办法是: 按同样的顺序加锁。 注意,读书不要读得太快。 听说“按同样顺序加锁”就能避免死锁,就总是先锁出账者,再锁入账者。。。 结果,还是死锁。 “按同样的顺序加锁”不是指“两个进程使用同样的操作顺序(比如先锁出账者,再锁入账者)”,而是要“对给定的两个资源,所有进程总是先锁资源1,再锁资源2”。 比如,在数据库中,可以总是先锁ID更大的那条记录;在JAVA代码中,可以总是先锁hashcode更大的那条记录。

JAVA代码片断:获取当前进程的PID

private static String getPid() throws IOException { Process p = Runtime.getRuntime().exec("/home/kent/opt/jdk1.6.0_41/bin/jps"); InputStream in = p.getInputStream(); List<String> jpsLines = IOUtils.readLines(in); IOUtils.closeQuietly(in); for (String line : jpsLines) { if (line.contains(HelloPoolSize.class.getSimpleName())) { return line.split("\\s")[0]; } } throw new IllegalStateException("拿不到pid"); }

例示FutureTask + Thread

package player.kent.chen.learn.future; import java.io.File; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import org.apache.commons.io.FileUtils; public class HelloFutureTask { public static void main(String[] args) { //一个待完成事项 Callable<String> callable = new Callable<String>() { public String call() throws Exception { return FileUtils.readFileToString(new File("/home/kent/temp/1.txt")); } }; //生成FutureTask对象 FutureTask<String> task = new FutureTask<String>(callable) { @Override //重载这个方法,可以在任务执行完时干点事 protected void done() { super.done(); System.out.println("The task is …

例示FutureTask + Thread Read More »

例示Executor + Callable + Future的使用

package player.kent.chen.learn.future; import java.io.File; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.apache.commons.io.FileUtils; public class HelloFuture { public static void main(String[] args) throws Throwable { //一个待完成事项 Callable<String> task = new Callable<String>() { public String call() throws Exception { return FileUtils.readFileToString(new File("/home/kent/temp/1.txt")); } }; //生成executor并异步执行 ExecutorService executor = Executors.newSingleThreadExecutor(); Future<String> future …

例示Executor + Callable + Future的使用 Read More »

ConcurrentHashMap的弱一致性

ConcurrentHashMap提供的迭代器不会抛出ConcurentModificationException,也就是说迭代器如果发现数据被修改了,它会尽量去访问到对Map已做的修改,但这个不能保证。 所以它读到的可能是过期的数据,也就是存在“弱一致性”问题。 对于一个被 并发访问的ConcurrentHashMap来说,size()和isEmpty()往往是不准确的。 但不要误会,以为ConcurrentHashMap的一致性很弱;除了迭代、size()、isEmpty()等操作有这个问题之外,像remove(), put()操作等都比HashMap提供了更高的一致性,因为这些操作都是原子操作。 这些操作中使用了粒度比较细的锁,不会像HashTable一样锁住整个容器,但也不至于像HashMap那样完全不加锁。

理解ConcurrentModificationException

当一个线程对类集进行迭代时,如果另一个线程修改了类集,迭代线程会发现这个修改,然后就会抛出ConcurrentModificationException . 不过即使出现了上面所说的并发读写,java类集也不能100%保证会抛出ConcurrentModificationException.  因为迭代线程在执行“判断类集是否修改 + 若有则抛出异常”这两步的组合时并没有加锁。 很有可能在 “发现类集没有修改后”, 另一个线程把类集修改了。 另一个要注意的是很多非迭代操作都可能引发ConcurrentModificationException, 因为这些操作隐式地调用了迭代。 比如 类集的hashCode(),equals(), containsAll()等,就连toString()都会,所以,在日志里直接打印类集时,要小心。

ReadWriteLock代码示例

ReadWriteLock的契约是:    1. 读、写都要先获得相应的锁    2. 如果在共享数据上已经加了读锁,则其他线程可以继续加读锁,再不能加写锁;也就是说,你读时别人也可以读,但不能写    3. 如果已经加了写锁,则其他线程不能加任何锁;也就是说,你在写时别人不能写也不能读 可见ReadWriteLock适用于读多写少的情形。 下面的代码例子是从《JAVA并发编程实践》中抄来的。注意我们一般用可重入的ReadWriteLock,即ReentrantReadWriteLock

例示CountDownLatch的使用

CountDownLatch可用于一个线程等待另外一些线程做了一些事后再做自己的事。做事的线程做完事后报一下数:将一个初数值减1,等这个数值变成0时,等待的线程就可以做自己的事。 用join()也能完成相同的目标。但join()意味着必须等待做事线程终止自己才能动手,CountDownLatch没有这个限制,做事线程再报完数后还可以继续做自己的事。 执行结果: 我是乞丐,要钱吃饭 walker0 已经动身了 walker1 已经动身了 walker2 已经动身了 我是行人walker0  我走到乞丐跟前花了3秒钟,然后给了乞丐一块钱 我是行人walker1  我走到乞丐跟前花了3秒钟,然后给了乞丐一块钱 我是行人walker2  我走到乞丐跟前花了3秒钟,然后给了乞丐一块钱 我是乞丐,钱要够了,现在去买吃的 我是行人walker0  给完钱后,我在无边的人海中继续前行直到永远。。。 我是行人walker1  给完钱后,我在无边的人海中继续前行直到永远。。。 我是行人walker2  给完钱后,我在无边的人海中继续前行直到永远。。。 同时各行人线程仍处于alive状态。