Tulis servlet aman utas

Jika Anda menulis aplikasi Web di Java, servlet adalah teman terbaik Anda. Apakah Anda menulis Java ServerPages (JSP) atau servlet biasa, Anda berada di bawah kekuasaan servlet dan penerima yang beruntung dari apa yang ditawarkan servlet. Jadi mari kita lihat lebih dekat servlet.

Seperti yang kita ketahui, saat membangun situs web, kita terutama tertarik pada dua metode servlet: doPost()dan doGet(). Metode yang digarisbawahi untuk kedua metode tersebut adalah service(). Kemudian, saya melihat lebih dekat pada metode ini dan bagaimana hubungannya dengan keamanan benang, tapi pertama-tama mari kita tinjau konsep utas.

Sebuah utas adalah proses eksekusi tunggal; dengan kata lain, individu, aliran kontrol yang berurutan dalam suatu program. Ketika kami mengatakan bahwa program multithread, kami tidak menyiratkan bahwa program menjalankan dua contoh terpisah secara bersamaan (seolah-olah Anda menjalankan program secara bersamaan dua kali dari baris perintah). Sebaliknya, kami mengatakan bahwa contoh yang sama (dijalankan hanya sekali) memunculkan banyak utas yang memproses contoh kode tunggal ini. Ini berarti bahwa lebih dari satu aliran kendali berurutan berjalan melalui blok memori yang sama.

Jadi, apa yang kami maksud dengan thread-safe, Anda bertanya? Ketika beberapa utas mengeksekusi satu contoh program dan oleh karena itu berbagi memori, beberapa utas mungkin mencoba membaca dan menulis ke tempat yang sama di memori. Mari kita lihat contohnya. Jika kita memiliki program multithread, kita akan memiliki banyak thread yang memproses instance yang sama (lihat Gambar 1).

Apa yang terjadi saat Thread-Amemeriksa variabel instanceVar? Perhatikan bagaimana Thread-Bbaru saja bertambah instanceVar. Masalahnya di sini Thread-Atelah ditulis ke instanceVardan tidak mengharapkan nilai itu berubah kecuali Thread-Asecara eksplisit melakukannya. Sayangnya Thread-Bmemikirkan hal yang sama tentang dirinya sendiri; satu-satunya masalah adalah mereka berbagi variabel yang sama. Masalah ini tidak hanya terjadi pada servlet. Ini adalah masalah pemrograman umum yang hanya muncul saat melakukan multithreading pada aplikasi. Anda mungkin berpikir; "Yah, aku tidak meminta multithreading. Aku hanya ingin servlet!" Dan servlet adalah apa yang Anda miliki. Izinkan saya memperkenalkan Anda kepada teman kami wadah servlet.

Wadah servlet Anda bukan boneka

Banyak keajaiban terjadi antara permintaan HTTP browser Web dan kode yang kita tulis di dalam metode doGet()dan doPost(). Wadah servlet memungkinkan "keajaiban" ini. Seperti program Java lainnya, servlet harus dijalankan dalam JVM, tetapi untuk aplikasi Web, kami juga memiliki kerumitan dalam menangani permintaan HTTP — di situlah container servlet masuk. Container servlet bertanggung jawab atas pembuatan, penghancuran, dan eksekusi; urutan kejadian ini disebut sebagai siklus hidup servlet.

Siklus hidup servlet adalah topik penting, dan karenanya, Anda akan menemukannya pada ujian sertifikasi Java Sun. Alasan pentingnya ini terutama karena begitu banyak siklus hidup servlet di luar kendali programmer. Kami tidak terlalu khawatir (untuk sebagian besar) tentang berapa banyak instance servlet kami yang ada saat runtime. Kami juga tidak secara umum peduli tentang pemanfaatan memori mengenai penciptaan dan penghancuran servlet kami. Alasan kurangnya perhatian kami adalah karena wadah servlet menangani ini untuk kami (ya, lebih banyak sihir).

Kontainer servlet tidak hanya menangani siklus hidup servlet, tetapi juga berfungsi dengan baik. Wadah servlet memperhatikan efisiensi. Ini memastikan bahwa ketika servlet dibuat, mereka digunakan secara efisien, dan, ya, Anda dapat menebaknya, ini termasuk multithreading. Seperti yang diilustrasikan oleh Gambar 2, beberapa utas memproses servlet Anda secara bersamaan.

