采访问:Java同步

时间:2016-07-29 14:25:06

标签: java synchronization thread-synchronization

我几个月前在通过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)上获取锁定,并以或多或少的精度显示当前时间。但是,我记得那位采访者提到了印刷中的不一致之处,但我无法回想起来。

提前致谢。

3 个答案:

答案 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中以处理数字,而不仅仅是外观和比较。

首先让我添加打印结果:

enter image description here

在左侧有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();
        }
    }
}