[Java][華為云Java編程創造營][學習筆記][第三階段][05_Java多線程實戰][05_JUC并發包]
5.1,線程的ThreadLocal本地緩存對象

ThreadLocal線程范圍內的共享變量:
線程范圍內的共享變量,每個線程只能自己的數據,不能訪問別的線程的數據。
每個線程調用全局ThreadLocal對象的set方法,就相當于往其內部的map中增加一條記錄,key分別是各自的線程,value是各自的set方法傳進去的值。
ThreadLocal以內存換安全
5.2,線程的volatile關鍵字
volatile關鍵字可以用來修飾字段(成員變量),就是告知程序任何對該變量的訪問均需要從共享內存中獲取,而對它的改變必須同步刷新回共享內存,它能保證所有線程對變量訪問的可見性。
class UserThread extends Thread { private volatile boolean flag = false; public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } @Override public void run() { System.out.println(Thread.currentThread().getName() + ",線程正在運行"); while (flag) { } System.out.println(Thread.currentThread().getName() + ",線程運行結束"); } } public class Test { public static void main(String[] args) { UserThread ut = new UserThread(); ut.start(); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } ut.setFlag(false); /* * 輸出結果 * Thread-0,線程正在運行 Thread-0,線程運行結束 * */ } }
volatile的作用:使變量在多個線程之間可見,但是無法保證原子性。
需要注意的是一般volatile用于只針對多個線程可見的變量操作,并不能代替synchronized的同步功能。
public class ThreadVolatile extends Thread { public static volatile int n = 0; @Override public void run() { for (int i = 0; i < 10; i++) { try { n += 1; //為了使運行結果更隨機,延遲3毫秒 sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws Exception { Thread threads[] = new Thread[100]; for (int i = 0; i < threads.length; i++) { //建立100個線程 threads[i] = new ThreadVolatile(); } for (int i = 0; i < threads.length; i++) { //運行剛才建立的100個線程 threads[i].start(); } for (int i = 0; i < threads.length; i++) { //100個線程都執行完后繼續 threads[i].join(); } System.out.println("n= " + ThreadVolatile.n);//n= 909 } }
5.3,線程池的作用和應用
線程池的作用:
降低資源消耗。
通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
提高響應速度。
當任務到達時,任務可以不需要等到線程創建就能立即執行。
提高線程的可管理性。
線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
線程池的應用
場景:請求頻繁,考慮到服務的并發問題,如果每個請求來到后,服務都為它啟動一個線程,那么這對服務的資源可能會造成很大的浪費。
5.4,線程的同步工具類CountDownLatch
CountDownLatch同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。
CountDownLatch類是一個同步計數器,構造時傳入int參數,該參數就是計數器的初始值,每調用countDown()方法,計數器減1,計數器大于0時,await()方法會阻塞程序繼續執行。
由于調用了countDown()方法,所以在當前計數到達0之前,await()方法會一直受阻塞。之后,會釋放所有等待的線程,await()的所有后續調用都將立即返回。這種現象只出現一次,計數無法被重置。一個線程或者多個,等待另外N個線程完成某個事情之后才能執行。
CountDownLatch最重要的方法是countDown()和await()
import java.util.concurrent.CountDownLatch; class UserThread1 extends Thread { private CountDownLatch cd; private int sum1; public UserThread1(CountDownLatch cd) { this.cd = cd; } @Override public void run() { for (int i = 0; i <= 100; i++) { sum1 += i; } cd.countDown(); } public int getSum1() { return sum1; } } class UserThread2 extends Thread { private CountDownLatch cd; private int sum2; public UserThread2(CountDownLatch cd) { this.cd = cd; } @Override public void run() { for (int i = 101; i <= 200; i++) { sum2 += i; } cd.countDown(); } public int getSum2() { return sum2; } } public class Test { public static void main(String[] args) { CountDownLatch cd = new CountDownLatch(2); UserThread1 u1 = new UserThread1(cd); UserThread2 u2 = new UserThread2(cd); u1.start(); u2.start(); try { cd.await(); int sum = u1.getSum1() + u2.getSum2(); System.out.println("兩個線程計算的和為:" + sum);//兩個線程計算的和為:20100 } catch (InterruptedException e) { e.printStackTrace(); } } }
5.5,線程的同步工具類CyclicBarrier
CyclicBarrier是一個同步輔助類,允許一組線程互相等待,直到到達某個公共屏障點(common barrier point)。因為該barrier在釋放等待線程后可以重用,所以稱它為循環的barrier。
5.6,線程的同步工具類semaphore
Semaphore是一個計數信號量,它的本質是一個共享鎖,是基于AQS實現的,通過state變量來實現共享。通過調用acquire方法,對state值減一,當調用release對state值加一。當state變量小于0時,在AQS隊列中阻塞等待。
import java.util.concurrent.Semaphore; class Car extends Thread { private Address address; public Car(Address address) { this.address = address; } @Override public void run() { this.address.autoCar(); } } class Address { //每次停車的數量 private int num; //信號量 private Semaphore sm; public Address(int num) { this.num = num; sm = new Semaphore(this.num); } public void autoCar() { try { //加鎖 sm.acquire(); System.out.println(Thread.currentThread().getName() + ",進入停車場"); //模擬停車時間 Thread.sleep(3000); System.out.println(Thread.currentThread().getName() + ",離開停車場"); //釋放鎖 sm.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Test { public static void main(String[] args) { Address address = new Address(2); for (int i = 0; i < 5; i++) { new Car(address).start(); } /* * 輸出結果 * Thread-0,進入停車場 Thread-1,進入停車場 Thread-1,離開停車場 Thread-0,離開停車場 Thread-2,進入停車場 Thread-3,進入停車場 Thread-3,離開停車場 Thread-2,離開停車場 * */ } }
5.7,線程的交換類Exchanger
Exchanger(交換者)是一個用于線程間協作的工具類,Exchanger用于進行線程間的數據交換。
它提供一個同步點,在這個同步點,兩個線程可以交換彼此的數據。
這兩個線程通過exchanger()交換數據。
如果第一個線程先執行exchange()方法,它會一直等待第二個線程也執行exchange(),當兩個線程都到達同步點時,這兩個線程就可以交換數據,將本線程生產出來的數據傳遞給對方。
import java.util.concurrent.Exchanger; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test { private static final Exchanger
5.8,線程的Fork-Join機制
Fork/Join框架是Java7提供一個用于并行執行任務的框架,是一個把大任務分割成若干個小任務,最終匯總每個小任務結果后得到大任務結果的框架。
分治法:把一個規模大的問題劃分為規模較小的子問題,然后分而治之,最后合并子問題的解得到原問題的解。
Fork/Join案例:
import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveTask; class CountTask extends RecursiveTask
5.9,線程的組合案例購票
/* * 線程并發購票 * */ class Tickets { private int allowance; public Tickets(int allowance) { this.allowance = allowance; } public int getAllowance() { return allowance; } public void setAllowance(int allowance) { this.allowance = allowance; } //購票方法 public void buyTickets(int num) { synchronized (this) { int before = this.allowance; int after = before - num; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.setAllowance(after); } } } class CustomerRunnable implements Runnable { private Tickets tickets; public CustomerRunnable(Tickets tickets) { this.tickets = tickets; } @Override public void run() { tickets.buyTickets(1); System.out.println(Thread.currentThread().getName() + "購票成功,余票:" + tickets.getAllowance()); } } public class Test { public static void main(String[] args) { //創建tickets對象 Tickets tickets = new Tickets(5); //創建CustomerRunnable數組 CustomerRunnable[] customerRunnables = new CustomerRunnable[5]; //創建Thread數組 Thread[] threads = new Thread[5]; //創建100個CustomerRunnable對象 for (int i = 0; i < customerRunnables.length; i++) { customerRunnables[i] = new CustomerRunnable(tickets); } //創建100個線程 for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(customerRunnables[i]); threads[i].start(); } /* * 輸出結果 * Thread-1購票成功,余票:4 Thread-4購票成功,余票:3 Thread-2購票成功,余票:2 Thread-0購票成功,余票:1 Thread-3購票成功,余票:0 * */ } }
5.10,線程的組合案例購物
import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /* * 線程并發秒殺購物 * */ class Shop { //信號量 private Semaphore semaphore; public Shop(int num) { //實例化信號量,每次可以有5個線程獲得許可 semaphore = new Semaphore(num); } //用戶去搶購 public void userShopping(String name) { boolean flag = false; try { //怎么限流搶購,什么時候算搶購成功,還需要入庫 flag = this.semaphore.tryAcquire(1, TimeUnit.SECONDS); if (flag) { System.out.println(name + ",搶購成功,可以下單了"); TimeUnit.SECONDS.sleep(1); } else { System.out.println(name + ",對不起,搶購沒有成功,請重試。。。"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (flag) { //搶購成功,一定要注意釋放 this.semaphore.release(); } } } } class User extends Thread { private String sname; private Shop shop; public User(String sname, Shop shop) { super(); this.sname = sname; this.shop = shop; } @Override public void run() { this.shop.userShopping(sname); } public String getSname() { return sname; } public void setSname(String sname) { this.sname = sname; } public Shop getShop() { return shop; } public void setShop(Shop shop) { this.shop = shop; } } public class Test { public static void main(String[] args) { Shop shop = new Shop(5); for (int i = 0; i < 5; i++) { new User("張" + i, shop).start(); } } /* * 輸出結果 * 張0,搶購成功,可以下單了 張4,搶購成功,可以下單了 張2,搶購成功,可以下單了 張1,搶購成功,可以下單了 張3,搶購成功,可以下單了 * */ }
5.11,線程的鎖的synchronized和Lock、volatile區別
synchronized和volatile區別:
volatile關鍵字解決的是變量在多個線程之間的可見性;而synchronized關鍵字解決的是多個線程之間訪問共享資源的同步性。
volatile只能用于修飾變量,而synchronized可以修飾方法,以及代碼塊。
多線程訪問volatile不會發生阻塞,而synchronized會出現阻塞。
volatile能保證變量在多個線程之間的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因為它會將私有內存和公有內存中的數據做同步。
synchronized和Lock區別:
Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現。
synchronized在發生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現象的出現;而Lock在發生異常時,如果沒有主動通過unlock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖。
Lock可以提高多個線程進行讀操作的效率(讀寫鎖)。
5.12,線程的讀寫分離機制
ReadWriteLock顧名思義,是讀寫鎖:它維護了一對相關的鎖"讀取鎖"和"寫入鎖",一個用于讀取操作,一個用于寫入操作。
讀取鎖用于只讀操作,它是共享鎖,能同時被多個線程獲取。
寫入鎖用于寫入操作,它是獨占鎖,寫入鎖只能被一個線程鎖獲取。
不能同時存在讀取鎖和寫入鎖!可以讀/讀,但不能讀/寫,寫/寫。
Java 多線程
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。