Bayangkan saja masalah kinerja yang akan kita alami dengan Situs web sepopuler Google atau Amazon jika situs tersebut tidak dibuat menggunakan pemrosesan multithread yang efisien. Meskipun aplikasi Web kita mungkin tidak sepopuler Google, namun tetap tidak praktis untuk membangun situs yang membutuhkan servlet untuk dipakai untuk setiap permintaan. Untuk alasan itu, saya bersyukur bahwa servlet container menangani multithreading untuk saya. Jika tidak, kebanyakan dari kita harus mengubah cara kita mendesain servlet kita.

Sekarang kita telah mengetahui bahaya dari aplikasi multithread dan kita tahu bahwa semua servlet kita multithread, mari kita lihat kapan tepatnya multithreading akan menjadi masalah bagi kita.

Apakah Anda aman untuk benang?

Di bawah ini adalah servlet sederhana yang tidak aman untuk thread. Perhatikan baik-baik, karena pada pandangan pertama, tidak ada yang salah dengannya:

paket threadSafety; impor java.io.IOException; import javax.servlet. *; import javax.servlet.http. *; import java.math. *; public class SimpleServlet memperluas HttpServlet {// Sebuah variabel yang BUKAN thread-safe! penghitung int pribadi = 0; public void doGet (HttpServletRequest req, HttpServletResponse resp) melempar ServletException, IOException {doPost (req, resp); } public void doPost (HttpServletRequest req, HttpServletResponse resp) melempar ServletException, IOException {resp.getWriter (). println (""); resp.getWriter (). println (ini + ":

"); untuk (int c = 0; c <10; c ++) {resp.getWriter (). println (" Counter = "+ counter +"

"); coba {Thread.currentThread (). sleep ((long) Math.random () * 1000); counter ++;} catch (InterruptedException exc) {}} resp.getWriter (). println (" ");}}

Variabel counteradalah variabel instan, disebut demikian karena terikat dengan instance kelas. Karena itu didefinisikan dalam definisi kelas, itu termasuk dalam instance kelas itu. Sangat mudah untuk menempatkan variabel kita dalam lingkup ini karena ia berada di luar masing-masing metode kelas dan dapat diakses kapan saja. Nilainya juga dipertahankan di antara panggilan metode. Masalahnya di sini adalah bahwa wadah servlet kita multithread dan berbagi satu instance servlet untuk banyak permintaan. Apakah mendefinisikan variabel Anda sebagai variabel instan terdengar seperti ide yang bagus sekarang? Ingat, hanya satu tempat dalam memori yang dialokasikan untuk variabel ini, dan itu dibagi antara semua utas yang bermaksud untuk mengeksekusi instance kelas yang sama ini.

Mari kita cari tahu apa yang terjadi jika kita menjalankan servlet ini secara bersamaan. Kami menambahkan penundaan dalam pemrosesan dengan menggunakan sleep()metode ini. Metode ini membantu mensimulasikan perilaku yang lebih akurat, karena sebagian besar permintaan berbeda dalam jumlah waktu yang diperlukan untuk pemrosesan. Tentunya seperti keberuntungan kita sebagai programmer, hal ini juga menyebabkan masalah kita lebih sering terjadi. Servlet sederhana ini akan bertambah countersedemikian rupa sehingga setiap servlet harus dapat menampilkan nilai berurutan. Kami membuat permintaan simultan dengan menggunakan bingkai HTML; setiap sumber frame adalah servlet yang sama:

   

Kode kami, yang merupakan servlet non-thread-safe, menghasilkan output berikut:

[email protected]: Penghitung = 0 Penghitung = 2 Penghitung = 4 Penghitung = 6 Penghitung = 9 Penghitung = 11 Penghitung = 13 Penghitung = 15 Penghitung = 17 Penghitung = 19 [email protected]: Penghitung = 0 Penghitung = 1 Penghitung = 3 Penghitung = 5 Penghitung = 7 Penghitung = 8 Penghitung = 10 Penghitung = 12 Penghitung = 14 Penghitung = 16 Keamanan [email protected]: Penghitung = 18 Penghitung = 20 Penghitung = 22 Penghitung = 23 Penghitung = 24 Penghitung = 25 Penghitung = 26 Penghitung = 27 Penghitung = 28 Penghitung = 29 

Seperti yang bisa kita lihat dalam hasil kita, kita gagal mendapatkan hasil yang kita inginkan. Perhatikan nilai yang dicetak dari thisreferensi diduplikasi. Ini adalah alamat memori servlet. Ini memberitahu kita bahwa hanya satu servlet yang dipakai untuk melayani semua permintaan. Servlet mencoba yang terbaik untuk mengeluarkan data berurutan, tetapi karena semua utas berbagi memori yang dialokasikan untukcounter, kami berhasil menginjak kaki kami sendiri. Kita dapat melihat bahwa nilainya tidak selalu berurutan, itu buruk! Bagaimana jika variabel itu digunakan untuk menunjukkan informasi pribadi pengguna? Bagaimana jika pengguna login ke sistem perbankan online mereka dan pada halaman tertentu, pengguna tersebut melihat informasi perbankan orang lain? Masalah ini dapat memanifestasikan dirinya dalam banyak cara, yang sebagian besar sulit untuk diidentifikasi, tetapi kabar baiknya adalah masalah ini dapat diatasi dengan mudah. Jadi mari kita lihat opsi kami.

Pertahanan pertama Anda: Penghindaran

Saya selalu mengatakan bahwa cara terbaik untuk memperbaiki masalah adalah dengan menghindari semuanya; dalam kasus kami, pendekatan ini adalah yang terbaik. Saat membahas keamanan thread, kami hanya tertarik pada variabel yang kami baca dan tulis serta yang berkaitan dengan percakapan Web tertentu. Jika variabel tersebut untuk penggunaan hanya-baca atau untuk seluruh aplikasi, maka tidak ada salahnya berbagi ruang memori ini di semua contoh. Untuk semua penggunaan variabel lainnya, kami ingin memastikan bahwa kami memiliki akses yang disinkronkan ke variabel (lebih lanjut tentang ini sebentar lagi) atau bahwa kami memiliki variabel unik untuk setiap utas.

Untuk memastikan kita memiliki contoh variabel unik kita sendiri untuk setiap utas, kita cukup memindahkan deklarasi variabel dari dalam kelas ke dalam metode yang menggunakannya. Kami sekarang telah mengubah variabel kami dari variabel contoh menjadi variabel lokal. Perbedaannya adalah, untuk setiap panggilan ke metode, variabel baru dibuat; oleh karena itu, setiap utas memiliki variabelnya sendiri. Sebelumnya, ketika variabel adalah variabel instance, variabel tersebut dibagikan untuk semua thread yang memproses instance kelas tersebut. Kode thread-safe berikut memiliki perbedaan yang halus, namun penting. Perhatikan dimana variabel counter dideklarasikan!

impor java.io.IOException; import javax.servlet. *; import javax.servlet.http. *; import java.math. *; kelas publik SimpleServlet memperluas HttpServlet {public void doGet (HttpServletRequest req, HttpServletResponse resp) melempar ServletException, IOException {doPost (req, resp); } public void doPost (HttpServletRequest req, HttpServletResponse resp) melempar ServletException, IOException {// Variabel yang IS thread-safe! penghitung int pribadi = 0; resp.getWriter (). println (""); resp.getWriter (). println (ini + ":

"); untuk (int c = 0; c <10; c ++) {resp.getWriter (). println (" Counter = "+ counter +"

"); coba {Thread.currentThread (). sleep ((long) Math.random () * 1000); counter ++;} catch (InterruptedException exc) {}} resp.getWriter (). println (" ");}}

Pindahkan deklarasi variabel ke dalam doGet() method and test again. Notice a change in behavior? I know, you're thinking; "It can't be that easy," but usually it is. As you scramble to revisit your latest servlet code to check where you declared your variables, you may run into a small snag. As you move your variables from within the class definition to within the method, you may find that you were leveraging the scope of the variable and accessing it from within other methods. If you find yourself in this situation, you have a couple of choices. First, change the method interfaces and pass this variable (and any other shared variables) to each method requiring it. I highly recommend this approach. Explicitly passing your data elements from method to method is always best; it clarifies your intentions, documents each method's requirements, makes your code well structured, and offers many other benefits.

If you discover that you must share a variable between servlets and this variable is going to be read from and written to by multiple threads (and you are not storing it in a database), then you will require thread synchronization. Sorry, there is no way around it now.

Your second defense: Partial synchronization

Thread synchronization is an important technique to know, but not one you want to throw at a solution unless required. Anytime you synchronize blocks of code, you introduce bottlenecks into your system. When you synchronize a code block, you tell the JVM that only one thread may be within this synchronized block of code at a given moment. If we run a multithreaded application and a thread runs into a synchronized code block being executed by another thread, the second thread must wait until the first thread exits that block.

Penting untuk secara akurat mengidentifikasi blok kode mana yang benar-benar perlu disinkronkan dan sesedikit mungkin menyinkronkan. Dalam contoh kami, kami berasumsi bahwa menjadikan variabel contoh kami sebagai variabel lokal bukanlah sebuah opsi. Lihat bagaimana kami akan menyinkronkan blok kode yang penting: