マルチスレッドの背景知識の紹介
マルチスレッドを使用するとモデルを簡素化し、強力なコードを作成できますが、マルチスレッドの作成は簡単ではなく、長期的な練習プロセスが必要です。
① プロセス:プログラム(タスク)の実行プロセス。ダイナミクス
はリソース (共有メモリ、共有ファイル) とスレッドを保持します。キャリア
例: Eclipse、QQ
② スレッド:
Eclipse: ソースコードのテキスト編集、ソースコードのコンパイル、構文検証。
QQ: テキストチャット、ファイルの送受信。
プロセスをクラスに例えると、このクラスの生徒全員がスレッドになります。生徒はクラスの最小単位であり、クラスの最小単位を構成します。クラスには複数の生徒が所属でき、これらの生徒は全員、クラス内で同じテーブル、椅子、黒板、チョークを使用します。
スレッドはシステム内の最小の実行単位であり、同じプロセス内に複数のスレッドがあり、スレッドはプロセスのリソースを共有します。
相互排他と同期。
Java.lang
クラスThread
インターフェースRunnable
public void run()
カテゴリ |
メソッドのシグネチャ |
はじめに |
スレッドの作成 |
Thread() |
|
Thread(文字列名) |
||
スレッド(実行可能なターゲット) ) |
||
Thread(実行可能なターゲット,文字列名) |
||
スレッドメソッド |
void start() |
スレッドを開始 |
static void sleep(long millis) |
thread sleep |
|
static void sleep(long millis, int nanos) | ||
void join() |
は、他のスレッドを待機させます。現在のスレッドを終了 |
|
void join(long millis) | ||
void join(long millis, int nanos) | ||
static void yield() |
現在実行中のスレッドリリースプロセッサリソース |
|
スレッドリファレンスを取得 |
static Thread currentThread() |
現在実行中のスレッドリファレンスを返す |
2 つのスレッドが処理を行っていないときは、交互に実行されます。
ブール型を使用してスレッドのループを制御する場合、変数の前に 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 }
正しい停止方法Java スレッド:
ブール型を使用してループの終了を制御できます。
not stopメソッド
stop()メソッドはスレッドを突然停止します。
stop() メソッドはスレッドを停止する間違った方法です。
複数のスレッドが同じデータ(メモリ領域)に同時にアクセスすると、それぞれのスレッドがデータを操作しようとし、データが破損(破損)してしまいます。 、この現象は競合状態と呼ばれます。
には 1 つのスレッドのみがアクセスでき、相互に排他的です。
相互排他実装: 同期 (固有ロック) ロック。
同期の実装: wait()/notify()/notifyAll()。 V Java 同時実行の知識を拡張する方法
マルチスレッドプログラミングで一般的に使用される対話モデル プロデューサー-コンシューマーモデル 読み取り/書き込みロックモデル --アウト アウト スルー ダウン スルー ‐ ‐ ‐ ‐ ‐ ‐ ‐ ‐ サービスへ呼び出し可能 &将来 Core Java実際の Java 同時実行
スレッド作成の 2 つの方法の比較
① Thread クラスを継承する;
Runnable:
① Runnable メソッドは次のことを回避できます。 Java の単一継承機能による Thread メソッドが不具合を引き起こしました。
② 実行可能なコードは複数のスレッド(スレッドインスタンス)で共有できるため、複数のスレッドが同じリソースを処理する状況に適しています。
Case:
Thread:
rreeerunnable:
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 }
illustration:
creation:thd = new Thread()などの新しいスレッドオブジェクトを作成します。
Ready: スレッド オブジェクトの作成後、スレッドの start() メソッドが呼び出されます (注: この時点では、スレッドはスレッド キューに入っているだけで、CPU サービスの取得を待機しており、実行する条件は整っていますが、必ずしも実行を開始しているわけではありません)。
実行中: 準備完了状態のスレッドが CPU リソースを取得すると、実行状態になり、run() メソッドのロジックの実行を開始します。
終了: スレッドの run() メソッドが実行されるか、スレッドが stop() メソッドを呼び出した後、スレッドは終了状態に入ります。
ブロッキング: 特定の状況下では、実行中のスレッドが何らかの理由で一時的に CPU リソースを放棄し、自身の実行を一時停止し、sleep() メソッドの呼び出しなどのブロッキング状態に入ります。
スレッドの守護聖人 - デーモン スレッド
ユーザー スレッド: フォアグラウンドで実行され、特定のタスクを実行します。
例えば、プログラムのメインスレッド、ネットワークに接続されたサブスレッドなどはすべてユーザースレッドです。 デーモンスレッド: バックグラウンドで実行され、他のフォアグラウンドスレッドを提供します。 機能: すべてのユーザー スレッドの実行が完了すると、デーモン スレッドは JVM とともに作業を終了します。 アプリケーション: データベース接続プール内のスレッドの監視、JVM 仮想マシンの起動後のスレッドの監視。 最も一般的なデーモン スレッド: ガベージ コレクション スレッド。可以通过调用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 変数を読み取るスレッド プロセス:
volatile 変数の最新の値をメインメモリからスレッドの作業メモリに読み取ります
作業メモリから volatile 変数のコピーを読み取ります。
Volatile が適用される場合
マルチスレッドで volatile 変数を安全に使用するには、以下も満たしている必要があります:
1. 変数への書き込み操作は、現在の値に依存しません
2。 温度を記録するために使用されるブール値
3。この変数は他の変数の不変条件には含まれません。
結論: 他人があなたについて陰でどう思っているかは気にしないでください。これらの言葉は事実を変えることはできませんが、あなたの心を混乱させる可能性があるからです。
以上がJavaマルチスレッドの詳しい説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。