Pertanyaan Wawancara T: Sinkronisasi Java


Saya mengalami pertanyaan ini beberapa bulan yang lalu saat wawancara melalui Skype untuk perusahaan Jerman. Diberikan kode berikut:

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();
    }
}

Nyatakan jika mungkin ada masalah sinkronisasi potensial dan mengapa.

Intuisiku memberitahuku bahwa seharusnya tidak ada. Kami membuat 100 Thread, masing-masing akan menanyakan kunci pada objek yang sama (DATE_FORMAT) dan menampilkan waktu saat ini dengan akurasi yang lebih atau kurang. Namun, saya ingat bahwa pewawancara menyebutkan sesuatu tentang inkonsistensi dalam pencetakan tetapi saya tidak dapat mengingatnya dengan benar.

Terima kasih sebelumnya.


4
2017-07-29 14:25


asal


Jawaban:


Saya tidak melihat masalah karena ada bagian penting yang dikendalikan oleh monitor tunggal (DATE_FORMAT) dan tidak ada kunci lain yang hadir, jadi tidak ada risiko kebuntuan.

Satu-satunya hal yang dapat saya pikirkan adalah bidang DATE_FORMAT tidak bersifat final, sehingga berpotensi kode lain dapat mengubah rujukan, tetapi itu masih tidak akan menimbulkan masalah karena penggunaan utama ini adalah bahwa Anda tidak menjalankan format pada sama contoh SimpleDateFormat secara bersamaan.


1
2017-07-29 14:42



Anda mungkin harus menggunakan new SimpleDateFormat("HH:mm:ss.SSS") sebagai formatter Anda, mendapatkan keanehan yang diinginkan dengan jam dan menit membutuhkan waktu yang tepat untuk mencapai batas menit.

Saya akan mengatakan dengan yakin bahwa tidak ada masalah sinkronisasi dengan kode apa adanya. Setiap kekhawatiran dengan keamanan benang dari berbagai panggilan ditunda oleh blok sinkronisasi, semua inisialisasi tampak dalam urutan, tidak ada getchas benar-benar melompat keluar pada saya.

Apa yang mungkin masuk akal adalah jika Calendar.getInstance().getTime() panggilan dibuat / ditugaskan di luar blok sinkronisasi dan digunakan di dalamnya. Blok sinkronisasi tidak akan selalu memanggil utas yang menunggu kunci dalam urutan kedatangannya, yang mengarah ke output antar-output yang mungkin out-of-order, tetapi itu tidak terjadi dengan kode saat ini. Yang bisa saya sarankan adalah bahwa mungkin pewawancara Anda menyajikan kode yang salah atau mereka keliru.

Untuk referensi, kode berikut ini akan menghasilkan interleaving out-of-order:

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();
        }
    }
}

1
2017-07-29 15:21



Menurut kode yang diberikan;

Memanggil metode format dari instance DATE_FORMAT adalah memodifikasi objek kalender yang ada di instance DATE_FORMAT, sehingga kemungkinan untuk 1 utas dapat memodifikasi kalender sebelum pencetakan utas lainnya (untaian lainnya adalah utas yang memodifikasi objek kalender tetapi belum dicetak).

di sini adalah referensi di 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

Oke, namun kunci pada DATE_FORMAT harus mencegah Thread lainnya   memodifikasi kalender di dalam DATE_FORMAT tidak? - @Santi

Ini tidak mencegah untuk memodifikasi kalender secara langsung tetapi mencegah mengakses instance DATE_FORMAT, Jika 2 thread mencoba untuk mengeksekusi blok tersinkronisasi pada saat yang sama dengan argumen yang sama (yang merupakan contoh DATE_FORMAT) 1 utas harus menunggu yang lain untuk mengeksekusi blok tersinkronisasi tersebut. Itulah cara kerja sinkronisasi.

Sekarang seperti yang saya janjikan di komentar, saya membuat simulasi untuk membuktikan jawaban saya.

    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);
            }
        });


    }

Saya memodifikasi kode yang diberikan tanpa mengubah tujuannya. Seperti yang Anda lihat, saya menggunakan waktu tetap (dan peningkatan 100ms untuk per utas) untuk membandingkan hasil dengan versi kode yang disinkronkan dan tidak disinkronkan.

Saya mencetak Tanggal dengan dan juga menambahkan Tanggal ke ArrayList of String untuk bekerja dengan angka, bukan hanya melihat dan merasakan perbandingan.

Pertama izinkan saya menambahkan hasil cetak:

enter image description here

Di sisi kiri ada 2 tanggal ganda tercetak, Di sisi kanan tidak ada banyak tanggal

tentu saja 5 hasil pertama tidak membuktikan apa-apa, Anda harus memeriksa semuanya. Jadi saya menambahkan hasil ke Daftar dan hasil cetak setelah menghapus entri yang sama dari daftar

Berikut ini hasil untuk versi Tersinkronkan:

//Output of all executions
//All elements' size :100
//Unique elements' size :100

Berikut ini hasil dari versi Tidak Disinkronkan:

//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

Menurut hasil kami dapat mengatakan bahwa kalender berubah dengan X thread sebelum A, B, C ... utas pencetakan tanggal (atau menambahkan ke daftar)

Anda dapat menguji dan melihatnya sendiri, untuk hasil yang berbeda Anda memerlukan JDK 8 untuk menggunakan api sungai atau Anda dapat menggunakan kode lain. Tolong beri tahu saya jika Anda memiliki pertanyaan sehingga kami dapat berdebat.


1
2017-07-29 15:00