一.並發的定義
並發:對於這個概念一直就是沒怎么搞懂,就是感覺特別的生疏,(自己從從字面上理解就是多個東西,一起出發),所以就上網上查了一些資料:
同時擁有兩個或多個線程,如果程序在單核處理器上運行,多個線程將交替地換入或者換出內存,這些線程是同時“存在”的,每個線程都處於執行過程中的某個狀態,如果運行在多核處理器上,此時,程序中的每個線程都將分配到一個處理器核上,因此可以同時運行。
高並發(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.kuang.demo1;
//創建線程的第二種 首先實現runnable接口,接着重寫run()方法,執行線程時需要丟入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
七.線程池
package com.kuang.state;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
//創建服務 創建線程池
//newFixedThreadPool 參數為:線程池的大小
ExecutorService service = Executors.newFixedThreadPool(10);
//執行 把Runnable接口丟進去
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//關閉鏈接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
八.死鎖(就是兩個人互相像索要對方的東西時,最后誰也沒有得到)
//死鎖:多個線程相互擁抱對方需要的資源,然后形成僵持
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0,"灰姑娘");
Makeup g2 = new Makeup(1,"白雪公主");
g1.start();
g2.start();
}
}
//口紅
class Lipstick{
}
//鏡子
class Mirror{
}
class Makeup extends Thread{
//需要的資源只要一個 用
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice; // 選擇
String girlName; //使用化妝品
Makeup(int choice ,String girlName){
this.choice=choice;
this.girlName=girlName;
}
@Override
public void run() {
//化妝
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妝
private void makeup() throws InterruptedException{
if(choice==0) {
//同步塊
synchronized(lipstick) {
System.out.println(this.girlName + "獲得口紅的鎖");
Thread.sleep(1000);
//synchronized(mirror) {
// System.out.println(this.girlName + "獲得鏡子的鎖");
}
synchronized(mirror) {
System.out.println(this.girlName + "獲得鏡子的鎖");
}
}
else {
synchronized(mirror) {
System.out.println(this.girlName + "獲得鏡子的鎖");
Thread.sleep(2000);
//synchronized(lipstick) {
//System.out.println(this.girlName + " 獲得口紅的鎖");
}
synchronized(lipstick) {
System.out.println(this.girlName + " 獲得口紅的鎖");
}
}
}
}
今天就先寫到這里了,之后的鎖,同步,線程池等.....我會陸續加載出來的!有需要的的可以收藏加關注哦!希望可以幫助您們。