多執行緒背景知識介紹
利用多執行緒可以簡化模型,寫功能強大的程式碼,但是要寫好多執行緒卻不容易,需要一個長期實踐的過程。
① 進程:程式(任務)的執行過程。動態性
持有資源(共享內存,共享檔案)和線程。載體
範例:Eclipse、QQ
② 執行緒:
Eclipse:原始碼文字編輯、原始碼編譯、語法校驗。
QQ:文字聊天、收發檔案。
如果把行程比喻成一個班級,那麼這個班級中的每一個學生就是線程。學生是班級當中的最小單元,構成班級當中的最小單位。一個班級可以有多個學生,這些學生都使用班級當中共同的桌椅、黑板、粉筆。
線程是系統中最小的執行單元,同一個進程中有多個線程,線程共享進程的資源。
互斥、同步。
Java.lang
class Thread
interface Runnable
public void run ()
#方法簽章 |
簡介 |
|
#執行緒的建立 |
Thread() |
|
#Thread(String name) |
|
|
#Thread(Runnable target) |
# |
|
## Thread(Runnable target,String name) | ||
執行緒的方法 | ||
void start() |
啟動執行緒 |
|
static void sleep(long millis) | ||
void join() |
||
void join( long millis) |
void join(long millis,int nanos) |
兩個執行緒不做任何處理的時候,會交替運行。
當使用boolean類型控制執行緒的循環時,要在變數前面加上volatile關鍵字,volatile保證了執行緒可以正確的讀取其他執行緒寫入的值。
注意點:
sleep()方法的作用:使執行緒休眠指定的時間。
join()方法的作用: //使其他執行緒等待目前執行緒執行完畢。
範例1:
1 package com.czgo; 2 3 4 5 /** 6 7 * 线程先生 8 9 * @author 疯子 10 11 * 12 13 */ 14 15 public class Actor extends Thread { 16 17 @Override 18 19 public void run() { 20 21 //getName():获取当前线程的名称 22 23 System.out.println(getName()+"是一个演员!"); 24 25 //用来记录线程跑的次数 26 27 int count = 0; 28 29 boolean keepRunning = true; 30 31 while(keepRunning){ 32 33 System.out.println(getName()+"登台演出"+(++count)); 34 35 if(count==100){ 36 37 keepRunning = false; 38 39 } 40 41 if(count%10==0){ 42 43 try { 44 45 Thread.sleep(1000); 46 47 } catch (InterruptedException e) { 48 49 e.printStackTrace(); 50 51 } 52 53 } 54 55 } 56 57 System.out.println(getName()+"的演出结束了!"); 58 59 } 60 61 62 63 public static void main(String[] args) { 64 65 Thread actor = new Actor(); 66 67 //setName:设置线程的名称 68 69 actor.setName("Mr.Thread"); 70 71 //启动线程 72 73 actor.start(); 74 75 76 77 Thread actressThread = new Thread(new Actress(),"Ms.Runnable"); 78 79 actressThread.start(); 80 81 } 82 83 } 84 85 86 87 class Actress implements Runnable{ 88 89 @Override 90 91 public void run() { 92 93 //getName():获取当前线程的名称 94 95 //currentThread()获取当前线程的引用 96 97 System.out.println(Thread.currentThread().getName()+"是一个演员!"); 98 99 //用来记录线程跑的次数 100 101 int count = 0; 102 103 boolean keepRunning = true; 104 105 while(keepRunning){ 106 107 System.out.println(Thread.currentThread().getName()+"登台演出"+(++count)); 108 109 if(count==100){ 110 111 keepRunning = false; 112 113 } 114 115 if(count%10==0){ 116 117 try { 118 119 Thread.sleep(1000); 120 121 } catch (InterruptedException e) { 122 123 e.printStackTrace(); 124 125 } 126 127 } 128 129 } 130 131 System.out.println(Thread.currentThread().getName()+"的演出结束了!"); 132 133 }134 135 }
範例2:
軍隊:
1 package com.czgo; 2 3 4 5 /** 6 7 * 军队线程 8 9 * 模拟作战双方的行为 10 11 * @author 疯子 12 13 * 14 15 */ 16 17 public class ArmyRunnable implements Runnable { 18 19 20 21 //volatile保证了线程可以正确的读取其他线程写入的值 22 23 //可见性 ref JMM,happens-before 24 25 volatile boolean keepRunning = true; 26 27 28 29 @Override 30 31 public void run() { 32 33 34 35 while(keepRunning){ 36 37 //发动5连击 38 39 for(int i=0;i<5;i++){ 40 41 System.out.println(Thread.currentThread().getName()+"进攻对方["+i+"]"); 42 43 //让出了处理器时间,下次谁进攻还不一定呢! 44 45 Thread.yield(); 46 47 } 48 49 } 50 51 52 53 System.out.println(Thread.currentThread().getName()+"结束了战斗!"); 54 55 56 57 } 58 59 }
關鍵人物:
1 package com.czgo; 2 3 4 5 /** 6 7 * 关键人物 8 9 * @author 疯子 10 11 * 12 13 */ 14 15 public class KeyPersonThread extends Thread { 16 17 @Override 18 19 public void run() { 20 21 System.out.println(Thread.currentThread().getName()+"开始了战斗!"); 22 23 for(int i=0;i<10;i++){ 24 25 System.out.println(Thread.currentThread().getName()+"左突右杀,攻击随军..."); 26 27 } 28 29 System.out.println(Thread.currentThread().getName()+"结束了战斗!"); 30 31 32 33 } 34 35 }
舞台:
1 package com.czgo; 2 3 4 5 /** 6 7 * 隋唐演义大戏舞台 8 9 * @author win7 10 11 * 12 13 */ 14 15 public class Stage extends Thread { 16 17 18 19 @Override 20 21 public void run() { 22 23 System.out.println("欢迎观看隋唐演义"); 24 25 26 27 try { 28 29 Thread.sleep(5000); 30 31 } catch (InterruptedException e2) { 32 33 e2.printStackTrace(); 34 35 } 36 37 38 39 System.out.println("大幕徐徐拉开"); 40 41 42 43 try { 44 45 Thread.sleep(5000); 46 47 } catch (InterruptedException e2) { 48 49 e2.printStackTrace(); 50 51 } 52 53 54 55 System.out.println("话说隋朝末年,隋军与农民起义军杀得昏天暗地..."); 56 57 58 59 //隋朝军队 60 61 ArmyRunnable armyTaskOfSuiDynasty = new ArmyRunnable(); 62 63 //农民起义军 64 65 ArmyRunnable armyTaskOfRevolt = new ArmyRunnable(); 66 67 68 69 //使用Runnable接口创建线程 70 71 Thread armyOfSuiDynasty = new Thread(armyTaskOfSuiDynasty,"隋军"); 72 73 Thread armyOfSuiRevolt = new Thread(armyTaskOfRevolt,"农民起义军"); 74 75 76 77 //启动线程,让军队开始作战 78 79 armyOfSuiDynasty.start(); 80 81 armyOfSuiRevolt.start(); 82 83 84 85 //舞台线程休眠,大家专心观看军队的厮杀 86 87 try { 88 89 //Thread会指向当前类的线程 90 91 Thread.sleep(50); 92 93 } catch (InterruptedException e) { 94 95 e.printStackTrace(); 96 97 } 98 99 System.out.println("正当双方激战正酣,半路杀出了个程咬金"); 100 101 102 103 Thread mrCheng = new KeyPersonThread(); 104 105 mrCheng.setName("程咬金"); 106 107 108 109 System.out.println("程咬金的理想就是结束战争,使百姓安居乐业!"); 110 111 112 113 //停止军队作战 114 115 //停止线程的方法 116 117 armyTaskOfSuiDynasty.keepRunning=false; 118 119 armyTaskOfRevolt.keepRunning=false; 120 121 122 123 try { 124 125 Thread.sleep(2000); 126 127 } catch (InterruptedException e1) { 128 129 e1.printStackTrace(); 130 131 } 132 133 134 135 //历史大戏留给关键人物 136 137 mrCheng.start(); 138 139 140 141 try { 142 143 //使其他线程等待当前线程执行完毕 144 145 mrCheng.join(); 146 147 } catch (InterruptedException e) { 148 149 e.printStackTrace();150 151 } 152 153 154 155 System.out.println("战争结束,人民安居乐业,程先生实现了积极的人生梦想,为人民作出了贡献!"); 156 157 System.out.println("谢谢观看隋唐演义,再见!"); 158 159 } 160 161 162 163 public static void main(String[] args) { 164 165 new Stage().start(); 166 167 } 168 169 }
#o
##多線程編程常用的交互模型 Producer-Consumer模型 Read-Write Lock模型 Future模型 Worker Thread模型 Java5中並發程式設計工具 Java.util.concurrent 執行緒池ExecutorService#
#推薦兩本書: Core JavaJava concurrency in practice#執行緒所建立的兩種方式比較
Thread: #① 繼承Thread類別;Runnable:① Runnable方式可避免Thread方式因Java單一繼承特性所帶來的缺陷。 ② Runnable的程式碼可以被多個執行緒(Thread實例)共享,適合於多個執行緒處理相同資源的情況。1 package com.czgo; 2 3 4 5 class MyThread extends Thread{ 6 7 8 9 private int ticketsCont = 5; //一共有5张火车票 10 11 12 13 private String name; //窗口,也即是线程的名字 14 15 16 17 public MyThread(String name){ 18 19 this.name = name; 20 21 } 22 23 24 25 @Override 26 27 public void run() { 28 29 30 31 while(ticketsCont>0){ 32 33 ticketsCont--; //如果还有票,就卖掉一张 34 35 System.out.println(name+"卖了1张票,剩余票数为:"+ticketsCont); 36 37 } 38 39 } 40 41 42 43 } 44 45 46 47 public class TicketsThread { 48 49 50 51 public static void main(String[] args) { 52 53 //创建3个线程,模拟三个窗口卖票 54 55 MyThread mt1 = new MyThread("窗口1"); 56 57 MyThread mt2 = new MyThread("窗口2"); 58 59 MyThread mt3 = new MyThread("窗口3"); 60 61 62 63 //启动这三个线程,也即是窗口,开始卖票 64 65 mt1.start(); 66 67 mt2.start(); 68 69 mt3.start(); 70 71 72 73 } 74 75 76 77 }Runnable:
1 package com.czgo; 2 3 4 5 class MyThread implements Runnable{ 6 7 8 9 private int ticketsCont = 5; //一共有5张火车票 10 11 12 13 @Override 14 15 public void run() { 16 17 18 19 while(ticketsCont>0){ 20 21 ticketsCont--; //如果还有票,就卖掉一张 22 23 System.out.println(Thread.currentThread().getName()+"卖了1张票,剩余票数为:"+ticketsCont); 24 25 } 26 27 28 29 } 30 31 32 33 } 34 35 36 37 public class TicketsRunnable { 38 39 40 41 public static void main(String[] args) { 42 43 44 45 MyThread mt1 = new MyThread(); 46 47 MyThread mt2 = new MyThread(); 48 49 MyThread mt3 = new MyThread(); 50 51 52 53 //创建三个线程来模拟三个售票窗口 54 55 Thread th1 = new Thread(mt1,"窗口1"); 56 57 Thread th2 = new Thread(mt2,"窗口2"); 58 59 Thread th3 = new Thread(mt3,"窗口3"); 60 61 62 63 //启动这三个线程,也即是三个窗口开始卖票 64 65 th1.start(); 66 67 th2.start(); 68 69 th3.start(); 70 71 72 73 } 74 75 76 77 }
執行緒的生命週期
##圖示:
建立:新建一個執行緒對象,如Thread thd = new Thread()。 就緒:創建了線程物件後,呼叫了線程的start()方法(注意:此時線程只是進入了線程隊列,等待獲取cpu服務,具備了運行的條件,但不一定已經開始運行了)。 運行:處於就緒狀態的線程,一旦取得了CPU資源,便進入到運行狀態,開始執行run()方法裡面的邏輯。
用戶執行緒:運行在前台,執行具體的任務。
例如程式的主執行緒、連接網路的子執行緒等都是使用者執行緒。
可以通过调用Thread类的setDaemon(true)方法来设置当前线程为守护线程。
注意事项:
setDaemon(true)必须在start()方法之前调用,否则会抛出IllegalThreadStateException异常。
在守护线程中产生的新线程也是守护线程
不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑。
案例:
1 package com.czgo; 2 3 4 5 import java.io.File; 6 7 import java.io.FileOutputStream; 8 9 import java.io.IOException; 10 11 import java.io.OutputStream; 12 13 import java.util.Scanner; 14 15 16 17 class DaemonThread implements Runnable{ 18 19 @Override 20 21 public void run() { 22 23 System.out.println("进入守护线程"+Thread.currentThread().getName()); 24 25 try { 26 27 writeToFile(); 28 29 } catch (IOException e) { 30 31 e.printStackTrace(); 32 33 } catch (InterruptedException e) { 34 35 e.printStackTrace(); 36 37 } 38 39 System.out.println("退出守护线程"+Thread.currentThread().getName()); 40 41 42 43 } 44 45 46 47 private void writeToFile() throws IOException, InterruptedException{ 48 49 File filename = new File("C:\\ide"+File.separator+"daemon.txt"); 50 51 OutputStream os = new FileOutputStream(filename,true); 52 53 int count = 0; 54 55 while(count<999){ 56 57 os.write(("\r\nword"+count).getBytes()); 58 59 System.out.println("守护线程"+Thread.currentThread().getName()+"向文件中写入了word"+count++); 60 61 Thread.sleep(1000); 62 63 } 64 65 os.close(); 66 67 } 68 69 } 70 71 72 73 public class DaemonThreadDemo { 74 75 76 77 public static void main(String[] args) { 78 79 80 81 System.out.println("进入主线程"+Thread.currentThread().getName()); 82 83 DaemonThread daemonThread = new DaemonThread(); 84 85 Thread thread = new Thread(daemonThread); 86 87 thread.setDaemon(true); 88 89 thread.start(); 90 91 92 93 Scanner sc = new Scanner(System.in); 94 95 sc.next(); 96 97 98 99 System.out.println("退出主线程"+Thread.currentThread().getName()); 100 101 } 102 103 104 105 }
作用:生成jvm当前时刻线程的快照(threaddump,即当前进程中所有线程的信息)
目的:帮助定位程序问题出现的原因,如长时间停顿、cpu占用率过高等。
jstack –l pid
可见性:一个线程对共享变量值的修改,能够及时地被其他线程看到。
共享变量:如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
Java内存模型(Java Memory Model)描述了程序中各种变量(线程共享变量)的访问规则,以及在Java中将变量存储到内存和从内存中读取出变量这样的底层细节。
所有变量都存储在主内存中
每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)。
线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接从主内存中读写。
不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
线程1对共享变量的修改要想被线程2及时看到,必须要经过如下2个步骤:
把工作内存1中更新过的共享变量刷新到主内存中。
将主内存中最新的共享变量的值更新到工作内存2中。
要实现共享变量的可见性,必须保证两点:
线程修改后的共享变量值能够及时从工作内存刷新到主内存中。
其他线程能够及时把共享变量的最新值从主内存更新到自己的工作内存中。
Java语言层面支持的可见性实现方式:
Synchronized
Volatile
原子性(同步);
可见性
线程解锁前,必须把共享变量的最新值刷新到主内存中;
线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁需要是同一把锁)
线程解锁前对共享变量得修改在下次加锁时对其他线程可见。
获得互斥锁
清空工作内存
从主内存中拷贝变量的最新副本到工作内存
执行代码。
将更改后的共享变量的值刷新到主内存。
释放互斥锁。
重排序:代码书写的顺序与实际执行的顺序不同,指令重排序是编译器或处理器为了提高程序性能而做的优化。
编译器优化的重排序(编译器优化)。
指令级并行重排序(处理器优化)。
内存系统的重排序(处理器优化)。
as-if-serial:无论如何重排序,程序执行的结果应该与代码顺序执行的结果一致(java编译器、运行时和处理器都会保证Java在单线程下遵循as-if-serial语义)。
例子:
Int num = 1;
Int num2 = 2;
Int sum = num+num2;
单线程:第1、2行的顺序可以重排,但第3行不能
重排序不会给单线程带来内存可见性问题
多线程中程序交错执行时,重排序可能会造成内存可见性问题。
Volatile关键字:
能够保证volatile变量的可见性
不能保证volatile变量复合操作的原子性
Volatile如何实现内存可见性:
深入來說:透過加入記憶體屏障和禁止重排序優化來實現的。
對volatile變數執行寫入作業時,會在寫入作業後加入一個store屏障指令
#對volatile變數執行讀取操作時,會在讀取操作前加入load屏障指令
通俗的講:volatile變數在每次被執行緒存取時,都強迫從主記憶體重讀該變數的值,而當該變數發生變化時,又會強迫執行緒將最新的值刷新到主記憶體。這樣任何時刻,不同執行緒總是能看到該變數的最新值。
線程寫volatile變數的過程:
改變線程工作記憶體中volatile變數副本的值
以上是Java多執行緒詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!