Java多執行緒就是這麼簡單
多執行緒
關於多執行緒有關的概念:
- 程式:程式指正在執行的程式,並且具有一定獨立功能
- 執行緒:執行緒是程式中的一個執行單位,負責當前程式程式的執行,一個程式中至少會有一個執行緒,如果一個程式中包含多個執行緒,那麼可稱為多執行緒程式
- 單執行緒:當要執行多個任務時,cpu只會依次執行,當一個任務執行完後,再去執行另外一個任務
- 多執行緒:多個任務可以同時進行
在Java中,不同執行緒會有不同的優先順序搶佔cpu,如果執行緒優先順序相同,就會隨機先去一個執行緒去執行
Java程式執行時會預設執行3個程式:
- main主執行緒
- gc垃圾回收機制
- 異常處理機制
我們如何能夠判斷程式是否是多執行緒?
如果我們能夠將程式的執行用一條直線畫出來,就說明是單執行緒
關於執行緒的常用API方法
-
run():該方法需要被重寫,重寫的內容就是需要執行的操作
-
start():呼叫該方法就會啟動相應的執行緒,並呼叫當前執行緒的run方法
-
sleep(long millitime):將當前執行緒進入阻塞狀態(不會釋放鎖,即同步監視器)
-
join():當a執行緒呼叫b執行緒的join方法時,a執行緒會進入阻塞狀態,直到b執行緒的任務執行完畢
-
isAlive():判斷當前執行緒是否存活
-
yield():呼叫該方法後回釋放當前執行緒的cpu執行權,當時並不代表不會再次執行,有可能釋放後,又是該執行緒搶佔到了cpu的執行權
-
currentThread():Thread類中的靜態方法,會返回執行當前程式的執行緒
-
getName():返回當前執行緒的名字
-
setName():設定執行緒的名字
-
getPriority():設定執行緒的優先順序(
MAX_PRIORITY=10
MIN_PRIORITY=1
NORM_PRIORITY=5 預設優先順序
-
wait():將執行緒進入阻塞狀態(會釋放掉鎖),只能在同步程式碼塊或同步方法中使用
-
notify():將另外一個執行緒喚醒
-
notifyAll():喚醒所有被阻塞的執行緒
執行緒建立的4種方式
一:繼承Thread類
- 首先建立一個類去繼承Thread
- 重寫Thread中的run方法
- main()中建立該物件
- 呼叫該物件的start方法,啟動執行緒
public class test {
public static void main(String[] args) {
MyThread1 myThread1=new MyThread1();
myThread1.start();
}
}
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("繼承了Thread");
}
}
二:實現Runnable介面
- 建立一個類實現Runnable介面
- 實現介面的run方法
- 在main()中建立實現Runnable的物件
- 建立Thread物件,並把剛建立好的類傳參
- 呼叫Thread物件的start方法,啟動執行緒
public class test {
public static void main(String[] args) {
MyThread1 myThread1=new MyThread1();
Thread t=new Thread(myThread1);
t.start();
}
}
class MyThread1 implements Runnable{
@Override
public void run() {
System.out.println("實現了Runnable");
}
}
三:實現Callable介面
- 建立一個類實現Callable介面
- 實現介面的call()方法
- 在main中建立實現Callable的物件
- 建立FutureTask物件並把上面建立的物件傳參
- 建立Thread物件,傳入FutureTask物件
- 呼叫Thread的start方法,啟動執行緒
public class test {
public static void main(String[] args) {
MyThread2 myThread2=new MyThread2();
FutureTask futureTask=new FutureTask(myThread2);
Thread t=new Thread(futureTask);
t.start();
}
}
class MyThread2 implements Callable{
@Override
public Object call() throws Exception {
System.out.println("實現了Callable");
return null;
}
}
此方式如果需要得到返回值需要呼叫futureTask.get();
但是會拋異常,用try,catch方法捕捉一下就好了
四:執行緒池
- 建立ExecutorService物件
- 傳入相應的執行緒物件
- 結束執行緒池
public class test {
public static void main(String[] args) {
//建立執行緒池,設定執行緒池執行緒的數量為10
ExecutorService service = Executors.newFixedThreadPool(10);
//execute適用於實現了Runnable的物件
service.execute(new MyThread1());
//submit適用於實現了Callable的物件
service.submit(new MyThread2());
//結束執行緒池
service.shutdown();
}
}
class MyThread1 implements Runnable{
@Override
public void run() {
System.out.println("實現了Runnable");
}
}
class MyThread2 implements Callable{
@Override
public Object call() throws Exception {
System.out.println("實現了Callable");
return null;
}
}
Runnable和Callable的對比:
- Callable可以有返回值,執行完相應操作後可以返回需要的結果
- 可以拋異常,可以將call方法中的異常丟擲
- 支援泛型,可以指定返回值型別
執行緒安全
什麼是執行緒安全問題呢?
當多個執行緒對同一個共享資料操作時,執行緒執行還沒來得及更新處理共享的資料,從而使得其他操作的執行緒並未得到最新的資料,從而產生問題
舉個例子:
- 當甲乙兩人向同一賬戶存錢,讓甲乙兩個執行緒同時存錢,如果甲向賬戶存了1000元,並列印此時餘額,應為1000元,但是如果此時乙也存了1000元,就會導致,顯示餘額為2000元,並不是甲當時的餘額
- 還有就是火車售票問題,如果多個視窗同時售票,如果1號視窗正在賣001號票時,此時還未處理完成,這是2號視窗也賣了001號票,這就導致產生了兩個001號票
那麼如何解決呢?
有三種方式:
方法一:同步程式碼塊
synchronized(Object obj)
{
//操作內容
}
- synchronized():傳入的可以是任意類的物件,但必須是多個執行緒共用的,一般可以利用this,即當前物件(Runnable),Thread不太行,因為繼承多個Thread類會導致this物件不一致
- 被包住的程式碼執行為單執行緒,當一個執行緒執行完後,另外一個執行緒才有可能會分配到執行權去執行
- 多個執行緒必須共用同一把鎖,這樣才能夠判斷一個執行緒是夠執行
- Runnable一般很實用,因為多個執行緒都呼叫同一個類的方法,但是Thread就需要自己定義靜態變數或者當前的唯一類即(windows.class)
public class test {
public static void main(String[] args) {
Window t1 = new Window("視窗1");
Window t2 = new Window("視窗2");
Window t3 = new Window("視窗3");
t1.start();
t2.start();
t3.start();
}
}
class Window extends Thread{
private static int ticket=100;
//繼承方式,要用靜態物件,因為繼承實現多執行緒有多個物件,不是共用一個物件
private static Object obj=new Object();
public Window(String name){
super(name);
}
@Override
public void run() {
while(true){
//不可以用this,同理,因為有很多物件,不唯一
synchronized(obj){
if(ticket>0){
System.out.println(getName()+":賣票,票號為:"+ticket);
ticket--;
}else{
break;
}
}
}
}
}
方法二:同步方法
和同步程式碼塊類似
就是將共享資料的操作封裝成方法
將這個方法用鎖鎖住
public class test {
public static void main(String[] args) {
Window t1 = new Window("視窗1");
Window t2 = new Window("視窗2");
Window t3 = new Window("視窗3");
t1.start();
t2.start();
t3.start();
}
}
class Window extends Thread{
private static int ticket=100;
public Window(String name){
super(name);
}
@Override
public void run() {
while(true){
show();
}
}
public static synchronized void show(){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":賣票,票號為:"+ticket);
ticket--;
}
}
}
注意:
- show方法要定義成靜態,因為如果是非靜態,它的鎖預設是當前物件,繼承方式就會有多個鎖,如果是Runnable就可以,所以需要程式設計靜態,這樣預設鎖就是當前類物件
方法三:lock鎖
- 首先建立一個ReentrantLock物件
- 在執行共享資料之前將鎖開啟,呼叫lock方法
- 在結束時將鎖解開,呼叫unlock方法
public class test {
public static void main(String[] args) {
Windows w = new Windows();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("視窗1");
t2.setName("視窗2");
t3.setName("視窗3");
t1.start();
t2.start();
t3.start();
}
}
class Windows implements Runnable{
private int ticket=100;
private ReentrantLock lock=new ReentrantLock(true);
@Override
public void run() {
while(true){
try {
lock.lock();
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":賣票,票號為:"+ticket);
ticket--;
}
else{
break;
}
} finally {
lock.unlock();
}
}
}
}
問題:該方式和synchrnized有什麼不同呢?
synchronized在執行完相應程式碼後會自動上鎖解鎖,而lock需要手動上鎖和解鎖,較為靈活
執行緒的死鎖
描述:死鎖是指兩個或兩個以上的程式在執行過程中,由於競爭資源或者由於彼此通訊而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程式稱為死鎖程式。
舉個例子:兩個人迎面相遇,甲希望乙會給他讓路,而乙希望甲給他讓他讓路,就這樣兩個人僵持在這裡,最終誰也不給誰讓路,導致死鎖問題
public class test {
public static void main(String[] args) {
StringBuffer s1=new StringBuffer();
StringBuffer s2=new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
本例中,執行緒1首先拿到了s1鎖,然後阻塞了一段時間,這段時間執行緒2拿到了s2鎖,這是執行緒1就緒需要s2鎖,而執行緒2需要s1鎖,兩者誰都拿不到,就會僵持住
解決死鎖的相應辦法:
- 減少共享變數的使用
- 設計相應的演算法去規避死鎖問題
- 儘量減少鎖的巢狀使用
執行緒的通訊
-
wait():將執行緒進入阻塞狀態(會釋放掉鎖),只能在同步程式碼塊或同步方法中使用
-
notify():將另外一個優先順序高的執行緒喚醒
-
notifyAll():喚醒所有被阻塞的執行緒
注意:這三個方法只能夠在同步程式碼塊或者同步方法使用,都定義在了Object類中
例題要求:
讓兩個執行緒交替列印1-100之間的數字
public class 執行緒通訊 {
public static void main(String[] args) {
Number number=new Number();
Thread t1=new Thread(number);
Thread t2=new Thread(number);
t1.setName("執行緒一");
t2.setName("執行緒二");
t1.start();
t2.start();
}
}
class Number implements Runnable{
private int number=1;
@Override
public void run() {
while(true){
synchronized (this) {
notify();
//喚醒全部
// notifyAll();
if(number<=100){
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
try {
//使得呼叫如下方法程式阻塞,執行wait後,鎖就被釋放
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
- 當執行緒1首次進入,會列印出1,然後呼叫了wait方法 ,進入阻塞狀態
- 此時執行緒2進入,首先會喚醒執行緒1,然後列印2,然後自己進入阻塞狀態
- 兩者交替阻塞喚醒,直到列印完為止
那麼問題是sleep和wait方法有什麼異同?
兩個方法宣告的位置不同,sleep是Thread中宣告的,而wait是Object中宣告的
sleep可以在任何情景呼叫,而wait只能夠在同步程式碼塊或同步方法中使用
sleep執行後不會釋放當前的鎖,而wait會釋放掉當前的鎖
生產者和消費者問題:
生產者(Priductor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品,店員一次只能持有固定數量的產品(比如20個),如果生產者檢視生產更多的產品,店員會叫生產者停一下,如果店中有空位放產品了再通知生產者繼續生產:如果店中沒有產品了,店員會告訴消費者等一下,如果店中有產品了再通知消費者來取走產品。
package 生產者與消費者問題;
public class 生產者 {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生產者");
Consumer c1 = new Consumer(clerk);
c1.setName("消費者");
p1.start();
c1.start();
}
}
class Clerk{
private int productCount=0;
public synchronized void consumeProduct() {
if(productCount>0){
System.out.println(Thread.currentThread().getName()+":開始消費第"+productCount+"個產品");
productCount--;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void produceProduct() {
if(productCount<20){
productCount++;
System.out.println(Thread.currentThread().getName()+":開始生產第"+productCount+"個產品");
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName()+":開始生產產品......");
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread{
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run() {
System.out.println(getName()+":開始消費產品......");
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
- 生產者和消費者會共享產品數量
- 我們可以在售貨員類中定義方法,當此時的生產數量未達到標準時,就會進行生產,然後會喚醒消費的程式,否則就會進入阻塞狀態
- 而當產品數量不足時,消費者就會喚醒生產程式,此時,自己進入阻塞狀態
相關文章
- 多執行緒之死鎖就是這麼簡單執行緒
- python 多執行緒就這麼簡單(續)Python執行緒
- 沒想到,這麼簡單的執行緒池用法,深藏這麼多坑!執行緒
- 面試官:這就是你理解的Java多執行緒基礎?面試Java執行緒
- 【Java】【多執行緒】執行緒池簡述Java執行緒
- java多執行緒 wait() notify()簡單使用Java執行緒AI
- Java簡單多執行緒斷點下載Java執行緒斷點
- jdbc就是這麼簡單JDBC
- WebService就是這麼簡單Web
- WebSocket就是這麼簡單Web
- jwt 就是這麼簡單JWT
- Activiti就是這麼簡單
- Java多執行緒——執行緒Java執行緒
- Golang多執行緒簡單鬥地主Golang執行緒
- wxWidgets簡單的多執行緒執行緒
- 多執行緒 -- 初學簡單例子執行緒單例
- Java多執行緒—執行緒同步(單訊號量互斥)Java執行緒
- Java多執行緒-執行緒中止Java執行緒
- Java多執行緒——執行緒池Java執行緒
- redis為什麼用單執行緒不用多執行緒Redis執行緒
- promise原理就是這麼簡單Promise
- ThreadLocal就是這麼簡單thread
- JAVA_多執行緒_單例模式Java執行緒單例模式
- 多執行緒Demo學習(執行緒的同步,簡單的執行緒通訊)執行緒
- 【Java多執行緒】輕鬆搞定Java多執行緒(二)Java執行緒
- 執行緒和執行緒池的理解與java簡單例子執行緒Java單例
- java——多執行緒Java執行緒
- java 多執行緒Java執行緒
- 【Java】多執行緒Java執行緒
- JAVA 多執行緒 ??Java執行緒
- java多執行緒Java執行緒
- Java - 多執行緒Java執行緒
- 這麼理解執行緒生命週期,是不是很簡單?執行緒
- 【原創】Java多執行緒初學者指南(1):執行緒簡介Java執行緒
- java 多執行緒守護執行緒Java執行緒
- Java多執行緒-執行緒通訊Java執行緒
- Java多執行緒-執行緒狀態Java執行緒
- Java多執行緒(2)執行緒鎖Java執行緒