5 สิ่งที่คุณอาจไม่รู้เกี่ยวกับ Multithreading ใน Java
ที่มา: DZone Thread เป็นหัวใจสำคัญของภาษาการเขียนโปรแกรม Java แม้แต่การรันโปรแกรม Hello World ก็จำเป็นต้องมีเธรดหลัก หากจำเป็น เราสามารถเพิ่มเธรดอื่นๆ ให้กับโปรแกรมได้หากต้องการให้โค้ดแอปพลิเคชันของเราทำงานได้และมีประสิทธิภาพมากขึ้น หากเรากำลังพูดถึงเว็บเซิร์ฟเวอร์ มันจะประมวลผลคำขอหลายร้อยรายการพร้อมกัน มีการใช้หลายเธรดสำหรับสิ่งนี้ ไม่ต้องสงสัยเลยว่าเธรดมีประโยชน์ แต่การทำงานกับเธรดอาจเป็นเรื่องยากสำหรับนักพัฒนาหลายคน ในบทความนี้ ฉันจะแบ่งปันแนวคิดเกี่ยวกับมัลติเธรดห้าประการที่นักพัฒนาใหม่และมีประสบการณ์อาจไม่รู้1. ลำดับโปรแกรมและลำดับการดำเนินการไม่ตรงกัน
เมื่อเราเขียนโค้ด เราถือว่าโค้ดจะดำเนินการตามที่เราเขียนทุกประการ อย่างไรก็ตามในความเป็นจริงไม่เป็นเช่นนั้น คอมไพเลอร์ Java อาจเปลี่ยนลำดับการดำเนินการเพื่อปรับให้เหมาะสม หากสามารถระบุได้ว่าเอาต์พุตจะไม่เปลี่ยนแปลงในโค้ดแบบเธรดเดี่ยว ดูข้อมูลโค้ดต่อไปนี้:package ca.bazlur.playground;
import java.util.concurrent.Phaser;
public class ExecutionOrderDemo {
private static class A {
int x = 0;
}
private static final A sharedData1 = new A();
private static final A sharedData2 = new A();
public static void main(String[] args) {
var phaser = new Phaser(3);
var t1 = new Thread(() -> {
phaser.arriveAndAwaitAdvance();
var l1 = sharedData1;
var l2 = l1.x;
var l3 = sharedData2;
var l4 = l3.x;
var l5 = l1.x;
System.out.println("Thread 1: " + l2 + "," + l4 + "," + l5);
});
var t2 = new Thread(() -> {
phaser.arriveAndAwaitAdvance();
var l6 = sharedData1;
l6.x = 3;
System.out.println("Thread 2: " + l6.x);
});
t1.start();
t2.start();
phaser.arriveAndDeregister();
}
}
รหัสนี้ดูเหมือนง่าย เรามีอินสแตนซ์ข้อมูลที่ใช้ร่วมกันสองอินสแตนซ์ ( sharedData1และsharedData2 ) ที่ใช้สองเธรด เมื่อเรารันโค้ด เราคาดหวังว่าผลลัพธ์จะเป็นดังนี้:
เธรด 2: 3 เธรด 1: 0,0,0
แต่ถ้าคุณรันโค้ดหลายครั้ง คุณจะเห็นผลลัพธ์ที่แตกต่างออกไป:
เธรด 2: 3 เธรด 1: 3,0,3 เธรด 2: 3 เธรด 1: 0,0,3 เธรด 2: 3 เธรด 1: 3,3,3 เธรด 2: 3 เธรด 1: 0,3,0 เธรด 2 : 3 เธรด 1: 0,3,3
ฉันไม่ได้บอกว่าสตรีมเหล่านี้จะเล่นแบบนี้บนเครื่องของคุณทุกประการ แต่มันเป็นไปได้ทั้งหมด
2. จำนวนเธรด Java มีจำนวนจำกัด
การสร้างเธรดใน Java เป็นเรื่องง่าย อย่างไรก็ตามนี่ไม่ได้หมายความว่าเราจะสามารถสร้างได้มากเท่าที่เราต้องการ จำนวนเธรดมีจำนวนจำกัด เราสามารถค้นหาจำนวนเธรดที่เราสามารถสร้างบนเครื่องใดเครื่องหนึ่งได้อย่างง่ายดายโดยใช้โปรแกรมต่อไปนี้:package ca.bazlur.playground;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
public class Playground {
public static void main(String[] args) {
var counter = new AtomicInteger();
while (true) {
new Thread(() -> {
int count = counter.incrementAndGet();
System.out.println("thread count = " + count);
LockSupport.park();
}).start();
}
}
}
โปรแกรมข้างต้นนั้นง่ายมาก โดยจะสร้างเธรดในลูปแล้วพักไว้ ซึ่งหมายความว่าเธรดจะถูกปิดใช้งานสำหรับการใช้งานในอนาคต แต่ทำการเรียกของระบบและจัดสรรหน่วยความจำ โปรแกรมยังคงสร้างเธรดต่อไปจนกว่าจะสร้างไม่ได้อีกต่อไป และจากนั้นก็ส่งข้อยกเว้น เราสนใจจำนวนที่เราจะได้รับจนกว่าโปรแกรมจะมีข้อยกเว้น ในคอมพิวเตอร์ของฉัน ฉันสร้างได้เพียง 4065 เธรดเท่านั้น
3. จำนวนเธรดมากเกินไปไม่ได้รับประกันประสิทธิภาพที่ดีขึ้น
เป็นเรื่องไร้เดียงสาที่จะเชื่อว่าการทำให้เธรดเป็นเรื่องง่ายใน Java จะช่วยปรับปรุงประสิทธิภาพของแอปพลิเคชัน น่าเสียดายที่สมมติฐานนี้ผิดกับโมเดลมัลติเธรดแบบดั้งเดิมของเราที่ Java นำเสนอในปัจจุบัน ที่จริงแล้ว การมีเธรดมากเกินไปอาจทำให้ประสิทธิภาพของแอปพลิเคชันลดลงได้ ก่อนอื่นมาถามคำถามนี้: จำนวนเธรดสูงสุดที่เหมาะสมที่สุดที่เราสามารถสร้างได้คือเท่าใดเพื่อเพิ่มประสิทธิภาพแอปพลิเคชันสูงสุด คำตอบไม่ง่ายขนาดนั้น ขึ้นอยู่กับประเภทของงานที่เราทำเป็นอย่างมาก หากเรามีงานอิสระหลายงาน ซึ่งทั้งหมดเป็นงานด้านการคำนวณและไม่บล็อกทรัพยากรภายนอกใดๆ การมีเธรดจำนวนมากจะไม่ช่วยปรับปรุงประสิทธิภาพมากนัก ในทางกลับกัน หากเรามีโปรเซสเซอร์ 8 คอร์ จำนวนเธรดที่เหมาะสมที่สุดอาจเป็น (8 + 1) ในกรณีเช่นนี้ เราสามารถพึ่งพาเธรดแบบขนานที่นำมาใช้ใน Java 8 ตามค่าเริ่มต้น เธรดแบบขนานจะใช้พูล Fork/Join ที่ใช้ร่วมกัน สร้างเธรดเท่ากับจำนวนโปรเซสเซอร์ที่มีอยู่ซึ่งเพียงพอสำหรับการทำงานอย่างเข้มข้น การเพิ่มเธรดในงานที่ใช้ CPU มากโดยที่ไม่มีสิ่งใดถูกบล็อกจะไม่ปรับปรุงประสิทธิภาพ แต่เราก็จะสิ้นเปลืองทรัพยากรไปโดยเปล่าประโยชน์ บันทึก. เหตุผลในการมีเธรดเพิ่มเติมคือ แม้แต่เธรดที่เน้นการประมวลผลในบางครั้งก็ทำให้เกิดข้อผิดพลาดของเพจหรือถูกระงับด้วยเหตุผลอื่นบางประการ (ดู: Java Parallelism in Practice , Brian Goetz, หน้า 170) อย่างไรก็ตาม สมมติว่างานต่างๆ ถูกผูกไว้กับ I/O ในกรณีนี้ ขึ้นอยู่กับการสื่อสารภายนอก (เช่น ฐานข้อมูล API อื่นๆ) ดังนั้นจำนวนเธรดที่มากขึ้นจึงสมเหตุสมผล เหตุผลก็คือ เมื่อเธรดกำลังรอ Rest API เธรดอื่นๆ ก็สามารถทำงานต่อไปได้ ทีนี้ลองถามใหม่อีกครั้งว่ามีกี่กระทู้ที่มากเกินไปสำหรับกรณีนี้? พึ่งพา. ไม่มีตัวเลขที่สมบูรณ์แบบที่เหมาะกับทุกกรณี ดังนั้นเราจึงต้องทำการทดสอบอย่างเพียงพอเพื่อค้นหาว่าอะไรทำงานได้ดีที่สุดสำหรับปริมาณงานและการใช้งานเฉพาะของเรา ในสถานการณ์ทั่วไปส่วนใหญ่ เรามักจะมีชุดงานที่หลากหลาย และในกรณีเช่นนี้ สิ่งต่างๆ ก็จะเสร็จสิ้น ในหนังสือของเขา “Java Concurrency in Practice” Brian Goetz เสนอสูตรที่เราสามารถใช้ได้ในกรณีส่วนใหญ่ จำนวนเธรด = จำนวนคอร์ที่มีอยู่ * (1 + เวลารอ / เวลาบริการ) เวลารออาจเป็น IO ได้ เช่น การรอการตอบกลับ HTTP การได้รับการล็อค และอื่นๆ เวลาให้บริการ(เวลาบริการ) คือเวลาในการคำนวณ เช่น การประมวลผลการตอบสนอง HTTP การจัดเรียง/ยกเลิกการจัดเรียง และอื่นๆ ตัวอย่างเช่น แอปพลิเคชันเรียก API แล้วจึงประมวลผล หากเรามีโปรเซสเซอร์ 8 ตัวบนแอปพลิเคชันเซิร์ฟเวอร์ เวลาตอบสนอง API โดยเฉลี่ยคือ 100 มิลลิวินาที และเวลาประมวลผลการตอบสนองคือ 20 มิลลิวินาที ดังนั้นขนาดเธรดในอุดมคติจะเป็น:
ยังไม่มีข้อความ = 8 * ( 1 + 100/20) = 48
อย่างไรก็ตาม นี่เป็นการทำให้ง่ายเกินไป การทดสอบที่เพียงพอเป็นสิ่งสำคัญเสมอในการกำหนดจำนวน
4. มัลติเธรดไม่ใช่ความขนาน
บางครั้งเราใช้มัลติเธรดและความขนานแทนกัน แต่สิ่งนี้ไม่เกี่ยวข้องกันอีกต่อไป แม้ว่าใน Java เราจะทำได้ทั้งสองอย่างโดยใช้เธรด แต่มันก็เป็นสองสิ่งที่แตกต่างกัน “ในการเขียนโปรแกรม มัลติเธรดเป็นกรณีพิเศษโดยไม่คำนึงถึงกระบวนการที่ทำงานอยู่ และความเท่าเทียมคือการดำเนินการคำนวณ (อาจเกี่ยวข้อง) ไปพร้อมๆ กัน Multithreading คือการโต้ตอบกับสิ่งต่างๆ มากมายในเวลาเดียวกัน การเห็นพ้องต้องกันกำลังทำหลายสิ่งหลายอย่างในเวลาเดียวกัน” คำจำกัดความข้างต้นที่กำหนดโดย Rob Pike ค่อนข้างแม่นยำ สมมติว่าเรามีงานอิสระโดยสมบูรณ์ และสามารถคำนวณแยกกันได้ ในกรณีนี้ งานเหล่านี้เรียกว่าแบบขนานและสามารถดำเนินการได้ด้วยพูล Fork/Join หรือเธรดแบบขนาน ในทางกลับกัน ถ้าเรามีหลายงาน บางงานก็อาจต้องอาศัยงานอื่น วิธีที่เราเขียนและจัดโครงสร้างเรียกว่ามัลติเธรด มันเกี่ยวข้องกับโครงสร้างด้วย เราอาจต้องการทำงานหลายอย่างพร้อมกันเพื่อให้ได้ผลลัพธ์ที่แน่นอน โดยไม่จำเป็นต้องทำงานให้เสร็จเร็วขึ้น5. Project Loom ช่วยให้เราสามารถสร้างเธรดได้นับล้าน
ในประเด็นที่แล้ว ฉันแย้งว่าการมีเธรดมากขึ้นไม่ได้หมายความว่าประสิทธิภาพของแอปพลิเคชันจะดีขึ้น อย่างไรก็ตาม ในยุคของไมโครเซอร์วิส เรามีปฏิสัมพันธ์กับบริการต่างๆ มากเกินไปจนไม่สามารถทำงานเฉพาะด้านได้สำเร็จ ในสถานการณ์ดังกล่าว เธรดยังคงอยู่ในสถานะถูกบล็อกเกือบตลอดเวลา แม้ว่าระบบปฏิบัติการสมัยใหม่จะสามารถรองรับซ็อกเก็ตที่เปิดอยู่นับล้านได้ แต่เราไม่สามารถเปิดช่องทางการสื่อสารได้มากมาย เนื่องจากเราถูกจำกัดด้วยจำนวนเธรด แต่ถ้าคุณสร้างเธรดหลายล้านเธรด และแต่ละเธรดใช้ซ็อกเก็ตแบบเปิดเพื่อสื่อสารกับโลกภายนอกล่ะ สิ่งนี้จะปรับปรุงปริมาณงานแอปพลิเคชันของเราอย่างแน่นอน เพื่อสนับสนุนแนวคิดนี้ มีความคิดริเริ่มใน Java ที่เรียกว่า Project Loom เมื่อใช้มัน เราสามารถสร้างเธรดเสมือนจริงนับล้านได้ ตัวอย่างเช่น เมื่อใช้ข้อมูลโค้ดต่อไปนี้ ฉันสามารถสร้างเธรดได้ 4.5 ล้านเธรดบนเครื่องของฉันimport java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
public class Main {
public static void main(String[] args) {
var counter = new AtomicInteger();
// 4_576_279
while (true) {
Thread.startVirtualThread(() -> {
int count = counter.incrementAndGet();
System.out.println("thread count = " + count);
LockSupport.park();
});
}
}
}
ในการรันโปรแกรมนี้คุณต้องติดตั้ง Java 18 ซึ่งสามารถดาวน์โหลดได้ที่นี่ คุณสามารถรันโค้ดได้โดยใช้คำสั่งต่อไปนี้: java --source 18 --enable-preview Main.java
10 ส่วนขยาย JetBrains เพื่อต่อสู้กับหนี้ทางเทคนิค
ที่มา: DZone ทีมพัฒนาจำนวนมากรู้สึกกดดันอย่างมากที่จะต้องทำตามกำหนดเวลา ด้วยเหตุนี้พวกเขาจึงมักไม่มีเวลาเพียงพอที่จะแก้ไขและล้างโค้ดเบสของตน บางครั้งในสถานการณ์เหล่านี้ หนี้ทางเทคนิคก็สะสมอย่างรวดเร็ว ส่วนขยายของตัวแก้ไขสามารถช่วยแก้ปัญหานี้ได้ มาดูส่วนขยาย JetBrains ที่ดีที่สุด 10 อันดับเพื่อต่อสู้กับหนี้ทางเทคนิค (พร้อมการรองรับ Java)เครื่องมือการปรับโครงสร้างหนี้และทางเทคนิค
1. รีแฟคเตอร์อินไซต์
RefactorInsight ปรับปรุงการมองเห็นการเปลี่ยนแปลงโค้ดใน IDE โดยการให้ข้อมูลเกี่ยวกับการปรับโครงสร้างใหม่- ส่วนขยายกำหนดการปรับโครงสร้างใหม่ในคำขอผสาน
- Marks คอมมิตที่มีการรีแฟคเตอร์
- ช่วยในการดูการปรับโครงสร้างใหม่ของคอมมิตเฉพาะใดๆ ที่เลือกในแท็บ Git Log
- แสดงประวัติการปรับโครงสร้างใหม่ของคลาส วิธีการ และฟิลด์
2. ตัวติดตามปัญหา Stepsize ใน IDE
Stepsize เป็นตัวติดตามปัญหาที่ยอดเยี่ยมสำหรับนักพัฒนา ส่วนขยายนี้ช่วยให้วิศวกรไม่เพียงแต่สร้าง TODO และความคิดเห็นเกี่ยวกับโค้ดที่ดีขึ้นเท่านั้น แต่ยังช่วยจัดลำดับความสำคัญของหนี้ด้านเทคนิค การปรับโครงสร้างใหม่ และอื่นๆ ที่คล้ายกัน:- Stepsize ช่วยให้คุณสร้างและดูงานในโค้ดได้จากในตัวแก้ไข
- ค้นหาปัญหาที่ส่งผลต่อฟีเจอร์ที่คุณกำลังทำงานอยู่
- เพิ่มปัญหาให้กับ Sprint ของคุณโดยใช้การผสานรวม Jira, Asana, Linear, Azure DevOps และ GitHub
3. CodeStream ที่ระลึกใหม่
New Relic CodeStream เป็นแพลตฟอร์มการทำงานร่วมกันของนักพัฒนาสำหรับการพูดคุยและตรวจสอบโค้ด รองรับการดึงคำขอจาก GitHub, BitBucket และ GitLab การจัดการปัญหาจาก Jira, Trello, Asana และอีก 9 คน และจัดเตรียมการอภิปรายเกี่ยวกับโค้ด โดยเชื่อมโยงทุกอย่างเข้าด้วยกัน- สร้าง ตรวจสอบ และรวมคำขอพุลใน GitHub
- รับคำติชมเกี่ยวกับงานที่กำลังดำเนินการด้วยการตรวจสอบโค้ดเบื้องต้น
- หารือเกี่ยวกับปัญหาโค้ดกับเพื่อนร่วมทีม
สิ่งที่ต้องทำและความคิดเห็น
4. ปากกาเน้นข้อความความคิดเห็น
ปลั๊กอินนี้ช่วยให้คุณสร้างการเน้นบรรทัดความคิดเห็นและคำหลักภาษาที่กำหนดเองได้ ปลั๊กอินยังมีความสามารถในการกำหนดโทเค็นที่กำหนดเองเพื่อเน้นบรรทัดความคิดเห็น5. ความคิดเห็นที่ดีขึ้น
ส่วนขยาย Better Comments ช่วยให้คุณสร้างความคิดเห็นที่ชัดเจนยิ่งขึ้นในโค้ดของคุณ ด้วยส่วนขยายนี้ คุณจะสามารถจัดประเภทคำอธิบายประกอบของคุณเป็น:- การแจ้งเตือน
- คำขอ
- ทำ.
- ช่วงเวลาพื้นฐาน
จุดบกพร่องและช่องโหว่ด้านความปลอดภัย
6.โซนาร์ลินท์_
SonarLint ช่วยให้คุณสามารถแก้ไขปัญหาโค้ดก่อนที่จะเกิดขึ้น นอกจากนี้ยังสามารถใช้เป็นเครื่องตรวจตัวสะกดได้อีกด้วย SonarLint เน้นจุดบกพร่องและช่องโหว่ด้านความปลอดภัยในขณะที่คุณเขียนโค้ด พร้อมคำแนะนำในการแก้ไขที่ชัดเจน เพื่อให้คุณสามารถแก้ไขได้ก่อนที่จะคอมมิตโค้ด7. สปอตบัก
ปลั๊กอิน SpotBugs ให้การวิเคราะห์โค้ดไบต์แบบคงที่เพื่อค้นหาจุดบกพร่องในโค้ด Java จาก IntelliJ IDEA SpotBugs เป็นเครื่องมือตรวจจับข้อบกพร่องสำหรับ Java ที่ใช้การวิเคราะห์แบบคงที่เพื่อค้นหารูปแบบข้อบกพร่องมากกว่า 400 รูปแบบ เช่น การยกเลิกการอ้างอิงตัวชี้ null การวนซ้ำแบบไม่มีที่สิ้นสุด การใช้ไลบรารี Java ในทางที่ผิด และการหยุดชะงัก SpotBugs สามารถระบุข้อบกพร่องร้ายแรงหลายร้อยรายการในแอปพลิเคชันขนาดใหญ่ (โดยทั่วไปประมาณ 1 ข้อบกพร่องต่อ 1,000-2,000 บรรทัดของคำสั่งดิบที่ไม่มีข้อคิดเห็น)8. เครื่องสแกนช่องโหว่ Snyk
Vulnerability Scanner ของ Snyk ช่วยคุณค้นหาและแก้ไขช่องโหว่ด้านความปลอดภัยและปัญหาคุณภาพของโค้ดในโปรเจ็กต์ของคุณ- การค้นหาและแก้ไขปัญหาด้านความปลอดภัย
- ดูรายการปัญหาประเภทต่างๆ โดยแบ่งออกเป็นหมวดหมู่
- แสดงคำแนะนำในการแก้ไขปัญหา
GO TO FULL VERSION