Bagaimana cara kerja metode statis tersinkronisasi di Java dan dapatkah saya menggunakannya untuk memuat entitas Hibernate?

179

Jika saya memiliki kelas util dengan metode statis yang akan memanggil fungsi Hibernate untuk mencapai akses data dasar. Saya bertanya-tanya apakah membuat metode synchronizedini merupakan pendekatan yang tepat untuk memastikan keamanan benang.

Saya ingin ini mencegah akses info ke instance DB yang sama. Namun, saya sekarang yakin jika kode berikut mencegah getObjectByIddipanggil untuk semua Kelas ketika dipanggil oleh kelas tertentu.

public class Utils {
     public static synchronized Object getObjectById (Class objclass, Long id) {
           // call hibernate class
         Session session = new Configuration().configure().buildSessionFactory().openSession();
         Object obj = session.load(objclass, id);
         session.close();
         return obj;
     }

     // other static methods
}
tomat
sumber

Jawaban:

136

Dengan menggunakan sinkronisasi pada metode kunci statis Anda akan menyinkronkan metode dan atribut kelas (sebagai lawan dari metode dan atribut instan)

Jadi anggapan Anda benar.

Saya bertanya-tanya apakah membuat metode ini disinkronkan adalah pendekatan yang tepat untuk memastikan keamanan thread.

Tidak juga. Anda seharusnya membiarkan RDBMS Anda melakukan itu sebagai gantinya. Mereka bagus dalam hal-hal semacam ini.

Satu-satunya hal yang akan Anda dapatkan dengan menyinkronkan akses ke database adalah membuat aplikasi Anda sangat lambat. Lebih jauh lagi, dalam kode yang Anda posting Anda sedang membangun Pabrik Sesi setiap kali, dengan cara itu, aplikasi Anda akan menghabiskan lebih banyak waktu mengakses DB daripada melakukan pekerjaan yang sebenarnya.

Bayangkan skenario berikut:

Klien A dan B berupaya memasukkan informasi yang berbeda ke dalam catatan X dari tabel T.

Dengan pendekatan Anda, satu-satunya hal yang Anda dapatkan adalah memastikan satu dipanggil setelah yang lain, ketika hal ini akan tetap terjadi dalam DB, karena RDBMS akan mencegah mereka memasukkan setengah informasi dari A dan setengah dari B pada saat yang sama. . Hasilnya akan sama tetapi hanya 5 kali (atau lebih) lebih lambat.

Mungkin lebih baik untuk melihat bab "Transaksi dan Konkurensi" dalam dokumentasi Hibernate. Sebagian besar masalah yang Anda coba selesaikan, sudah dipecahkan dan cara yang jauh lebih baik.

OscarRyz
sumber
1
Jawaban yang sangat membantu! Terima kasih! Jadi Hibernate menangani cnocurrency dengan "penguncian optimis". Maka tidak perlu menggunakan metode "disinkronkan" sama sekali untuk menyelesaikan konkurensi akses data apa pun ?? Gunakan metode "disinkronkan" hanya jika data tidak disimpan dalam database ?? ..Ketika kamu menggunakannya ??
tomat
1
1) Saya pikir ada beberapa cara untuk menggunakan penguncian pesimistis juga. 2) Tidak, RDBMS dapat melakukan pekerjaan itu. 3) Jika data diakses oleh banyak utas secara bersamaan. 4) sinkronisasi berguna ketika dua utas harus berbagi data. Jika mereka tidak perlu, maka jauh lebih baik!
OscarRyz
7
Setiap restoran cepat saji menggunakan multithread. Satu utas membawa Anda memesan dan menggunakan utas lain untuk menyiapkannya, dan berlanjut dengan pelanggan berikutnya. Titik sinkronisasi hanya berfungsi ketika mereka menukar informasi untuk mengetahui apa yang harus disiapkan. Mengikuti model seperti itu benar-benar menyederhanakan kehidupan.
OscarRyz
5
"seluruh kelas" tidak dikunci. The Java spesifikasi bahasa mesin : For a class (static) method, the monitor associated with the Class object for the method's class is used. For an instance method, the monitor associated with this (the object for which the method was invoked) is used.Jadi jika satu thread memasuki sebuah metode statis, sama objek dikembalikan oleh Object # getClass terkunci. Utas lain masih dapat mengakses metode contoh.
Martin Andersson
4
lol Saya menemukan bahwa kata-kata saya sendiri tidak benar pada akhirnya juga. Saya berkata "Jadi jika satu utas memasuki metode statis, objek yang sama dikembalikan oleh Objek # getClass dikunci". Secara teknis tidak benar. Cerita panjang dibuat singkat untuk semua orang yang ingin tahu: Untuk setiap kelas dalam aplikasi Anda, ada Classobjek, dibuat oleh salah satu classloader mesin virtual. Seperti semua objek, objek ini juga memiliki Monitorketerkaitan dengannya. Dan monitor ini sedang dikunci.
Martin Andersson
233

