string是线程安全的么_Java-21 多线程 - 是阿凯啊
1.多線程概述
進程:一個程序運行,程序在內(nèi)存中分配的那片空間。
線程:進程中一個執(zhí)行單元執(zhí)行路徑
進程中至少有一個線程,如果進程中有多個線程,就是多線程的程序。
并行與并發(fā):
并行:某一時間點,有多個程序同時執(zhí)行,多核CPU運行 并發(fā):某一時間段,有多個程序同時執(zhí)行,并不是真正意義的同時執(zhí)行。 為多線程。并發(fā)真的是同時執(zhí)行嗎?
不是,而是時間間隔很短,造成同時執(zhí)行感覺。多線程優(yōu)勢?
提高了用戶體驗,提高了程序的運行效率,提高CPU使用率。
2.開啟線程兩種方式
開啟線程
/** 1.繼承Thread* 2.重寫run方法* 3.創(chuàng)建子類的對象* 4.調(diào)用start方法* */ public class ThreadDemo1 {public static void main(String[] args) {// 創(chuàng)建子類的對象Demo d1 = new Demo("Jack");Demo d2 = new Demo("Tom");// 設(shè)置線程名字("d1");// 獲取d1執(zhí)行線程名字(());// 獲取當前線程名字(().getName());// 調(diào)用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:開啟新的線程,會自動調(diào)用run方法在新的線程中執(zhí)行 run:沒有開啟新的線程,只是普通方法開啟新線程第二種方式
聲明實現(xiàn)Runnable接口的類,該類然后實現(xiàn)run方法,然后可以分配該類的實例,在創(chuàng)建Thread時做為一個參數(shù)來傳遞并啟動,采用這種風格的同一個例子 /** 實現(xiàn)多線程第二種方式:* 1.實現(xiàn)Runnable* 2.重寫run方法* 3.創(chuàng)建Runnable子類的對象* 4.創(chuàng)建Thread類的對象,把第三步的對象傳到構(gòu)造方法中* 5.使用Thread子類對象,調(diào)用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);}} }兩種實現(xiàn)方式,區(qū)別是?
第一種方式有局限性的,因為Java是單繼承,如果一個類已經(jīng)有一個繼承,它就不能再繼承Thread類,就無法實現(xiàn)多線程。而第二種實現(xiàn)通過接口方式實現(xiàn)更合理,并且第二種方式更加符合面向?qū)ο筇攸c:高內(nèi)聚低耦合,把線程對象和線程任務分離開了。
3.線程中方法
方法使用
public Static void sleep(long millis) 靜態(tài)方法 進入阻塞狀態(tài),時間結(jié)束后進入可執(zhí)行 (3000);sleep方法讓誰阻塞,取決于他在哪個線程中。
方法使用
public final void join() 被誰調(diào)用,讓哪個線程先執(zhí)行,執(zhí)行完畢后,再執(zhí)行所在線程 public class JoinDemo1 {public static void main(String[] args) {Sum s = new Sum();();// join:用誰調(diào)用,就讓那個線程先執(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í)行時候在棧空間,開辟空間給子線程,而他們的之間棧是獨立的,但他們堆空間數(shù)據(jù)是共享的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ā)現(xiàn)多個線程會賣出同一張票。這樣會產(chǎn)生線程安全問題。
線程安全產(chǎn)生原因:1.具備多線程。2.操作共享數(shù)據(jù)。3.操作共享數(shù)據(jù)的代碼有多條。
通過加鎖:讓每一時刻只能有一個線程操作數(shù)據(jù)。方式有三種
1.同步代碼塊
synchronized(鎖對象){容易產(chǎn)生線程安全問題的代碼 }// 鎖對象:可以是任意對象,但是必須保證多個線程使用是同一個對象。 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方法里,則無法實現(xiàn)線程安全,原因是執(zhí)行run方法,實現(xiàn)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;// 構(gòu)造方法私有化,為了不讓別人隨便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是線程安全的內(nèi)部有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í)行的過程中,因爭奪資源產(chǎn)生一種互相等待現(xiàn)象。
7.線程池用法
線程池可以減少創(chuàng)建和銷毀線程的次數(shù),每個工作線程都可以被重復利用,可執(zhí)行多個任務。
可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線程數(shù)目,放置因為消耗過多的內(nèi)存,而把服務器累死(每個線程需要大約1MB內(nèi)存,線程開的越多,消耗的內(nèi)存也就越大,最后死機)。
線程池的創(chuàng)建:
public static ExecutorService newCachedThreadPool()創(chuàng)建一個具有緩存功能的線程池 public static ExecutorService new FixedThreadPool(int nThreads)創(chuàng)建一個可重用的,具有固定線程數(shù)的線程池 public static ExecutorService newSingleThreadExecutor()創(chuàng)建一個只有單線程的線程池,相當于上個方法的參數(shù)是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");}});// 關(guān)閉線程池();// 2pool = (20);} } // 緩存線程池,如果沒有任務會等待60s就關(guān)閉
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();}("等待結(jié)束...");}} }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... 等待結(jié)束... 等待結(jié)束...* */? 注意:
? 一定要在同步代碼塊中執(zhí)行,使用鎖對象調(diào)用。
? 2.要想能喚醒wait,必須使用同一個鎖對象調(diào)用notify/notifyAll
? 方法會釋放鎖對象,進入了等待狀態(tài)以后,允許其他線程進入同步代碼塊執(zhí)行。
? 方法喚醒了以后,wait不是立馬執(zhí)行,等待notify中代碼執(zhí)行完畢。
? 5.而notify方法只能喚醒一個(隨機喚醒),而notifyAll全部都能喚醒
為什么wait,notify,notfiyAll 放到Object類中?
因為他們使用鎖對象調(diào)用,鎖對象可以是任意對象,任意對象都有的方法定義在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();// 如果時間已經(jīng)過期,它會立刻運行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);} }
總結(jié)
以上是生活随笔為你收集整理的string是线程安全的么_Java-21 多线程 - 是阿凯啊的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开发使用air还是pro_苹果MacBo
- 下一篇: javacore分析工具_Javacor