1. 程式人生 > 實用技巧 >java基礎——併發1

java基礎——併發1

一.併發的定義

併發:對於這個概念一直就是沒怎麼搞懂,就是感覺特別的生疏,(自己從從字面上理解就是多個東西,一起出發),所以就上網上查了一些資料:

同時擁有兩個或多個執行緒,如果程式在單核處理器上執行,多個執行緒將交替地換入或者換出記憶體,這些執行緒是同時“存在”的,每個執行緒都處於執行過程中的某個狀態,如果執行在多核處理器上,此時,程式中的每個執行緒都將分配到一個處理器核上,因此可以同時執行。

高併發(High Concurrency):

是網際網路分散式系統架構設計中必須考慮的因素之一,它通常是指,通過設計保證系統能夠同時並行處理很多請求。

兩者區別:

併發:多個執行緒操作相同的資源,保證執行緒安全,合理使用資源。

高併發(High Concurrency):服務能同時處理很多請求,提高程式效能

二.執行緒的定義

1.執行緒(英語:thread)是作業系統能夠進行運算排程的最小單位。它被包含在程式之中,是程式中的實際運作單位。一條執行緒指的是程式中一個單一順序的控制流,一個程式中可以併發多個執行緒,每條執行緒並行執行不同的任務。在Unix System V及SunOS中也被稱為輕量程式(lightweight processes),但輕量程式更多指核心執行緒(kernel thread),而把使用者執行緒(user thread)稱為執行緒。

在網上查資料執行緒是這麼定義的,其實說簡單點,執行緒其實就是可以多個同時進行,也稱之為多執行緒。下面是個生活中的例子

比如一天突然來一個電話,張三跟我說:“他的朋友李四的卡里面沒有錢了,想讓我借給他的朋友點

,我之後便去銀行,到了銀行,我先把錢轉給張三,之後張三再把錢轉到他的朋友李四的卡里。

其實,這個例子就是多執行緒的同時操作,第一個執行緒是  我把錢轉到張三的卡里面  ;第二個執行緒是  張三在把錢轉到李四的卡里。
在有一個例子:都聽過龜兔賽跑的事情吧!這其實也是多執行緒的問題

兔子和烏龜同時從原點出發,比誰先到終點,這其實就是兩個多執行緒共同進行,且互不影響。
在譬如說:咱們執行一個程式,程式沒開始執行的時候是靜態的,經過程式,再到執行緒,

它真正執行的也就是執行緒,其中main()稱之為主執行緒。
在實際生活中多執行緒也是非常有用的,

比如,我們在瀏覽器上可以同時下載幾張圖片。

上面所說的都是執行緒的例子,它還是與我們生活密切相關的,用處也是非常大的。

執行緒就是獨立的執行路徑

在程式執行時,即使沒有自己建立執行緒,後臺也會有多少少執行緒,如主執行緒,gc執行緒

main()稱之為主執行緒,為系統的入口,用於執行整個程式

在一個程式中,如果開闢了多少執行緒,執行緒的執行由排程器安排排程,排程器是與作業系統緊密相關的,先後順序是不能認為的幹預的。

對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制

每個執行緒在自己的工作記憶體互動,記憶體控制不會造成吧資料不一致。

三.怎樣實現多執行緒?

1.繼承Thread類實現多執行緒的能力

package com.kuang.demo1;

//建立Thread類  重寫run()方法  呼叫start()方法
//執行緒開始不一定立即執行,由CPU執行
public class TestThread1 extends Thread{
@Override
public void run() {
//run()方法執行緒
for(int i=0;i<20;i++) {
System.out.println("我愛寫程式碼--" +i);
}
} public static void main(String[] args) {
//main執行緒 主執行緒 //建立一個執行緒
TestThread1 testThread1 = new TestThread1(); //呼叫start()方法開啟執行緒
testThread1.start(); for(int i=0;i<20;i++) {
System.out.println("我愛學習多執行緒" + i);
}
}
}

2.實現Runnable介面建立多執行緒

