string是线程安全的么_Java-21 多线程 - 是阿凯啊
1.多線程概述
- 進程:一個程序運行,程序在內存中分配的那片空間。 
- 線程:進程中一個執(zhí)行單元執(zhí)行路徑 - 進程中至少有一個線程,如果進程中有多個線程,就是多線程的程序。 
- 并行與并發(fā): 并行:某一時間點,有多個程序同時執(zhí)行,多核CPU運行 并發(fā):某一時間段,有多個程序同時執(zhí)行,并不是真正意義的同時執(zhí)行。 為多線程。
- 并發(fā)真的是同時執(zhí)行嗎? 不是,而是時間間隔很短,造成同時執(zhí)行感覺。
- 多線程優(yōu)勢? 提高了用戶體驗,提高了程序的運行效率,提高CPU使用率。
2.開啟線程兩種方式
- 開啟線程 /** 1.繼承Thread* 2.重寫run方法* 3.創(chuàng)建子類的對象* 4.調用start方法* */ public class ThreadDemo1 {public static void main(String[] args) {// 創(chuàng)建子類的對象Demo d1 = new Demo("Jack");Demo d2 = new Demo("Tom");// 設置線程名字("d1");// 獲取d1執(zhí)行線程名字(());// 獲取當前線程名字(().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方法區(qū)別 start:開啟新的線程,會自動調用run方法在新的線程中執(zhí)行 run:沒有開啟新的線程,只是普通方法
- 開啟新線程第二種方式 聲明實現Runnable接口的類,該類然后實現run方法,然后可以分配該類的實例,在創(chuàng)建Thread時做為一個參數來傳遞并啟動,采用這種風格的同一個例子 /** 實現多線程第二種方式:* 1.實現Runnable* 2.重寫run方法* 3.創(chuàng)建Runnable子類的對象* 4.創(chuàng)建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);}} }
- 兩種實現方式,區(qū)別是? 第一種方式有局限性的,因為Java是單繼承,如果一個類已經有一個繼承,它就不能再繼承Thread類,就無法實現多線程。而第二種實現通過接口方式實現更合理,并且第二種方式更加符合面向對象特點:高內聚低耦合,把線程對象和線程任務分離開了。
3.線程中方法
方法使用
public Static void sleep(long millis) 靜態(tài)方法 進入阻塞狀態(tài),時間結束后進入可執(zhí)行 (3000);sleep方法讓誰阻塞,取決于他在哪個線程中。
方法使用
public final void join() 被誰調用,讓哪個線程先執(zhí)行,執(zhí)行完畢后,再執(zhí)行所在線程 public class JoinDemo1 {public static void main(String[] args) {Sum s = new Sum();();// join:用誰調用,就讓那個線程先執(zhí)行,執(zhí)行完畢后,再執(zhí)行他所在線程(將他所在線程阻塞)。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() 讓其他線程先執(zhí)行,不一定生效,因為讓誰執(zhí)行是CPU決定的方法
停止一個線程方法
打斷線程的阻塞狀態(tài),進入可執(zhí)行狀態(tài),會拋出異常 public class interruptDemo {public static void main(String[] args) {Demo3 d = new Demo3();();// 將d執(zhí)行線程阻塞狀態(tài)打斷。();("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() {();} }- 練習:創(chuàng)建兩個線程,一個線程負責打印大寫字母表,一個線程負責打印小寫字母表
4.線程生命的周期
主線程執(zhí)行時候在棧空間,開辟空間給子線程,而他們的之間棧是獨立的,但他們堆空間數據是共享的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;}}} }- 在執(zhí)行代碼時候,會發(fā)現多個線程會賣出同一張票。這樣會產生線程安全問題。 
- 線程安全產生原因: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方法里,則無法實現線程安全,原因是執(zhí)行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鎖的范圍太大,第一個進來線程,執(zhí)行完整個while循環(huán)。導致在使用同步方法時候也需要注意這個問題。
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個線程,當一個線程執(zhí)行到s=new Singleton2();此時剛要new Singleton2,cpu切到第二個線程,因為s此時還是等于null,第二個線程也執(zhí)行new Singleton2() 導出單例模式創(chuàng)建多個對象。此時通過同步方法添加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.死鎖
- 是指兩個或兩個以上的線程在執(zhí)行的過程中,因爭奪資源產生一種互相等待現象。
7.線程池用法
- 線程池可以減少創(chuàng)建和銷毀線程的次數,每個工作線程都可以被重復利用,可執(zhí)行多個任務。 
- 可以根據系統(tǒng)的承受能力,調整線程池中工作線程數目,放置因為消耗過多的內存,而把服務器累死(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最后死機)。 
- 線程池的創(chuàng)建: public static ExecutorService newCachedThreadPool()創(chuàng)建一個具有緩存功能的線程池 public static ExecutorService new FixedThreadPool(int nThreads)創(chuàng)建一個可重用的,具有固定線程數的線程池 public static ExecutorService newSingleThreadExecutor()創(chuàng)建一個只有單線程的線程池,相當于上個方法的參數是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以后,被喚醒的線程并不是立馬執(zhí)行,需要等到notify,notifyAll所在代碼塊執(zhí)行完畢后才會執(zhí)行。因為只有同步代碼塊執(zhí)行完畢后,才會釋放鎖對象,其他線程才可以進來。
? wait方法會釋放鎖對象,也就是一個線程使用wait進入等待狀態(tài)后,允許其他線程進入同步代碼塊。而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個線程都執(zhí)行到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... 等待結束... 等待結束...* */? 注意:
? 一定要在同步代碼塊中執(zhí)行,使用鎖對象調用。
? 2.要想能喚醒wait,必須使用同一個鎖對象調用notify/notifyAll
? 方法會釋放鎖對象,進入了等待狀態(tài)以后,允許其他線程進入同步代碼塊執(zhí)行。
? 方法喚醒了以后,wait不是立馬執(zhí)行,等待notify中代碼執(zhí)行完畢。
? 5.而notify方法只能喚醒一個(隨機喚醒),而notifyAll全部都能喚醒
- 為什么wait,notify,notfiyAll 放到Object類中? 因為他們使用鎖對象調用,鎖對象可以是任意對象,任意對象都有的方法定義在Object
9.定時器
- 方法 public void schedule(TimerTask task, long delay) 延遲多少毫秒后執(zhí)行定時任務 public void schedule(TimerTask task, Date date) 指定時間執(zhí)行定時任務 public void schedule(TimerTask task, long delay, long period) 延遲執(zhí)行,指定間隔后循環(huán)執(zhí)行 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毫秒執(zhí)行一次任務,同一個定時任務只能執(zhí)行一次// 它是多線程啟動的(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秒后執(zhí)行,每隔1秒執(zhí)行一次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
- 下一篇: 家庭接入: dsl 电缆ftth 拨号和
