一段java并发同步示例代码的疑惑
import java.util.ArrayList;
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;
public class CachedThreadPool {
private static int id = 0;
public static void main(String[] args) {
new CachedThreadPool().fun();
}
private void fun() {
ExecutorService exe = Executors.newCachedThreadPool();
ArrayList<Future<String>> list = new ArrayList<Future<String>>();
for (int i=0;i<3;i++) {
list.add(exe.submit(new TaskCall()));
}
for (Future<String> fs : list) {
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
exe.shutdown();
}
private synchronized String getId() {
return ++id + "";
}
class TaskCall implements Callable<String> {
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return getId();
}
}
}
这段代码并没有什么问题,但是为何把getId()函数放到TaskCall内部,却会得到同步失败的输出(2 2 3或者3 3 3等),这是为什么呢?
hander
9 years, 6 months ago
Answers
按你的测试代码,楼上回答能解释。
然而:
测试代码有错误,以下是正确的测试代码:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
public class CachedThreadPool {
private static int id = 0;
private static List<Integer> results = Collections.synchronizedList(new ArrayList<>());
public static void main(String[] args) throws Exception {
new CachedThreadPool().fun();
}
private void fun() throws Exception {
ExecutorService exe = Executors.newCachedThreadPool();
List<Future<String>> list = new ArrayList<>();
for (int i=0;i<100;i++) {
list.add(exe.submit(new TaskCall()));
}
exe.shutdown();
for (Future<String> fs : list) {
fs.get();
}
// 断言
for (int i = 1; i < results.size(); i++) {
if (results.get(i) != results.get(i-1) + 1) {
throw new IllegalStateException();
}
}
System.out.println("\n" + results);
}
private synchronized String getId() {
++id;
System.out.print(id + ", ");
results.add(id);
return id + "";
}
class TaskCall implements Callable<String> {
@Override
public String call() throws Exception {
return getId();
}
}
}
然后是分析:
先翻译几处代码,以便理解:
1.
private synchronized String getId() {
return ++id + "";
}
等价于
private String getId() {
synchronized(this) {
return ++id + "";
}
}
2.
// TaskCall
return getId();
等价于
// TaskCall
return CachedThreadPool.this.getId();
可以看到同步范围仅限于this,也就是CachedThreadPool的实例,只有一个。但是static字段是类的字段,不是实例的字段,因此不在加锁范围!然而,同步会刷新代码块内所有用到的变量,不论static与否。而唯一实例使++代码块被单线程独占。两者结合,意外地做到了并发安全。
还可以试验一下,
synchronized
改成
synchronized(CachedThreadPool.class) {...}
,并把main标为synchronized,会导致死锁。
synchronized的知识:指定了一个同步范围,进出范围时会刷新相关变量,阻止其他线程进入该范围。synchronized method的范围是this,synchronized static method的范围是class。
补充:如果同一个类有的方法写了synchronized,有的方法没写,也达不到同步。
求女王包养
answered 9 years, 6 months ago