package com.kuan瞭解即可丟入runnable介面實現類,呼叫start方法
public class TestThread2 implements Runnable{
@Override
public void run() {
//run方法執行緒體
for(int i=0;i<20;i++) {
System.out.println("我在看程式碼--" + i);
}
} public static void main(String [] args) {
//建立runnable實現介面類的執行緒
TestThread2 testThread2 = new TestThread2();
new Thread(testThread2).start(); for(int i=0;i<20;i++) {
System.out.println("我在學習多執行緒--" + i);
}
}
}

3.實現Callable介面(這個我就不舉程式碼例子了,瞭解即可)

1.實現Callable介面,需要返回值型別

2.重寫call方法,需要丟擲異常

3.建立目標物件

4.建立執行服務:ExecutorService ser = Excutors.newFixedThreadPool(1);

5.提交執行:Future<Boolean> result1 = ser.submit(t1);

6.獲取結果:boolean r1 = result.get();

7.關閉服務:ser.shutdownNow();

專案例項——龜兔賽跑

package com.kuang.demo1;

//模擬龜兔賽跑
public class Race implements Runnable { //勝利者
private static String winner; @Override
public void run() {
for(int i=0;i<=100;i++) { //模擬兔子休息,設定讓他十步卡以下
if(Thread.currentThread().getName().equals("兔子")&&( i+1)%10==0) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判斷比賽是否結束
boolean flag = gameOver(i);
//如果比賽結束,就停止程式
if(flag) {
break;
}
System.out.println(Thread.currentThread().getName() +"-->跑了" + i + "步");
}
} //判斷是否完成比賽
private boolean gameOver(int steps) {
//判斷是否有勝利者
if(winner!=null) {
return true;
}{
if(steps>=100) {
winner = Thread.currentThread().getName();
System.out.println("winner is " + winner);
return true;
}
}
return false;
} public static void main(String[] args) {
Race race = new Race(); new Thread(race,"兔子").start();
new Thread(race,"烏龜").start();
}
}

執行結果:

兔子-->跑了0步
兔子-->跑了1步
烏龜-->跑了0步
烏龜-->跑了1步
兔子-->跑了2步
烏龜-->跑了2步
烏龜-->跑了3步
烏龜-->跑了4步
兔子-->跑了3步
烏龜-->跑了5步
烏龜-->跑了6步
兔子-->跑了4步
兔子-->跑了5步
烏龜-->跑了7步
烏龜-->跑了8步
烏龜-->跑了9步
兔子-->跑了6步
兔子-->跑了7步
兔子-->跑了8步
烏龜-->跑了10步
烏龜-->跑了11步
烏龜-->跑了12步
烏龜-->跑了13步
烏龜-->跑了14步
烏龜-->跑了15步
烏龜-->跑了16步
烏龜-->跑了17步
烏龜-->跑了18步
烏龜-->跑了19步
烏龜-->跑了20步
烏龜-->跑了21步
烏龜-->跑了22步
烏龜-->跑了23步
烏龜-->跑了24步
烏龜-->跑了25步
烏龜-->跑了26步
烏龜-->跑了27步
烏龜-->跑了28步
烏龜-->跑了29步
烏龜-->跑了30步
烏龜-->跑了31步
烏龜-->跑了32步
烏龜-->跑了33步
烏龜-->跑了34步
烏龜-->跑了35步
烏龜-->跑了36步
烏龜-->跑了37步
烏龜-->跑了38步
烏龜-->跑了39步
烏龜-->跑了40步
烏龜-->跑了41步
烏龜-->跑了42步
烏龜-->跑了43步
烏龜-->跑了44步
烏龜-->跑了45步
烏龜-->跑了46步
烏龜-->跑了47步
烏龜-->跑了48步
烏龜-->跑了49步
烏龜-->跑了50步
烏龜-->跑了51步
烏龜-->跑了52步
烏龜-->跑了53步
烏龜-->跑了54步
烏龜-->跑了55步
烏龜-->跑了56步
烏龜-->跑了57步
烏龜-->跑了58步
烏龜-->跑了59步
烏龜-->跑了60步
烏龜-->跑了61步
烏龜-->跑了62步
烏龜-->跑了63步
烏龜-->跑了64步
烏龜-->跑了65步
烏龜-->跑了66步
烏龜-->跑了67步
烏龜-->跑了68步
烏龜-->跑了69步
烏龜-->跑了70步
烏龜-->跑了71步
烏龜-->跑了72步
烏龜-->跑了73步
烏龜-->跑了74步
烏龜-->跑了75步
烏龜-->跑了76步
烏龜-->跑了77步
烏龜-->跑了78步
烏龜-->跑了79步
烏龜-->跑了80步
烏龜-->跑了81步
烏龜-->跑了82步
烏龜-->跑了83步
烏龜-->跑了84步
烏龜-->跑了85步
烏龜-->跑了86步
烏龜-->跑了87步
烏龜-->跑了88步
烏龜-->跑了89步
烏龜-->跑了90步
烏龜-->跑了91步
烏龜-->跑了92步
烏龜-->跑了93步
烏龜-->跑了94步
烏龜-->跑了95步
烏龜-->跑了96步
烏龜-->跑了97步
烏龜-->跑了98步
烏龜-->跑了99步
winner is 烏龜

