sql筆記
多執行緒
概述
什麼是程序?什麼是執行緒?
答:程序是一個應用程式(相當於軟體)。執行緒是一個程序中的執行場景/執行單元,一個程序可以啟動多個執行緒。
對於java 程式來說當在DOS命令視窗中輸入java HelloWorld
回車之後,會先啟動JVM(程序),JVM再啟動一個主執行緒呼叫main方法,同時再啟動一個垃圾回收執行緒負責看護且回收垃圾。
不同程序間的記憶體獨立不共享;不同執行緒間堆記憶體和方法區記憶體共享,但是棧記憶體相互獨立,一個執行緒一個棧。
問題:使用了多執行緒機制後,main方法結束,有可能程式也不會結束,main方法的主執行緒結束後其他的棧(執行緒)可能還在壓棧彈棧。
對於單核的 cpu 可以做到真正的多執行緒併發嗎?
真正的多執行緒併發:各個執行緒同時工作互不影響。(比如火車站的售票視窗)
單核的 cpu 表示只有一個“大腦”,在某一個時間點上實際只能處理一件事,但 cpu 的處理速度極快,多個執行緒頻繁切換執行給人一種同時做的錯覺,所以單核的 cpu 不能夠做到真正的多執行緒併發,但會給人一種“多執行緒併發”的感覺。
對於多核 cpu 的電腦在同一時間點上,可以真正的有多個程序併發執行。
實現執行緒的方式
java 支援多執行緒機制,並且 java 已經將多執行緒實現了,我們只需要繼承就行。
java 語言中實現執行緒有兩種方式:
1、編寫一個類,直接繼承java.lang.Thread
重寫 run 方法。
public class ThreadTest{ public static void main(String[] args){ //新建一個分支執行緒 MyThread myThread = new MyThread(); //啟動執行緒:啟動一個分支執行緒,在JVM中開闢一個新的棧空間,這段程式碼任務完成之後,瞬間就結束了。只要新的棧空間開出來start()方法就結束了。 myThread.start(); for(int i=0;i<1000;i++){ System.out.println("主執行緒-->"+i); } } } class MyThread extends Thread{ @Overried public void run(){ //這段程式執行在分支執行緒中 for(int i=0;i<1000;i++){ System.out.println("分支執行緒-->"+i); } } }
程式碼分析:
myThread.start();
的任務是開啟一個新的棧空間,只要新的棧空間開出來 start() 方法就結束了。啟動成功的執行緒會自動呼叫 run() 方法,並且 run() 方法在分支棧的棧底部(壓棧),main() 方法在主棧的棧底部, run() 方法和 main() 方法是平級的。
2、編寫一個類,實現java.lang.Runnable
介面,實現 run 方法
public class ThreadTest02{
public static void main(String[] args){
//建立一個可執行物件
MyRunnable r = new MyRunnable();
//將物件封裝成一個執行緒物件
Thread t = new Thread(r);
//合併程式碼
Thread t = new Thread( new MyRunnable());
//啟動執行緒
t.start();
for(int i=0;i<100;i++){
System.out.println("主執行緒-->"+i)
}
}
}
//這是一個可執行的類,並不是一個執行緒類
class MyRunnable implements Runnable{
@Overried
public void run(){
//這段程式執行在分支執行緒中
for(int i=0;i<100;i++){
System.out.println("分支執行緒-->"+i);
}
}
我們通常使用第二種方式建立執行緒,實現 Runnable 介面的同時還可以繼承其他的類;因為 java 只支援單繼承,第一種方式繼承了 Thread 之後無法繼承其他的類。
採用匿名內部類方式建立執行緒:
public class ThreadTest03{
public static void main(String[] args){
//匿名內部類方式建立執行緒
Thread t = new Thread( new MyRunnable(){
@Overried
public void run(){
//這段程式執行在分支執行緒中
for(int i=0;i<100;i++){
System.out.println("分支執行緒-->"+i);
}
});
//啟動執行緒
t.start();
for(int i=0;i<100;i++){
System.out.println("主執行緒-->"+i)
}
}
}
執行緒的生命週期:新建狀態、就緒狀態、執行狀態、阻塞狀態、死亡狀態
獲取當前執行緒物件
1、當執行緒沒有設定名字的時候,預設的名字的規律如下
Thread-0,Thread-1,Thread-2…
2、獲取執行緒物件的名字
String name = 執行緒物件.getName();
3、修改執行緒物件的名字
執行緒物件.setName("執行緒名字");
4、獲取當前執行緒物件
static Thread currentThread();
//返回值t就是當前執行緒
Thread t = Thread.currentThread();
測試:
public class ThreadTest{
public void doSome(){
/*此時this是指doSome(),不是指執行緒
this.getName();
super.getName();*/
String name = Thread.currentThread().getName();
System.out.println("--->"+name);
}
public static void main(String[] args){
ThreadTest tt = new ThreadTest();
tt.doSome();
//currentThread指當前執行緒物件,在main方法中所以當前執行緒就是主執行緒
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()); //main
//建立執行緒物件
MyThread t = new MyThread();
//設定執行緒的名字
t.setName("t1");
//獲取執行緒的名字
String tName = t.getName();
System.out.println(tName);
//啟動執行緒
t.start();
}
}
//執行緒類
class MyThread extends Thread{
public void run(){
for(int i=0;i<100;i++){
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()+"-->"+i);
/*
System.out.println(super.getName()+"-->"+i); //super指MyThread的父類Thread
System.out.println(this.getName()+"-->"+i); //this指MyThread
*/
}
}
}
為什麼獲取當前執行緒一定要使用 Thread currentThread = Thread.currentThread();
?
答: MyThread 類是執行緒類所以可以使用super.getName()和this.getName()
獲取執行緒的名字;但是在 doSome() 方法中因為不是執行緒類就不能使用這兩種方法獲取執行緒的名字,必須使用 Thread currentThread = Thread.currentThread();
執行緒的static void sleep(long millis)
方法:
Thread.sleep(1000);
引數是毫秒,效果是間隔特定的時間去執行一段特定程式碼。
public static void main(String[] args){
//建立執行緒物件
Thread t = new MyThread();
t.start();
//呼叫sleep方法
try{
//問題:這行程式碼會讓執行緒t進入休眠狀態嗎?
t.sleep(1000 * 5);
}catch(InterruptedException e){
e.printStackTrace();
}
//5秒之後才執行
System.out.println("hello world");
}
class MyThread extends Thread{
public void run(){
for(int i=0;i<100;i++){
System.out.println("分支執行緒-->"+i);
}
}
問題: t.sleep(1000 * 5);
這行程式碼會讓執行緒t進入休眠狀態嗎?
答:在執行的時候還是會轉換成Thread.sleep(1000 * 5);
,這行程式碼的作用是讓當前執行緒進入休眠,也就是說main執行緒進入休眠。
終止執行緒的休眠:
讓一個正在休眠(sleep)的執行緒醒來。
public static void main(String[] args){
Thread t = new Thread(new MyRunnable());
t.setName("t");
t.start();
//5秒後,t執行緒醒來
try{
Thread.sleep(1000 * 5);
}catch(InterruptedException e){
e.printStackTrace();
}
//終止t執行緒的睡眠
t.interrupt();
}
class MyRunnable implements Runnable{
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"-->begin");
try{
//睡眠1年
Thread.sleep(1000 * 60 *60 * 24 * 365);
}catch(){
//列印異常資訊
e.printSackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->end");
}
}
問題:為什麼Thread.sleep(1000 * 60 *60 * 24 * 365);
只能用 try/catch 處理異常,而不能用 throws 呢?
答:MyRunnable 的實現介面(父類) Runnable 的 run() 方法中沒有 throws 異常,子類不能丟擲比父類更寬泛的異常,MyRunnable 重寫的 run() 方法所以只能用 try/catch 處理異常。
結論:run() 方法當中的異常不能 throws ,只能用 try/catch 處理異常,因為 run() 方法在父類中沒有丟擲任何異常,子類不能比父類丟擲更多的異常。
t.interrupt();
執行時 MyRunnable 的 run() 方法的Thread.sleep()
方法會拋異常,進入 catch() 捕捉處理異常,即沒有執行Thread.sleep()
方法從而終止 t 執行緒的睡眠。
終止執行緒的執行:
1、強行終止執行緒:
使用執行緒物件.stop();
來強行終止執行緒(殺死執行緒),這個方法以過時不建議使用。
缺點:容易丟失資料,這種方式是直接將執行緒殺死,執行緒中沒有儲存的資料將會丟失。
2、合理的終止一個執行緒:
public static void main(String[] args){
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.setName("t");
t.start();
//主執行緒睡5秒後將t執行緒終止
try{
Thread.sleep(5000);
}catch(InterruptedException e){
e.printStackTrace();
}
//終止執行緒,將run標記修改為false就可以終止執行緒
r.run=false;
}
class MyRunnable implements Runnable{
//做一個標記
boolean run = true;
@Override
public void run(){
for(int i=0;i<10;i++){
if(run){
System.out.println(Thread.currentThread().getName()+"-->"+i);
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
}else{
//在結束之前還有什麼沒儲存的可以在這裡儲存
//終止執行緒
return;
}
}
}
}
執行緒排程
1、搶佔式排程模型:
那個執行緒的優先順序比較高,搶到的 CPU 時間片的概率就高一些,java 採用的就是搶佔式排程模型。
2、均分式排程模型:
平均分配 CPU 時間片,每個執行緒佔有的 CPU 時間片的時間長度一樣。(cpu的時間片多一些是指執行緒處於執行狀態的時間多,而不是搶執行權的能力(誰先執行誰後執行))
MAX_PRIORITY 最高優先順序10
MIN_PRIORITY 最低優先順序1
NORM_PRIORITY 預設優先順序5
例項方法:
void setPriority(int newPriority) 設定執行緒的優先順序
int getPriority() 獲取執行緒優先順序
優先順序比較高的獲取CPU時間片可能會多一些(大概率)。
靜態方法:
static void yield() 暫停當前正在執行的執行緒物件,並執行其他執行緒。該方法不是阻塞方法,而是讓當前執行緒從“執行狀態”回到“就緒狀態”,就緒之後還會再次強cpu時間片。(用法:Thread.yield();)
注意:優先順序高只是執行緒搶到的cpu時間片多(大概率情況下),並不表示該執行緒一定優先執行。
Thread t = new Thread(new MyRunnable());
//設定優先順序
t.setPriority(10);
//設定執行緒名
t.setName("t");
//啟動執行緒
t.start();
//輸出執行緒的優先順序
System.out.println(Thread.currentThread().getName()+"執行緒的優先順序為-->"+Thread.currentThread().getPriority());
3、合併執行緒(把多執行緒變成單執行緒)
void join()
合併執行緒(例項方法)
class MyThread1 extends Thread{
public void doSome(){
MyThrea2 t = new MyThread2();
t.join();
//當前執行緒(執行緒類MyThread1的物件)進入阻塞狀態,t執行緒(MyThread2的物件)執行,直到t執行緒結束當前執行緒才繼續執行
}
}
public static void main(String[] args){
System.out.println("main begin");
Thread t = new Thread(new MyRunnable());
t.setName("t");
t.start();
//合併執行緒
try{
t.join();//執行緒t合併到當前執行緒中,當前執行緒受阻塞,執行緒t執行直到結束
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("main over");
}
class MyRunnable implements Runnable{
@Override
public void run(){
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
結果:main begin …… main over
執行緒安全
1、多執行緒併發環境下資料的安全問題
在開發中,我們的專案執行在伺服器中,而伺服器已經將執行緒的定義、執行緒物件的建立、執行緒的啟動等實現了,不再需要我們編寫。
重要的是,我們必須關注自己編寫的程式和資料在多執行緒併發的環境下是否安全。
2、什麼時候資料在多執行緒併發的環境下會存在安全問題?
三個條件:多執行緒併發、有共享資料、共享資料有修改的行為。
上圖中,銀行賬戶存款為一萬,兩個人同時取錢,因為賬戶餘額更新延遲,第一個人取出一萬後餘額沒有及時更新,第二個人也能取出一萬。
3、解決執行緒安全問題
執行緒同步機制:執行緒排隊執行,用排隊執行解決執行緒安全問題。(執行緒排隊執行使資料安全了,但會犧牲一部分效率)
執行緒非同步機制:執行緒各自執行,其實就是多執行緒併發,效率較高。