string是线程安全的么_Java-21 多线程 - 是阿凯啊
1.多線程概述
進程:一個程序運行,程序在內存中分配的那片空間。
線程:進程中一個執行單元執行路徑
進程中至少有一個線程,如果進程中有多個線程,就是多線程的程序。
并行與并發:
并行:某一時間點,有多個程序同時執行,多核CPU運行 并發:某一時間段,有多個程序同時執行,并不是真正意義的同時執行。 為多線程。并發真的是同時執行嗎?
不是,而是時間間隔很短,造成同時執行感覺。多線程優勢?
提高了用戶體驗,提高了程序的運行效率,提高CPU使用率。
2.開啟線程兩種方式
開啟線程
/** 1.繼承Thread* 2.重寫run方法* 3.創建子類的對象* 4.調用start方法* */ public class ThreadDemo1 {public static void main(String[] args) {// 創建子類的對象Demo d1 = new Demo("Jack");Demo d2 = new Demo("Tom");// 設置線程名字("d1");// 獲取d1執行線程名字(());// 獲取當前線程名字(().getName());// 調用start方法();();} } // 繼承Thread class Demo extends Thread{String nickName;public Demo(String nickName) {this.nickName = nickName;}// 重寫run方法public void run() {for(int i=0;i<30;i++) {(nickName + "---" + i);}} } // 整個運行過程有三個線程運行,主線程開啟d1和d2線程run方法與start方法區別
start:開啟新的線程,會自動調用run方法在新的線程中執行 run:沒有開啟新的線程,只是普通方法開啟新線程第二種方式
聲明實現Runnable接口的類,該類然后實現run方法,然后可以分配該類的實例,在創建Thread時做為一個參數來傳遞并啟動,采用這種風格的同一個例子 /** 實現多線程第二種方式:* 1.實現Runnable* 2.重寫run方法* 3.創建Runnable子類的對象* 4.創建Thread類的對象,把第三步的對象傳到構造方法中* 5.使用Thread子類對象,調用start方法* */ public class ThreadDemo2 {public static void main(String[] args) {Demo5 d = new Demo5();// 只有Thread或子類線程對象才是線程對象。它只是線程任務度夏寧。Thread th = new Thread(d); // th才是線程對象Thread th2 = new Thread(d);();();} }class Demo5 implements Runnable{public void run() {for (int i=0;i<20;i++) {(().getName() + "---" + i);}} }兩種實現方式,區別是?
第一種方式有局限性的,因為Java是單繼承,如果一個類已經有一個繼承,它就不能再繼承Thread類,就無法實現多線程。而第二種實現通過接口方式實現更合理,并且第二種方式更加符合面向對象特點:高內聚低耦合,把線程對象和線程任務分離開了。
3.線程中方法
方法使用
public Static void sleep(long millis) 靜態方法 進入阻塞狀態,時間結束后進入可執行 (3000);sleep方法讓誰阻塞,取決于他在哪個線程中。
方法使用
public final void join() 被誰調用,讓哪個線程先執行,執行完畢后,再執行所在線程 public class JoinDemo1 {public static void main(String[] args) {Sum s = new Sum();();// join:用誰調用,就讓那個線程先執行,執行完畢后,再執行他所在線程(將他所在線程阻塞)。try {();} catch (InterruptedException e) {// TODO Auto-generated catch block();}();} }class Sum extends Thread{static int sum = 0;public void run() {for(int i=0;i<=1000;i++) {sum += i;}} }方法
public static void yield() 讓其他線程先執行,不一定生效,因為讓誰執行是CPU決定的方法
停止一個線程方法
打斷線程的阻塞狀態,進入可執行狀態,會拋出異常 public class interruptDemo {public static void main(String[] args) {Demo3 d = new Demo3();();// 將d執行線程阻塞狀態打斷。();("over");} }class Demo3 extends Thread{public void run() {try {(2000);} catch (InterruptedException e) {// TODO Auto-generated catch block();}for (int i=0;i<10;i++) {(i);}} } public class InterruptDemo2 {public static void main(String[] args) {// 主線程傳給Demo4Demo4 d = new Demo4(());();try {(2000);} catch (InterruptedException e) {// TODO Auto-generated catch block();} ("over");} }class Demo4 extends Thread{Thread th;public Demo4(Thread th) {this.th = th;}// 用于打斷主線程public void run() {();} }- 練習:創建兩個線程,一個線程負責打印大寫字母表,一個線程負責打印小寫字母表
4.線程生命的周期
主線程執行時候在棧空間,開辟空間給子線程,而他們的之間棧是獨立的,但他們堆空間數據是共享的5.線程安全的問題
public class SellTicketsDemo {public static void main(String[] args) {Tickets t = new Tickets();Thread th1 = new Thread(t);Thread th2 = new Thread(t);Thread th3 = new Thread(t);();();();} }class Tickets implements Runnable{static int tickets = 100;public void run() {while (true) {if (tickets> 0) {try {(30);} catch (InterruptedException e) {// TODO Auto-generated catch block();}(().getName() + "====" + "正在出售第" + tickets-- +"張票!");} else {break;}}} }在執行代碼時候,會發現多個線程會賣出同一張票。這樣會產生線程安全問題。
線程安全產生原因:1.具備多線程。2.操作共享數據。3.操作共享數據的代碼有多條。
通過加鎖:讓每一時刻只能有一個線程操作數據。方式有三種
1.同步代碼塊
synchronized(鎖對象){容易產生線程安全問題的代碼 }// 鎖對象:可以是任意對象,但是必須保證多個線程使用是同一個對象。 public class SellTicketsDemo {public static void main(String[] args) {Tickets t = new Tickets();Thread th1 = new Thread(t);Thread th2 = new Thread(t);Thread th3 = new Thread(t);();();();} }class Tickets implements Runnable{static int tickets = 100;// 如果o方法run方法里,則無法實現線程安全,原因是執行run方法,實現3個o對象,對于這三個線程來說o對象不是共有的同一個對象。Object o = new Object();public void run() {while (true) {synchronized (o) {// o所在的類被new幾次if (tickets> 0) {try {(30);} catch (InterruptedException e) {// TODO Auto-generated catch block();}(().getName() + "====" + "正在出售第" + tickets-- +"張票!");} else {break;}}}} }- 鎖對象選取錯誤示例:
2.同步方法
- 把synchronized放到方法的修飾符中,鎖的是整個方法。
上述代碼雖然解決了線程安全問題,但是編程了單線程程序,原因synchronized鎖的范圍太大,第一個進來線程,執行完整個while循環。導致在使用同步方法時候也需要注意這個問題。
package com.xjk; // 懶漢式:存在問題,存在線程安全問題 public class Singleton2 {private static Singleton2 s;// 構造方法私有化,為了不讓別人隨便newprivate Singleton2() {}// 通過synchronized 解決線程安全問題public synchronized static Singleton2 getInstance() {if (s == null) {s = new Singleton2();} return s;} } // 當啟100個線程,當一個線程執行到s=new Singleton2();此時剛要new Singleton2,cpu切到第二個線程,因為s此時還是等于null,第二個線程也執行new Singleton2() 導出單例模式創建多個對象。此時通過同步方法添加synchronized可以有效解決此問題。- 同樣StringBuffer是線程安全的內部有synchronized,效率相對StringBuilder低,StringBuilder是線程不安全的。
3.Lock
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class SellTicketsDemo4 {public static void main(String[] args) {Tickets4 th1 = new Tickets4();new Thread(th1).start();new Thread(th1).start();new Thread(th1).start();} }class Tickets4 implements Runnable{static int tickets = 100;// 也要保證該鎖對象對于多個線程是同一個Lock lock = new ReentrantLock();public synchronized void run() {while (true) {();// 加鎖try {if (tickets> 0) {try {(30);} catch (InterruptedException e) {();}(().getName() + "====" + "正在出售第" + tickets-- +"張票!");} else {break;}} finally {();//解鎖}}} }- 釋放鎖的代碼放到finally代碼塊中,否則容易造成程序阻塞。
6.死鎖
- 是指兩個或兩個以上的線程在執行的過程中,因爭奪資源產生一種互相等待現象。
7.線程池用法
線程池可以減少創建和銷毀線程的次數,每個工作線程都可以被重復利用,可執行多個任務。
可以根據系統的承受能力,調整線程池中工作線程數目,放置因為消耗過多的內存,而把服務器累死(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最后死機)。
線程池的創建:
public static ExecutorService newCachedThreadPool()創建一個具有緩存功能的線程池 public static ExecutorService new FixedThreadPool(int nThreads)創建一個可重用的,具有固定線程數的線程池 public static ExecutorService newSingleThreadExecutor()創建一個只有單線程的線程池,相當于上個方法的參數是1示例:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class ThreadPoolDemo {public static void main(String[] args) {// 1ExecutorService pool = ();(new Runnable() {public void run() {("hello world");}});// 關閉線程池();// 2pool = (20);} } // 緩存線程池,如果沒有任務會等待60s就關閉
8.wait/Notify/NotifyAll
- wait,notify,notifyAll這三個方法都是Object中方法,并且這三個方法必須在同步方法或同步代碼塊中使用。
? notify或notifyAll以后,被喚醒的線程并不是立馬執行,需要等到notify,notifyAll所在代碼塊執行完畢后才會執行。因為只有同步代碼塊執行完畢后,才會釋放鎖對象,其他線程才可以進來。
? wait方法會釋放鎖對象,也就是一個線程使用wait進入等待狀態后,允許其他線程進入同步代碼塊。而sleep方法不會釋放鎖對象,到時間后自己會醒來。
public class WaitNotifyDemo {public static void main(String[] args) {Object o = new Object();// 當使用new Thread(new Demos1(o)).start();new Thread(new Demos1(o)).start();try {// sleep 作用是保證上面2個線程都執行到wait,然后第三個線程可以使用notifyAll解除阻塞(30);} catch (InterruptedException e) {// TODO Auto-generated catch block();}new Thread(new Demos2(o)).start();} }class Demos1 implements Runnable{Object o;public Demos1(Object o) {this.o = o;}@Overridepublic void run() {synchronized(o) {(().getName() + "Wait ,,,start...");try {// 阻塞線程();} catch (InterruptedException e) {// TODO Auto-generated catch block();}("等待結束...");}} }class Demos2 implements Runnable{Object o;public Demos2(Object o) {this.o = o;}@Overridepublic void run() {synchronized(o) {(().getName() + "notify ,,,start...");// 解除阻塞// ();All();(().getName() + "notify ,,,end...");}} } /* Thread-0Wait ,,,start... Thread-1Wait ,,,start... Thread-2notify ,,,start... Thread-2notify ,,,end... 等待結束... 等待結束...* */? 注意:
? 一定要在同步代碼塊中執行,使用鎖對象調用。
? 2.要想能喚醒wait,必須使用同一個鎖對象調用notify/notifyAll
? 方法會釋放鎖對象,進入了等待狀態以后,允許其他線程進入同步代碼塊執行。
? 方法喚醒了以后,wait不是立馬執行,等待notify中代碼執行完畢。
? 5.而notify方法只能喚醒一個(隨機喚醒),而notifyAll全部都能喚醒
為什么wait,notify,notfiyAll 放到Object類中?
因為他們使用鎖對象調用,鎖對象可以是任意對象,任意對象都有的方法定義在Object
9.定時器
方法
public void schedule(TimerTask task, long delay) 延遲多少毫秒后執行定時任務 public void schedule(TimerTask task, Date date) 指定時間執行定時任務 public void schedule(TimerTask task, long delay, long period) 延遲執行,指定間隔后循環執行 public void cancel() 取消定時任務示例1:
import java.util.Timer; import java.util.TimerTask;public class TimerDemo {public static void main(String[] args) {Timer timer = new Timer();// 5000毫秒執行一次任務,同一個定時任務只能執行一次// 它是多線程啟動的(new TimerTask() {public void run() {("你好");// 取消定時任務,一般放到定時任務中();}}, 5000);} }示例2:日期定時
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask;public class TimerDemo {public static void main(String[] args) throws ParseException {Timer timer = new Timer();// 如果時間已經過期,它會立刻運行Date d = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2020-10-10 10:10:10");(new TimerTask() {public void run() {("起床了!");}}, d);} }示例3:
import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask;public class TimerDemo2 {public static void main(String[] args) {// 3秒后執行,每隔1秒執行一次new Timer().schedule(new TimerTask() {public void run() {(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));}},3000,1000);} }
總結
以上是生活随笔為你收集整理的string是线程安全的么_Java-21 多线程 - 是阿凯啊的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开发使用air还是pro_苹果MacBo
- 下一篇: javacore分析工具_Javacor