四.lambda表示式

Lambda 表示式(lambda expression)是一個匿名函式,Lambda表示式基於數學中的λ演算得名,直接對應於其中的lambda抽象(lambda abstraction),是一個匿名函式,即沒有函式名的函式。

Java 8的一個大亮點是引入Lambda表示式,使用它設計的程式碼會更加簡潔。當開發者在編寫Lambda表示式時,也會隨之被編譯成一個函式式介面。

函式式介面:只包含一個抽象方法

package com.kuang.lambda;

/*
*推到lambda表示式
*函式式介面:只包含一個抽象方法
*/ public class TestLambda1{ //2.靜態內部類
static class Like2 implements ILike{
@Override
public void Lambda() {
System.out.println("I like Lambda2");
}
}
public static void main(String[] args) {
//用介面建立物件
ILike like = new Like();
like.Lambda(); like = new Like2();
like.Lambda(); //3.區域性內部類
class Like3 implements ILike{
@Override
public void Lambda() {
System.out.println("I like Lambda3");
}
} like = new Like3();
like.Lambda(); //4.匿名內部類,需要new一個介面,沒有類的名稱,必須藉助介面
like = new ILike() {
@Override
public void Lambda() {
System.out.println("I like Lambda4");
}
}; like.Lambda(); //5.用Lambda簡化
like = ()->{
System.out.println("I like Lambda5");
};
like.Lambda(); }
} //1.定義一個介面
interface ILike{
void Lambda();
} //實現介面
class Like implements ILike{
@Override
public void Lambda() {
System.out.println("I like Lambda1");
}
} 執行結果:
I like Lambda1
I like Lambda2
I like Lambda3
I like Lambda4
I like Lambda5

在舉一個就光是lambda用法的例子,lambda用法的核心語句就是

物件=(所要強調的)->{

};

package com.kuang.lambda;

public class TestLambda2{
public static void main(String[] args) {
ILove love =(int a )-> {
System.out.println("I Love You -->" + a);
};
love=(a)->{
System.out.println("I Love You -->" + a);
};
love.love(2);
}
} interface ILove{
void love(int a);
} 執行結果:
I Love You -->2

五.執行緒的5種狀態

New(新建),Runnable(可執行),堵塞和等待, 執行,Terminated(終止)

1.執行緒停止

不推薦使用JDK提供的stop(),destory()方法

推薦執行緒自己停止下來

建議使用一個標誌位進行終止變數 當flag=false,則終止執行緒執行。

例如下列例項:

整體思路:首先設立一個標誌flag為真,之後進行重寫,當flag真的時候輸出執行緒。

其次設立一個公開的方法停止執行緒,在這裡我們不要其它的方法,用stop()就可以,令this.flag=false 主函式建立一個物件,

之後主函式new Thread().start();的形式啟動它 進行設定迴圈多少次,輸出一個main執行緒,令i等於幾的時候執行緒停止 物件.stop();

下面那個例子我設定的是一共進行20次迴圈 main()執行緒第五次的時候,執行緒終止了。

這是程式碼的核心要點

public class TestStop implements Runnable{
//執行緒種定義執行緒體使用的標記
private boolean flag=true; @Override
public void run(){
//執行緒體使用該標記
while(flag){
System.out.println("run......Thread");
}
} //對外提供方法改變標識
public void stop(){
this.flag=false;
}
}

完整程式碼如下圖所示:

package com.kuang.state;

