java 多线程 总结_Java 多线程总结
昨天熬了個通宵,看了一晚上的視頻,把java 的多線程相關技術重新復習了一遍,下面對學習過程中遇到的知識點進行下總結。
首先我們先來了解一下進程、線程、并發執行的概念:
進程是指:一個內存中運行的應用程序,每個進程都有自己獨立的一塊內存空間,一個進程中可以啟動多個線程。比如在Windows系統中,一個運行的exe就是一個進程。
線程是指:進程中的一個執行流程,一個進程中可以運行多個線程。比如java.exe進程中可以運行很多線程。線程總是屬于某個進程,進程中的多個線程共享進程的內存。
一般來說,當運行一個應用程序的時候,就啟動了一個進程,當然有些會啟動多個進程。啟動進程的時候,操作系統會為進程分配資源,其中最主要的資源是內存空間,因為程序是在內存中運行的。
在進程中,有些程序流程塊是可以亂序執行的,并且這個代碼塊可以同時被多次執行。實際上,這樣的代碼塊就是線程體。線程是進程中亂序執行的代碼流程。當多個線程同時運行的時候,這樣的執行模式成為并發執行。
線程的狀態
1、線程共有下面4種狀態:
新建狀態(New):新創建了一個線程對象,當你用new創建一個線程時,該線程尚未運行。
就緒狀態(Runnable):線程對象創建后,其他線程調用了該對象的start()方法。該狀態的線程位于可運行線程池中,變得可運行,等待獲取CPU的使用權。
運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
阻塞狀態(Blocked):阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:
a. 等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
b. 同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM把該線程放入鎖。
c. 其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
死亡狀態(Dead):
a. ?由于run方法的正常退出而自然死亡;
b.? 沒有捕獲到的異常事件終止了run方法的執行,從而導致線程突然死亡
2、若要確定某個線程當前是否活著,可以使用isAlive方法。
如果該線程是可運行線程或者被中斷線程,那么該方法返回true;如果該線程仍然是個新建線程,或者該線程是個死線程,那么該方法返回false
3、注意:你無法確定一個活線程究竟是處于可運行狀態還是被中斷狀態,也無法確定一個可運行線程是否正處在運行之中。另外,你也無法對尚未成為可運行的線程與已經死掉的線程進行區分。
4、線程必須退出中斷狀態,并且返回到可運行狀態,方法是使用與進入中斷狀態相反的過程:
a.如果線程已經處于睡眠狀態,就必須經過規定的毫秒數
b.如果線程正在等待輸入或輸出操作完成,那么必須等待該操作完成
c.如果線程調用了wait方法,那么另外一個線程必須調用notifyAll或者notify方法
d.如果線程正在等待另一個線程擁有的對象鎖,那么另一個線程必須放棄該鎖的所有權
5、下面這副圖很好的反映了線程在不同情況下的狀態變化。
了解完多線程的相關知識,下面來介紹一下在java中多線程的實現方式
JAVA多線程實現方式
JAVA多線程實現方式主要有以下三種:
1、繼承Thread類
2、實現Runnable接口
3、使用ExecutorService、Callable、Future實現有返回結果的多線程。
其中前兩種方式線程執行完后都沒有返回值,只有最后一種是帶返回值的。其中最常用的也是前兩種實現方式。下面對前兩種實現方式分別做下講解。
1、繼承Thread類實現多線程
繼承Thread類的方法盡管被我列為一種多線程實現方式,但Thread本質上也是實現了Runnable接口的一個實例,它代表一個線程的實例,并且,啟動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啟動一個新線程,并執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,并復寫run()方法,就可以啟動新線程并執行自己定義的run()方法。
例如:
package thread;
public class MyThread extends Thread {
public void run() {
System.out.println("run()方法正在執行");
}
}
啟動線程方式如下:
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
2、實現Runnable接口方式實現多線程
如果自己的類已經extends另一個類,就無法直接extends Thread,此時,必須實現一個Runnable接口。
方法如下:
package thread;
class OtherClass{
public void print(String str){
System.out.println(str);
}
}
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("run()正在執行");
}
}
為了啟動MyThread,需要首先實例化一個Thread,并傳入自己的MyThread實例。
具體方法如下:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
事實上,當傳入一個Runnable target參數給Thread后,Thread的run()方法就會調用target.run(),參考JDK源代碼:
public void run() {
if (target != null) {
target.run();
}
}
學會了線程的創建方式,下面我們在舉幾個線程狀態轉換的例子
3、線程狀態的轉換實例
package thread;
public class ThreadStateDemo extends Thread {
Thread thread;
public ThreadStateDemo() {
thread = new Thread(this);
System.out.println("創建一個線程:thread");
thread.start();
}
public void run() {
try {
System.out.println("線程thread正在運行!");
System.out.println("線程thread睡眠3秒中...!");
Thread.sleep(3000); //靜態方法,使當前正在執行的線程睡眠3秒
System.out.println("線程thread在睡眠后重新運行!");
}catch(InterruptedException e) {
System.out.println("線程被中斷");
}
}
public static void main(String[] args) {
new ThreadStateDemo();
System.out.println("主線程main結束!");
}
}
【運行結果】如下:
創建一個線程:thread
主線程main結束!
線程thread正在運行!
線程thread睡眠3秒中...!
線程thread在睡眠后重新運行!
終止線程的實例:
package thread;
public class ThreadShutDownDemo {
public static void main(String args[]) {
Runner runner = new Runner();
Thread thread = new Thread(runner);
thread.start();
for(int i=0;i<10;i++) {
if(i%10!=0) {
System.out.println("在主線程中 i=" + i);
}
}
System.out.println("主線程main結束");
//通知線程結束
runner.shutDown();
}
}
class Runner implements Runnable {
//控制線程是否結束
private boolean flag = true;
public void run() {
int i=0;
while(flag == true) {
System.out.println("在子線程中 i=" + i++);
}
System.out.println("子線程結束");
}
//設置線程結束標志
public void shutDown() {
flag = false;
}
}
【運行結果】如下:
在主線程中 i=1
在子線程中 i=0
在主線程中 i=2
在子線程中 i=1
在主線程中 i=3
在子線程中 i=2
在主線程中 i=4
在子線程中 i=3
在主線程中 i=5
在子線程中 i=4
在主線程中 i=6
在主線程中 i=7
在主線程中 i=8
在主線程中 i=9
主線程main結束
在子線程中 i=5
子線程結束
join()方法實例:
package thread;
public class TheadJoinDemo {
public static void main(String[] args) {
Runner2 r = new Runner2();
Thread t = new Thread(r);
t.start();
try {
t.join();//主線程main將中斷,直到線程t執行完畢
}catch(InterruptedException e) {
}
for(int i=0;i<5;i++) {
System.out.println("主線程:" + i);
}
}
}
class Runner2 implements Runnable {
public void run() {
for(int i=0;i<10;i++) {
System.out.println("子線程:" + i);
}
}
}
【運行結果】如下:
子線程:0
子線程:1
子線程:2
子線程:3
子線程:4
子線程:5
子線程:6
子線程:7
子線程:8
子線程:9
主線程:0
主線程:1
主線程:2
主線程:3
主線程:4
介紹完以上幾個實例,我們下面對sleep()、wait()、yeid()、join()幾個方法進行下區別總結
sleep方法與wait方法的區別:
sleep方法是靜態方法,wait方法是非靜態方法。
sleep方法在時間到后會自己“醒來”,但wait不能,必須由其它線程通過notify(All)方法讓它“醒來”。
sleep方法通常用在不需要等待資源情況下的阻塞,像等待線程、數據庫連接的情況一般用wait。
sleep/wait與yeld方法的區別:
調用sleep或wait方法后,線程即進入block狀態,而調用yeld方法后,線程進入runnable狀態。
wait與join方法的區別:
wait方法體現了線程之間的互斥關系,而join方法體現了線程之間的同步關系。
wait方法必須由其它線程來解鎖,而join方法不需要,只要被等待線程執行完畢,當前線程自動變為就緒。
join方法的一個用途就是讓子線程在完成業務邏輯執行之前,主線程一直等待直到所有子線程執行完畢。
線程的同步問題
在實際應用中,我們通常會遇到多線程安全問題。多線程安全問題:當多條語句在操作同一線程共享數據是,一個線程對多條語句只執行了一部分,還沒有執行完, 此時另一個線程參與進來執行,導致共享數據的錯誤。
解決辦法:
對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。
Java 對于多線程的安全提供了專業的解決方式。
線程的同步是保證多線程安全訪問競爭資源的一種手段,對于同步,在具體的Java代碼中需要完成一下兩個操作:
把競爭訪問的資源標識為private;
同步哪些修改變量的代碼,使用synchronized關鍵字同步方法或代碼。
synchronized(對象){
代碼塊
...
}
同步的前提:
1、必須要有兩個或者兩個以上的線程運行;
2、必須是多個線程使用同一個鎖;
好處:解決了多線程的安全問題;
弊端:多個線程需要判斷鎖,較為消耗資源;
注意:非靜態同步函數的對象鎖為this,靜態同步函數所使用的鎖是該方法所在類的字節碼文件對象,即類名.class,靜態方法里的同步鎖都是使用的是類的字節碼對象。
//靜態同步函數鎖
public static synchronized void show(){
ticket++;
System.out.println(Thread.currentThread().getName()+"runtime..."+ticket--);
}
下面來例舉一個線程同步的例子:(同步方法)
package thread;
public class SynchronizedThread {
public static void main(String[] args) {
User u = new User("王某", 100);
MyThread2 t1 = new MyThread2("線程A", u, 10);
MyThread2 t2 = new MyThread2("線程B", u, -50);
MyThread2 t3 = new MyThread2("線程C", u, -60);
MyThread2 t4 = new MyThread2("線程D", u, -40);
MyThread2 t5 = new MyThread2("線程E", u, 20);
MyThread2 t6 = new MyThread2("線程F", u, 28);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}
class MyThread2 extends Thread {
private User u;
private int y = 0;
MyThread2(String name, User u, int y) {
super(name);
this.u = u;
this.y = y;
}
public void run() {
u.oper(y);
}
}
class User {
private String code;
private int cash;
User(String code, int cash) {
this.code = code;
this.cash = cash;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
/**
* 業務方法
* @param x 添加x萬元
*/
public synchronized void oper(int x) {
try {
Thread.sleep(10L);
this.cash += x;
System.out.println(Thread.currentThread().getName() + "運行結束,增加“" + x + "”,當前用戶賬戶余額為:" + cash);
Thread.sleep(10L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "User{" +
"code='" + code + '\'' +
", cash=" + cash +
'}';
}
}
【運行結果】如下:
線程A運行結束,增加“10”,當前用戶賬戶余額為:110
線程F運行結束,增加“28”,當前用戶賬戶余額為:138
線程E運行結束,增加“20”,當前用戶賬戶余額為:158
線程D運行結束,增加“-40”,當前用戶賬戶余額為:118
線程C運行結束,增加“-60”,當前用戶賬戶余額為:58
線程B運行結束,增加“-50”,當前用戶賬戶余額為:8
下面是線程不同步的情況,也就是去掉oper(int x)方法的synchronized修飾符,然后再運行程序
【運行結果】如下:
線程F運行結束,增加“28”,當前用戶賬戶余額為:128
線程D運行結束,增加“-40”,當前用戶賬戶余額為:88
線程B運行結束,增加“-50”,當前用戶賬戶余額為:38
線程E運行結束,增加“20”,當前用戶賬戶余額為:58
線程C運行結束,增加“-60”,當前用戶賬戶余額為:-2
線程A運行結束,增加“10”,當前用戶賬戶余額為:8
很顯然,上面的結果是錯誤的,導致錯誤的原因是多個線程并發訪問了競爭資源u,并對u的屬性做了改動。
注意:當去掉synchronized修飾符后,線程不在同步,每次運行的結果將都不一樣,可見同步的重要性。
再把以上實例改為同步代碼塊方式
對于同步,除了同步方法外,還可以使用同步代碼塊,有時候同步代碼塊會帶來比同步方法更好的效果。
追其同步的根本的目的,是控制競爭資源的正確的訪問,因此只要在訪問競爭資源的時候保證同一時刻只能一個線程訪問即可,因此Java引入了同步代碼快的策略,以提高性能。
在上個例子的基礎上,對oper方法做了改動,由同步方法改為同步代碼塊模式。代碼如下:
package thread;
/**
* 同步代碼塊
* @author Chu
*
*/
public class SynchronizedThread2 {
public static void main(String[] args) {
User u = new User("張三", 100);
MyThread3 t1 = new MyThread3("線程A", u, 10);
MyThread3 t2 = new MyThread3("線程B", u, -50);
MyThread3 t3 = new MyThread3("線程C", u, -60);
MyThread3 t4 = new MyThread3("線程D", u, -40);
MyThread3 t5 = new MyThread3("線程E", u, 20);
MyThread3 t6 = new MyThread3("線程F", u, 28);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}
class MyThread3 extends Thread {
private User u;
private int y = 0;
MyThread3(String name, User u, int y) {
super(name);
this.u = u;
this.y = y;
}
public void run() {
u.oper(y);
}
}
class User2 {
private String code;
private int cash;
User2(String code, int cash) {
this.code = code;
this.cash = cash;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
/**
* 業務方法
* @param x 添加x萬元
*/
public void oper(int x) {
try {
Thread.sleep(10L);
synchronized (this) {
this.cash += x;
System.out.println(Thread.currentThread().getName() + "運行結束,增加“" + x + "”,當前用戶賬戶余額為:" + cash);
}
Thread.sleep(10L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "User{" +
"code='" + code + '\'' +
", cash=" + cash +
'}';
}
}
【運行結果】如下:
線程A運行結束,增加“10”,當前用戶賬戶余額為:110
線程F運行結束,增加“28”,當前用戶賬戶余額為:138
線程D運行結束,增加“-40”,當前用戶賬戶余額為:98
線程E運行結束,增加“20”,當前用戶賬戶余額為:118
線程C運行結束,增加“-60”,當前用戶賬戶余額為:58
線程B運行結束,增加“-50”,當前用戶賬戶余額為:8
用到線程的同步,隨之可能會帶來死鎖問題。
導致死鎖的原因:兩個線程互相等待競爭資源,導致兩邊都無法得到資源,而使自己無法運行。
下面例舉一個導致死鎖的一個實例,代碼如下:
package thread;
class Demo1{
static Object obj1=new Object();
static Object obj2=new Object();
}
class Demo2 implements Runnable{
boolean flag;
Demo2(boolean flag){
this.flag=flag;
}
@Override
public void run(){
if(flag){
while(true){
synchronized(Demo1.obj1){
System.out.println("1");
synchronized(Demo1.obj2){
System.out.println("2");
}
}
}
}
else{
while(true){
synchronized(Demo1.obj2){
System.out.println("2");
synchronized(Demo1.obj1){
System.out.println("1");
}
}
}
}
}
}
最后我再說說:生產者消費者的問題
對于多線程程序來說,不管任何編程語言,生產者和消費者模型都是最經典的。
實際上,準確說應該是“生產者-消費者-倉儲”模型,離開了倉儲,生產者消費者模型就顯得沒有說服力了。
對于此模型,應該明確一下幾點:
1、生產者僅僅在倉儲未滿時候生產,倉滿則停止生產;
2、消費者僅僅在倉儲有產品時候才能消費,倉空則等待;
3、當消費者發現倉儲沒產品可消費時候會通知生產者生產;
4、生產者在生產出可消費產品時候,應該通知等待的消費者去消費。
此模型將要結合java.lang.Object的wait與notify、notifyAll方法來實現以上的需求。這是非常重要的。
具體實現代碼如下:
package thread;
/**
* Java線程:生產者消費者模型
* @author Chu 2013-06-15 05:32:29
*/
public class ProductTest {
public static void main(String[] args) {
Godown godown = new Godown(20);
Consumer c1 = new Consumer(80, godown);
Consumer c2 = new Consumer(30, godown);
Consumer c3 = new Consumer(20, godown);
Producer p1 = new Producer(5, godown);
Producer p2 = new Producer(5, godown);
Producer p3 = new Producer(5, godown);
Producer p4 = new Producer(10, godown);
Producer p5 = new Producer(20, godown);
Producer p6 = new Producer(35, godown);
Producer p7 = new Producer(50, godown);
c1.start();
c2.start();
c3.start();
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
p6.start();
p7.start();
}
}
/** 倉庫 */
class Godown {
public static final int max_size = 100; //最大庫存量
public int curnum; //當前庫存量
Godown() {
}
Godown(int curnum) {
this.curnum = curnum;
}
/**
* 生產指定數量的產品
* @param neednum
*/
public synchronized void produce(int neednum) {
//測試是否需要生產
while (neednum + curnum > max_size) {
System.out.println("要生產的產品數量" + neednum + "超過剩余庫存量" + (max_size - curnum) + ",暫時不能執行生產任務!");
try {
//當前的生產線程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//滿足生產條件,則進行生產,這里簡單的更改當前庫存量
curnum += neednum;
System.out.println("已經生產了" + neednum + "個產品,現倉儲量為" + curnum);
//喚醒在此對象監視器上等待的所有線程
notifyAll();
}
/**
* 消費指定數量的產品
* @param neednum
*/
public synchronized void consume(int neednum) {
//測試是否可消費
while (curnum < neednum) {
try {
//當前的生產線程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//滿足消費條件,則進行消費,這里簡單的更改當前庫存量
curnum -= neednum;
System.out.println("已經消費了" + neednum + "個產品,現倉儲量為" + curnum);
//喚醒在此對象監視器上等待的所有線程
notifyAll();
}
}
/** 生產者 */
class Producer extends Thread {
//生產產品的數量
private int neednum;
//倉庫
private Godown godown;
Producer(int neednum, Godown godown) {
this.neednum = neednum;
this.godown = godown;
}
public void run() {
//生產指定數量的產品
godown.produce(neednum);
}
}
/** 消費者 */
class Consumer extends Thread {
//生產產品的數量
private int neednum;
//倉庫
private Godown godown;
Consumer(int neednum, Godown godown) {
this.neednum = neednum;
this.godown = godown;
}
public void run() {
//消費指定數量的產品
godown.consume(neednum);
}
}
【運行結果】如下:
已經消費了20個產品,現倉儲量為0
已經生產了5個產品,現倉儲量為5
已經生產了5個產品,現倉儲量為10
已經生產了5個產品,現倉儲量為15
已經生產了20個產品,現倉儲量為35
已經生產了50個產品,現倉儲量為85
已經消費了80個產品,現倉儲量為5
已經生產了10個產品,現倉儲量為15
已經生產了35個產品,現倉儲量為50
已經消費了30個產品,現倉儲量為20
說明:
對于本例,要說明的是當發現不能滿足生產或者消費條件的時候,調用對象的wait方法,wait方法的作用是釋放當前線程的所獲得的鎖,并調用對象的notifyAll() 方法,通知(喚醒)該對象上其他等待線程,使得其繼續執行。這樣,整個生產者、消費者線程得以正確的協作執行。
notifyAll() 方法,起到的是一個通知作用,不釋放鎖,也不獲取鎖。只是告訴該對象上等待的線程可以競爭執行了。
以上這個例子僅僅是生產者消費者模型中最簡單的一種表示,在這個例子中,如果消費者消費的倉儲量達不到滿足,而又沒有生產者,則程序會一直處于等待狀態,這當然是不對的。實際上可以將此例進行修改,修改為,根據消費驅動生產,同時生產兼顧倉庫,如果倉不滿就生產,并對每次最大消費量做個限制,這樣就不存在此問題了,當然這樣的例子更復雜,更難以說明這樣一個簡單模型。
總結
以上是生活随笔為你收集整理的java 多线程 总结_Java 多线程总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不想备案证可以进户吗(不想备案)
- 下一篇: java integer valueof