Untuk menjawab pertanyaan secara lebih umum ...

Perlu diingat bahwa menggunakan metode yang disinkronkan benar-benar hanya singkatan (anggap kelas adalah SomeClass):

synchronized static void foo() {
    ...
}

sama dengan

static void foo() {
    synchronized(SomeClass.class) {
        ...
    }
}

dan

synchronized void foo() {
    ...
}

sama dengan

void foo() {
    synchronized(this) {
        ...
    }
}

Anda dapat menggunakan objek apa pun sebagai kunci. Jika Anda ingin mengunci himpunan bagian dari metode statis, Anda bisa

class SomeClass {
    private static final Object LOCK_1 = new Object() {};
    private static final Object LOCK_2 = new Object() {};
    static void foo() {
        synchronized(LOCK_1) {...}
    }
    static void fee() {
        synchronized(LOCK_1) {...}
    }
    static void fie() {
        synchronized(LOCK_2) {...}
    }
    static void fo() {
        synchronized(LOCK_2) {...}
    }
}

(untuk metode non-statis, Anda ingin menjadikan kunci sebagai bidang non-statis)

Scott Stanchfield
sumber
9
4 blok kode teratas itu adalah emas. Persis apa yang saya cari. Terima kasih.
Ryan Shillington
Apakah benar bahwa jika saya menggunakan kunci statis pada metode non-statis, tidak ada dua objek kelas SomeClass akan dapat menjalankan blok pada saat yang sama?
Samuel
2
@Samuel - Hampir ... Ini lebih tentang utas daripada instance objek. Anda benar karena contoh terpisah dari SomeClass semua akan menggunakan kunci / monitor yang sama: yang terkait dengan objek Someclass.class. Jadi jika dua utas berbeda memproses dua contoh SomeClass yang berbeda, keduanya tidak dapat berjalan pada saat yang sama. Namun, jika satu utas disebut metode dalam satu instance dari SomeClass, dan metode itu disebut metode dalam contoh lain, tidak ada pemblokiran yang akan terjadi.
Scott Stanchfield
@ScottStanchfield Anda telah membuat daftar cara untuk menyinkronkan metode, apakah semuanya itu setara?
Bionix1441
1
@ Bionix1441 - Ini semua tentang pelingkupan. Setiap mekanisme di atas memberi Anda kontrol penguncian yang lebih baik. Pertama, menggunakan instance itu sendiri untuk mengunci seluruh metode, kemudian instance itu sendiri untuk mengunci bagian di dalam metode, lalu instance objek apa pun untuk mengunci bagian.
Scott Stanchfield
17

Metode statis menggunakan kelas sebagai objek untuk mengunci, yaitu Utils.class untuk contoh Anda. Jadi ya, tidak apa-apa.

starblue
sumber
14

static synchronizedberarti memegang kunci pada objek kelas di Classmana sebagai synchronizedberarti memegang kunci pada objek kelas itu sendiri. Itu berarti, jika Anda mengakses metode tersinkronisasi non-statis di utas (eksekusi) Anda masih dapat mengakses metode tersinkronisasi statis menggunakan utas lain.

Jadi, mengakses dua jenis metode yang sama (baik dua metode statis atau dua metode non-statis) pada suatu titik waktu oleh lebih dari utas tidak mungkin.

prasad
sumber
10

Mengapa Anda ingin menegakkan bahwa hanya satu utas yang dapat mengakses DB pada suatu waktu?

Merupakan tugas driver database untuk mengimplementasikan penguncian yang diperlukan, dengan asumsi a Connectionhanya digunakan oleh satu utas pada satu waktu!

Kemungkinan besar, basis data Anda mampu menangani beberapa akses paralel

oxbow_lakes
sumber
Saya yakin ini adalah solusi untuk masalah transaksional. Yaitu, solusinya tidak menyelesaikan masalah yang sebenarnya
matt b
1
Saya tidak tahu itu .... Saya pikir saya harus mengimplementasikan ini secara manual. Terima kasih telah menunjukkannya! :)
tomato
2

Jika ada hubungannya dengan data dalam database Anda, mengapa tidak menggunakan penguncian isolasi database untuk mencapai?