import java.util.Iterator;

//測試stop
//j建議執行緒正常停止-->,利用次數,不建議死迴圈
//建議使用標誌位-->設定一個標誌為
//
public class TestStop implements Runnable { //1.設定一個標誌為
private boolean flag = true;
@Override
public void run() {
int i=0;
while(flag) {
System.out.println("run......Thread" + i ++);
}
} //設定一個公開的方法停止執行緒,轉換標誌位
public void stop() {
this.flag=false;
}
public static void main(String[] args) {
TestStop teststop= new TestStop();
new Thread(teststop).start(); //啟動 for(int i=0;i<20;i++) {
System.out.println("main執行緒" + i);
if(i==5) {
//呼叫stop方法切換標誌位,讓該執行緒停止
teststop.stop();
System.out.println("執行緒停止了");
}
}
}
}

執行結果:

main執行緒0
run......Thread0
main執行緒1
run......Thread1
run......Thread2
main執行緒2
run......Thread3
main執行緒3
main執行緒4
run......Thread4
main執行緒5
執行緒停止了
run......Thread5
main執行緒6
main執行緒7
main執行緒8
main執行緒9
main執行緒10
main執行緒11
main執行緒12
main執行緒13
main執行緒14
main執行緒15
main執行緒16
main執行緒17
main執行緒18
main執行緒19

2.執行緒禮讓Yield

在API中有一個這樣的方法 static void yield()

指的是:當前正在執行的執行緒向另一個執行緒交出執行權,注意,這是一個靜態方法。

要點:

1.禮讓(yield)執行緒,讓當前正在執行的執行緒暫停,但不堵塞。

2.將執行緒從執行狀態轉為就緒狀態

3.讓CPU重新排程,禮讓不一定成功!看CPU的心情。

程式碼例項:

package com.kuang.demo1;

//測試禮讓執行緒
//禮讓不一定成功,看cpu的心情
public class TestYiled { public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield ,"a").start();
new Thread(myYield ,"b").start(); }
} class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "執行緒開始了");
Thread.yield();//禮讓
System.out.println(Thread.currentThread().getName()+ "執行緒結束了");
}
} 執行結果: 第一次:
b執行緒開始了
a執行緒開始了
b執行緒結束了
a執行緒結束了 第二次:
a執行緒開始了
a執行緒結束了
b執行緒開始了
b執行緒結束了

看!它的執行結果是變化的,因為“禮讓”有時候不一定會成功,他需要看CPU的心情。

3.執行緒休眠sleep

執行緒休眠總結:

1.sleep(時間)指定當前堵塞的毫秒數

2.sleep存在異常interruptedException

3.sleep時間達到後執行緒進入就緒狀態

4.*每個物件都有一個一個鎖,sleep不會釋放鎖

下面例子是Thread.sleep()的用法

package com.kuang.demo1;

//模擬網路延遲的作用:放大問題的發生性
public class TestSleep implements Runnable { //票數
private int ticketNums=10; @Override
public void run() {
while(true) {
if(ticketNums<=0) {
break;
} //模擬延時
try {
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
} System.out.println(Thread.currentThread().getName() + "-->拿到了第" + ticketNums-- + "票");
}
} public static void main(String[] args) {
TestSleep ticket = new TestSleep(); new Thread(ticket ,"小明").start();
new Thread(ticket ,"老師").start();
new Thread(ticket ,"黃牛黨").start();
}
}

看下面的執行結果,結果顯示黃牛黨拿到了-1張票,通過模擬執行緒,好處是把問題給呈現出來了

小明-->拿到了第9票
黃牛黨-->拿到了第9票
老師-->拿到了第10票
老師-->拿到了第8票
小明-->拿到了第8票
黃牛黨-->拿到了第8票
黃牛黨-->拿到了第7票
小明-->拿到了第6票
老師-->拿到了第6票
老師-->拿到了第5票
小明-->拿到了第4票
黃牛黨-->拿到了第3票
老師-->拿到了第2票
小明-->拿到了第2票
黃牛黨-->拿到了第2票
老師-->拿到了第0票
小明-->拿到了第1票
黃牛黨-->拿到了第-1票

模擬倒計時 主要練習tenDown()的用法

package com.kuang.demo1;

