我几个月前在通过Skype面试德国公司时遇到过这个问题。给出以下代码:
private static DateFormat DATE_FORMAT = new SimpleDateFormat();
public void doSomething() {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (DATE_FORMAT) {
System.out.println(DATE_FORMAT.format(Calendar.getInstance().getTime()));
}
}
}).start();
}
}
说明是否存在任何潜在的同步问题以及原因。
我的直觉告诉我,不应该有任何东西。我们正在创建100个线程,每个线程都会在同一个对象(DATE_FORMAT)上获取锁定,并以或多或少的精度显示当前时间。但是,我记得那位采访者提到了印刷中的不一致之处,但我无法回想起来。
提前致谢。
答案 0 :(得分:1)
我没有看到问题,因为有一个关键部分由单个监视器(DATE_FORMAT
)控制,并且不存在其他锁定,因此不存在死锁的风险。
我唯一能想到的是DATE_FORMAT字段不是最终的,因此其他代码可能会改变引用,但这仍然不会导致问题,因为主要用途是你不运行{{1并发format
的同一实例。
答案 1 :(得分:1)
根据给定的代码;
来自DATE_FORMAT实例的调用格式方法正在修改DATE_FORMAT实例中的日历对象,因此可能1个线程可能会在其他线程打印之前修改日历(其他线程是修改日历对象但未打印的线程)但)。
这是SimpleDateFormat.class中的引用
// Called from Format after creating a FieldDelegate
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date); // modifies the calender's instance
好的,但DATE_FORMAT上的锁定应该阻止任何其他线程 修改DATE_FORMAT中的日历没有? - @Santi
它不会阻止直接修改日历,但是它阻止访问DATE_FORMAT的实例,如果2个线程尝试使用相同的参数同时执行synchronized块(这是DATE_FORMAT的实例),则1个线程应该等待另一个执行该synchronized块。多数民众同步的工作原理。
正如我在评论中承诺的那样,我做了模拟以证明我的答案。
private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static ArrayList<String> listDate = new ArrayList<>();
public static void doSomething()
{
for (int i = 0; i < 100; i++) {
final long msCurrentDate = i*100;
new Thread(new Runnable() {
public void run() {
synchronized (DATE_FORMAT) {
listDate.add(DATE_FORMAT.format(new Date(msCurrentDate)));
//System.out.println(DATE_FORMAT.format(Calendar.getInstance().getTime()));
}
}
}).start();
}
Runtime.getRuntime().addShutdownHook(new Thread()
{
@Override
public void run()
{
int resultSize = listDate.size();
System.out.println("All elements' size :" + resultSize);
resultSize = listDate.stream().distinct().collect(Collectors.toList()).size();
System.out.println("Unique elements' size :" + resultSize);
}
});
}
我修改了给定的代码而没有改变它的目的。正如您所看到的,我使用固定(并且每个线程增加100毫秒)时间来比较同步和未同步版本的代码的结果。
我正在使用Dates打印Dates并将Dates添加到String的ArrayList中以处理数字,而不仅仅是外观和比较。
首先让我添加打印结果:
在左侧有2个多个日期打印,在右侧没有多个日期
当然前5个结果证明什么都没有,你必须检查它们中的每一个。 因此,我从列表
中删除相同的条目后,将结果添加到列表并打印结果以下是同步版本的结果:
//Output of all executions
//All elements' size :100
//Unique elements' size :100
以下是Not Synced版本的结果:
//Output of execution : 1
//All elements' size :100
//Unique elements' size :82
//Output of execution : 2
//All elements' size :100
//Unique elements' size :78
//Output of execution : 3
//All elements' size :100
//Unique elements' size :81
根据结果我们可以说日历在A,B,C之前被X线程改变...线程打印日期(或添加到列表)
您可以自己测试和查看,对于不同的结果,您需要JDK 8来使用流API,或者您可以使用任何其他代码。如果您有任何疑问,请告诉我们,以便我们进行辩论。
答案 2 :(得分:1)
您应该使用new SimpleDateFormat("HH:mm:ss.SSS")
作为格式化程序,通过小时和分钟获得所需的怪异需要很好的时间来达到分钟边界。
我充满信心地说,代码没有任何同步问题。通过同步块来解决各种调用的线程安全问题,所有的初始化都是按顺序进行的,没有特别的问题真的在我身上跳过。
可能有意义的是,Calendar.getInstance().getTime()
调用是在同步块之外进行/分配并在其中使用的。同步块不一定按照它们到达的顺序调用等待锁定的线程,导致输出可能无序交错,但当前代码不是这种情况。我可以建议的是,也许你的面试官提出了错误的代码,或者他们错了。
作为参考,以下代码将产生无序交错:
public class Test {
private static DateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss.SSS");
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Date time = Calendar.getInstance().getTime();
synchronized (DATE_FORMAT) {
System.out.println(DATE_FORMAT.format(time));
}
}
}).start();
}
}
}