Ray Lu
sumber
Saya tidak memiliki latar belakang basis data. Sekarang saya tahu!! Terima kasih telah menunjukkannya! :)
tomato
2

Untuk menjawab pertanyaan Anda, ya itu: synchronizedmetode Anda tidak dapat dijalankan oleh lebih dari satu utas sekaligus.

David Z
sumber
2

Bagaimana synchronized kerja kata kunci Java

Ketika Anda menambahkan synchronized kata kunci ke metode statis, metode ini hanya dapat dipanggil dengan utas tunggal setiap kali.

Dalam kasus Anda, setiap pemanggilan metode akan:

  • buat yang baru SessionFactory
  • buat yang baru Session
  • ambil entitasnya
  • mengembalikan entitas kembali ke pemanggil

Namun, ini adalah persyaratan Anda:

  • Saya ingin ini mencegah akses ke info ke instance DB yang sama.
  • mencegah getObjectByIddipanggil untuk semua kelas ketika dipanggil oleh kelas tertentu

Jadi, bahkan jika getObjectByIdmetodenya aman, implementasinya salah.

SessionFactory praktik terbaik

Ini SessionFactoryadalah thread-safe, dan ini adalah objek yang sangat mahal untuk dibuat karena perlu mengurai kelas entitas dan membangun representasi metamodel entitas internal.

Jadi, Anda tidak harus membuat SessionFactorypada setiap getObjectByIdpemanggilan metode.

Sebagai gantinya, Anda harus membuat contoh tunggal untuk itu.

private static final SessionFactory sessionFactory = new Configuration()
    .configure()
    .buildSessionFactory();

The Sessionharus selalu tertutup

Anda tidak menutup Sessiondalam finallyblok, dan ini bisa membocorkan sumber daya basis data jika pengecualian dilemparkan saat memuat entitas.

Menurut Session.loadmetode JavaDoc mungkin membuang HibernateExceptionjika entitas tidak dapat ditemukan dalam database.

Anda sebaiknya tidak menggunakan metode ini untuk menentukan apakah ada instance (gunakan get()saja). Gunakan ini hanya untuk mengambil contoh yang Anda anggap ada, di mana ketidakberadaan akan menjadi kesalahan aktual.

Itu sebabnya Anda perlu menggunakan finallyblok untuk menutup Session, seperti ini:

public static synchronized Object getObjectById (Class objclass, Long id) {    
     Session session = null;
     try {
         session = sessionFactory.openSession();
         return session.load(objclass, id);
     } finally {
         if(session != null) {
             session.close(); 
         }
     }
 }

Mencegah akses multi-utas

Dalam kasus Anda, Anda ingin memastikan hanya satu utas yang mendapatkan akses ke entitas tertentu.

Tetapi synchronizedkata kunci hanya mencegah dua utas memanggil getObjectByIdsecara bersamaan. Jika kedua utas memanggil metode ini satu demi satu, Anda masih akan memiliki dua utas menggunakan entitas ini.

Jadi, jika Anda ingin mengunci objek database tertentu sehingga tidak ada utas lain yang dapat memodifikasinya, maka Anda perlu menggunakan kunci basis data.

Kata synchronizedkunci hanya berfungsi dalam JVM tunggal. Jika Anda memiliki beberapa web node, ini tidak akan mencegah akses multi-thread di beberapa JVM.

Yang perlu Anda lakukan adalah menggunakan LockModeType.PESSIMISTIC_READatauLockModeType.PESSIMISTIC_WRITE saat menerapkan perubahan pada DB, seperti ini:

Session session = null;
EntityTransaction tx = null;

try {
    session = sessionFactory.openSession();

    tx = session.getTransaction();
    tx.begin();

    Post post = session.find(
        Post.class, 
        id, 
        LockModeType.LockModeType.PESSIMISTIC_READ
    );

    post.setTitle("High-Performance Java Perisstence");

    tx.commit();
} catch(Exception e) {
    LOGGER.error("Post entity could not be changed", e);
    if(tx != null) {
        tx.rollback(); 
    }
} finally {
    if(session != null) {
        session.close(); 
    }
}

Jadi, inilah yang saya lakukan:

  • Saya membuat yang baru EntityTransactiondan memulai transaksi basis data baru
  • Saya memuat Postentitas sambil memegang kunci pada catatan database terkait
  • Saya mengubah Postentitas dan melakukan transaksi
  • Dalam kasus Exceptionpelemparan, saya memutar kembali transaksi

Untuk detail lebih lanjut tentang ACID dan transaksi basis data, lihat artikel ini juga.

Vlad Mihalcea
sumber