public class TestSleep1 {

	public static void main(String[] args) {
try {
tenDown();
}catch(InterruptedException e) {
e.printStackTrace();
}
} //模擬倒計時
public static void tenDown() throws InterruptedException{
int num=10;
while(true) {
Thread.sleep(1000);
System.out.println(num--);
if(num<=0) {
break;
}
}
}
}

執行結果:

10
9
8
7
6
5
4
3
2
1

模擬日常時間倒計時

在上一個的基礎上新增上Data用法,和時間格式符

package com.kuang.demo1;

import java.text.SimpleDateFormat;
import java.util.Date; public class TestSleep1 { public static void main(String[] args) {
//列印當前系統時間
Date startTime = new Date(System.currentTimeMillis()); //獲得系統當前時間
while(true) {
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); //輸出時間格式化工廠
startTime = new Date(System.currentTimeMillis()); //更新當前時間
}catch(InterruptedException e) {
e.printStackTrace();
}
}
} //模擬時間倒計時
public static void tenDown() throws InterruptedException{
int num=10;
while(true) {
Thread.sleep(1000);
System.out.println(num--);
if(num<=0) {
break;
}
}
}
}

程式碼結果

16:36:18
16:36:19
16:36:20
16:36:21
16:36:22
16:36:23
16:36:24
16:36:25
16:36:26
16:36:27
16:36:28
16:36:29
16:36:30
16:36:31
16:36:32
16:36:33
16:36:34
16:36:35
16:36:36
.......
.......
....... 它會一直這樣輸出.

4.新建執行緒

是通過new操作符建立一個新執行緒時,如new Thread(r),這個執行緒還沒有執行,這就意味著它的狀態是新建(new).

5.執行緒強制執行join

當執行緒等待另一個執行緒通過排程器出現一個條件,這個執行緒會進入等待狀態.通過Object.wait(),Thread.join()方法,或者是等待java.util.concurrent庫中的Lock或Condition時會出現這種情況。

總結:

1.Join合併執行緒,待此執行緒執行完成之後,在執行其他執行緒,其他執行緒堵塞

2.可以想象成插隊

程式碼:

package com.kuang.demo1;

//測試join方法
//想象成排隊
public class TestJoin implements Runnable {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0;i<10;i++) {
System.out.println("執行緒VIP來了" + i);
}
} public static void main(String[] args) throws InterruptedException {
//啟動我們的執行緒
TestJoin testjoin = new TestJoin();
Thread thread = new Thread(testjoin);
thread.start(); //主執行緒
for(int i=0;i<50;i++){
if(i==25) {
thread.join();//插隊
}
System.out.println("main" + i);
}
}
}

執行結果:

main0
main1
main2
main3
main4
main5
main6
main7
main8
main9
main10
main11
main12
main13
main14
main15
main16
main17
main18
main19
main20
main21
main22
main23
main24
執行緒VIP來了0
執行緒VIP來了1
執行緒VIP來了2
執行緒VIP來了3
執行緒VIP來了4
執行緒VIP來了5
執行緒VIP來了6
執行緒VIP來了7
執行緒VIP來了8
執行緒VIP來了9
main25
main26
main27
main28
main29
main30
main31
main32
main33
main34
main35
main36
main37
main38
main39
main40
main41
main42
main43
main44
main45
main46
main47
main48
main49

在i=25之前是CPU排程的,隨機,當i>25之後就開始先執行VIP了。

六.判斷執行緒所處的狀態

package com.kuang.demo1;
//觀察測試執行緒的狀態
public class TestState { public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for(int i=0;i<5;i++) {
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("我愛寫程式碼");
}); //觀察狀態
Thread.State state = thread.getState();
System.out.println(state); //NEW thread.start(); //啟動執行緒
state = thread.getState();
System.out.println(state); while(state!=Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState(); //更新執行緒狀態
System.out.println(state); //輸出狀態
} }
}

執行結果:從中可以看出一個執行緒所處的狀態一次經過new Runnable 堵塞 執行 終止 這五個狀態

NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
我愛寫程式碼
TERMINATED

今天先寫到這裡,之後的同步,鎖,執行緒池等我會陸續載入出來的,希望能幫助到您們。

請 大 家 多 多 關 注 !