Java執行緒狀態,同步,鎖學習
執行緒狀態
五大狀態
- 建立狀態:Thread thread = new Thread(); 執行緒物件一旦建立就進入了新生狀態。
- 就緒狀態:當呼叫start()方法時,進入就緒狀態,但不代表立即排程執行(等待cpu排程)。
- 執行狀態:進入執行狀態,執行緒才真正執行執行緒體的程式碼塊。
- 阻塞狀態:當呼叫sleep,wait或同步鎖時,執行緒進入阻塞狀態,就是程式碼不往下執行,阻塞解除後進入就緒狀態,等待cpu排程。
- 死亡狀態:執行緒中斷或者結束,進入死亡狀態,不能再次啟動。
停止執行緒
停止執行緒:不推薦使用jdk的方法(已廢棄),建議使用一個標誌位進行終止變數,當fiag=false時,則終止執行緒進行。
package com.thread.stop; //停止執行緒 //建議執行緒正常停止 利用次數 不建議死迴圈 //建議使用標誌位 //不要使用stop或者destory等 public class TestStop implements Runnable{ //設定標誌位 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 < 1000; i++) { System.out.println("main"+i); if (i==900){ //呼叫自己的stop方法切換標誌位,停止執行緒 testStop.stop(); System.out.println("執行緒停止了"); } } } }
執行緒休眠
sleep(時間)指定當前執行緒阻塞的毫秒數。
sleep存在異常interrupteException。
sleep時間到達後執行緒進入就緒狀態。
sleep可以模擬網路延時,倒計時等。
每一個物件都有一個鎖,sleep不會釋放鎖。
package com.thread.lesson02; //模擬倒計時 public class TestSleep2 { public static void main(String[] args) throws Exception { test(); } public static void test() throws Exception { int num = 10; while (true){ Thread.sleep(1000); System.out.println(num--); if (num<=0){ break; } } } }
public class TestSleep3 {
public static void main(String[] args) throws Exception {
//列印系統當前時間
Date time = new Date(System.currentTimeMillis());//獲取系統當前時間
while (true){
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(time));
time = new Date(System.currentTimeMillis());//更新當前系統時間
}
}
執行緒禮讓
執行緒禮讓(yield):讓當前正在執行的執行緒暫停,但不阻塞。將執行緒從執行狀態轉化為就緒狀態,讓cpu重新排程。
package com.thread.thread;
//執行緒禮讓 禮讓不一定成功
public class TestYield {
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()+"執行緒結束執行");
}
}
執行緒合併
Join合併執行緒,待此執行緒執行完畢後,再執行其他執行緒,其他執行緒阻塞。
package com.thread.thread;
//join方法 類似插隊
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("執行緒vip來了"+i);
}
}
public static void main(String[] args) throws Exception {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
//主執行緒
for (int i = 0; i < 500; i++) {
if(i==200){
thread.join();//插隊
}
System.out.println("main"+i);
}
}
}
觀察執行緒狀態
package com.thread.thread;
//測試執行緒狀態
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);
//觀察啟動後
thread.start();
state = thread.getState();
System.out.println(state);
//只要執行緒不終止就輸出它的狀態
while (state!=Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState();//更新狀態
System.out.println(state);
}
}
}
執行緒優先順序
Java提供一個執行緒排程器來監控程式中啟動後進入就緒狀態的所有執行緒,執行緒排程器按照優先順序來決定應該排程哪個執行緒來執行。
執行緒的優先順序(priority)用數字表示,範圍從1--10。使用getPriority()和setPwriority()來獲取,改變優先順序。
執行緒優先順序低只是意味著獲得排程的概率低,並不是優先順序低就不會被呼叫了,主要看cpu的排程(優先順序低的也有可能先執行)。
優先順序設定線上程啟動之前。執行緒的預設優先順序為5。
package com.thread.thread;
//測試執行緒的優先順序
public class TestPriority {
public static void main(String[] args) {
//主執行緒預設優先順序
System.out.println(Thread.currentThread().getName()+"->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
//先設定優先順序再啟動
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);
t4.start();
t5.setPriority(8);
t5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"->"+Thread.currentThread().getPriority());
}
}
守護執行緒
執行緒分為使用者執行緒和守護(daemon)執行緒。
虛擬機器必須確保使用者執行緒執行完畢,不用等待守護執行緒執行完畢。如操作日誌,監控記憶體,垃圾回收等。
package com.thread.thread;
//測試守護執行緒
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);//預設false是表示使用者執行緒 正常的執行緒都是使用者執行緒
thread.start();//啟動守護執行緒
new Thread(you).start();//使用者執行緒啟動
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("活著!");
}
System.out.println("-----goodbye world!");
}
}
執行緒同步
使用場景:出現併發,多個執行緒操作同一個資源。
處理多執行緒問題時,多個執行緒訪問同一個物件,並且某個執行緒還想修改這個物件,這個時候我們就需要執行緒同步,執行緒同步就是一種等待機制,多個需要同時訪問此物件的執行緒進入物件的等待池,形成佇列,等待前面執行緒使用完畢,下一個執行緒再使用。
由於同一程序的多個執行緒共享同一塊儲存空間,在帶來方便的同時,也帶來了訪問衝突的問題,為了保證資料在方法中被訪問的正確性,在訪問時加入鎖機制(synchronized),當一個執行緒獲得物件的排它鎖,獨佔資源,其他執行緒必須等待 ,事後釋放鎖。
存在的問題:一個執行緒有鎖會導致其它所有需要此鎖的執行緒掛起;在多執行緒競爭下,加鎖和釋放鎖會導致比較多的上下文切換和排程延時,引起效能問題;如果一個優先順序高的等待一個優先順序低的執行緒釋放鎖會導致優先順序倒置,引起效能問題。
不安全案例:
買票
package com.thread.thread;
//買票
public class UnSafeTicket{
public static void main(String[] args) {
ByTicket station = new ByTicket();
new Thread(station,"小周").start();
new Thread(station,"小張").start();
new Thread(station,"小秦").start();
}
class ByTicket implements Runnable{
private int ticketnums = 10;
boolean flag = true;
@Override
public void run() {
//標誌位
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
//判斷是否有票
if (ticketnums<=0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketnums--+"張票");
}
}
銀行取錢
package com.thread.thread;
//不安全的取錢
public class UnSafeBank {
public static void main(String[] args) {
//賬戶
Account account = new Account(100, "基金");
Drawing you = new Drawing(account,50,"你");
Drawing girlFruend = new Drawing(account,100,"girlFriend");
you.start();
girlFruend.start();
}
}
class Account{
int money;//餘額
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//銀行模擬取款
class Drawing extends Thread{
Account account;//賬戶
int drawingMoney;
//現在手裡多少錢
int nowMoney;
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取錢
@Override
public void run() {
//判斷餘額
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"餘額不足,無法取出");
return;
}
//放大問題的發生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡內餘額
account.money = account.money-drawingMoney;
//手裡的錢
nowMoney = nowMoney+drawingMoney;
System.out.println(account.name+"餘額為:"+account.money);
//Thread.currentThread().getName()這裡等價於this.getName()
System.out.println(this.getName()+"手裡的錢:"+nowMoney);
}
}
不安全的集合
package com.thread.thread;
import java.util.ArrayList;
import java.util.List;
//執行緒不安全的集合 list
public class UnSafeList throws Exception{
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(3000);
System.out.println(list.size());
}
}
同步方法及同步塊
由於我們可以通過priviate關鍵字來保證資料物件只能被方法訪問,所以我們只需要針對方法提出一套機制,即synchronized關鍵字。它有兩種用法:synchronized方法和synchronized程式碼塊。
synchronized方法控制對“物件”的訪問,每個物件對應一把鎖,每個synchronized方法都必須獲得呼叫該方法的物件的鎖才能執行,否則執行緒會阻塞,方法一旦執行,就獨佔該鎖,直到該方法返回才釋放鎖,後面被阻塞的執行緒才能獲得這個鎖,繼續執行。
注意:將一個大的方法宣告為synchronized會影響效率。
同步方法:public synchronized void method(int args){............}
同步塊:synchronized(obj){....................}
obj稱之為同步監視器,obj可以是任意物件,但是推薦使用共享資源作為同步監視器。同步方法中無需指定同步監視器,因為同步方法中同步監視器就是this,就是這個物件本身。
同步監視器的執行過程:
- 第一個程式訪問,鎖定同步監視器,執行其中程式碼。
- 第二個程式訪問,發現同步監視器被鎖定,無法訪問。
- 第一個執行緒訪問完畢:解鎖同步監視器。
- 第二個執行緒訪問,發現同步監視器沒有鎖,然後鎖定並訪問。
銀行取錢加synchronized
package com.thread.thread;
//不安全的取錢
public class UnSafeBank {
public static void main(String[] args) {
//賬戶
Account account = new Account(100, "基金");
Drawing you = new Drawing(account,50,"你");
Drawing girlFruend = new Drawing(account,100,"girlFriend");
you.start();
girlFruend.start();
}
}
class Account{
int money;//餘額
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//銀行模擬取款
class Drawing extends Thread{
Account account;//賬戶
int drawingMoney;//取多少錢
//現在手裡多少錢
int nowMoney;
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取錢
@Override
public void run() {
//判斷餘額
//加鎖 鎖有增刪改的物件
synchronized (account){
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"餘額不足,無法取出");
return;
}
//放大問題的發生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡內餘額
account.money = account.money-drawingMoney;
//手裡的錢
nowMoney = nowMoney+drawingMoney;
System.out.println(account.name+"餘額為:"+account.money);
//Thread.currentThread().getName()這裡等價於this.getName()
System.out.println(this.getName()+"手裡的錢:"+nowMoney);
}
}
}
集合加synchronized
package com.thread.thread;
import java.util.ArrayList;
import java.util.List;
//執行緒不安全的集合 list
public class UnSafeList {
public static void main(String[] args) throws Exception {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(3000);
System.out.println(list.size());
}
}
死鎖
死鎖:多個執行緒各自佔有一些公共資源,並且互相等待其他執行緒佔有的資源才能執行,而導致兩個或多個執行緒都在等待對方釋放資源,都停止執行的情況,某一個同步塊同時擁有“兩個以上物件的鎖”時,就會發生死鎖的問題。
產生死鎖的四個必要條件:
- 互斥條件:一個資源每次只能被一個程序使用。
- 請求與保持條件:一個程序因請求資源而阻塞時,對方獲得的資源保持不放。
- 不剝奪條件:程序已獲得的資源,在未使用完之前,不能強行剝奪。
- 迴圈等待條件:若干資源之間形成一種頭尾相接的迴圈等待資源的關係。
只要破壞上面任意一個或者多個條件就能避免死鎖的發生。
以下一個程式碼塊中有兩個物件的鎖會產生死鎖(將其中一個鎖放到程式碼塊外即可避免死鎖)
package com.thread.thread;
//死鎖 多個執行緒互相抱著對方所需的資源,形成死鎖
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0,"ZZ");
Makeup g2 = new Makeup(1,"RR");
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+"獲得鏡子的鎖");
}
}
}else {
synchronized (mirror){//獲得鏡子的鎖
System.out.println(this.girlName+"獲得鏡子的鎖");
Thread.sleep(2000);
synchronized (lipstick){
System.out.println(this.girlName+"獲得口紅的鎖");
}
}
}
}
}
Lock(鎖)
從JDK5.0開始,Javat提供了更強大的執行緒同步機制——通過顯是定義同步鎖物件來實現同步,同步鎖使用lock物件充當。
Java.util.concurrent.locks. Lock介面是控制多個執行緒對共享資源進行訪問的工具;鎖提供了對共享資源的獨佔訪問,每次只能由一個執行緒對Lock物件加鎖,執行緒開始訪問共享資源之前應先獲得Lock物件。
ReentrantLock(可重入鎖)類中實現了Lock,它擁有與synchronized相同的併發性和記憶體語義,在實現執行緒安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖,釋放鎖。
package com.thread.thread;
import java.util.concurrent.locks.ReentrantLock;
//測試Lock鎖
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketunms = 10;
//定義Lock鎖
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();//加鎖
try{
if (ticketunms<=0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketunms--);
}else {
break;
}
}finally {
lock.unlock();//解鎖
}
}
}
}
synchronized和Lock的區別:
- Lock式顯式鎖(手動開啟和關閉鎖 ),synchronized是隱式鎖,出了作用域自動釋放。
- Lock只有程式碼塊鎖,synchronized有程式碼塊鎖和方法鎖。
- 使用Lock鎖,JVM將花費較少的時間來排程執行緒,效能更好,並且擁有更好的拓展性(提供更多的子類)。
- 優先使用順序:Lock>同步程式碼塊(已經進入了方法體,分配了相應資源)>同步方法(在方法體外)
生產者和消費者
生產者和消費者共享同一個資源,並且生產者和消費者之間相互依賴,互為條件。
對於生產者,沒有生產產品之前,要通知消費者等待,而生產了產品之後,需要通知消費者馬上消費。
對於消費者,在消費之後,要通知消費者已經結束消費,需要勝場新的產品以供消費。
在生產者和消費者問題中,僅有synchronized是不夠的,synchronized可阻止併發更新同一個資源,實現了同步。synchronized不能用來實現不同執行緒之間的訊息傳遞(通訊)。
解決執行緒通訊問題的幾個方法(均是object類的方法,都只能在同步方法或同步程式碼塊中使用,否則會丟擲異常):
- wait():表示執行緒一直等待,直到其它執行緒通知,與sleep不同,會釋放鎖。
- wait(long timeout:指定等待的毫秒數。
- notify():喚醒一個處於等待的執行緒。
- notifyAll():喚醒同一個物件上所有呼叫wait()的執行緒,優先級別搞得優先排程。
管程法
併發協作模型“生產者/消費者模式”--->管程法
- 生產者:負責生成資料的模組(可能是方法,物件,程序,執行緒)。
- 消費者:負責處理資料的模組(可能是方法,物件,程序,執行緒)。
- 緩衝區:消費者不能直接使用生產者的資料,它們之間有個緩衝區。
生產者將生產好的資料放入緩衝區,消費者從緩衝區拿出資料。
package com.thread.thread;
//測試生產者消費者模型-->利用緩衝區解決:管程法
//生產者 消費者 產品 緩衝區
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生產者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container=container;
}
//生產
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生產了"+i+"只");
}
}
}
//消費者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container=container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消費了"+container.pop().id+"只");
}
}
}
//產品
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
//緩衝區
class SynContainer{
//需要一個容器大小
Chicken[] chinken = new Chicken[10];
//容器計數器
int count = 0;
//生產者放入產品
public synchronized void push(Chicken chicken){
//如果容器滿了等待消費者消費
if (count==chinken.length){
//需要通知消費者消費 生產等待
try{
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果沒有滿就放入產品
chinken[count]=chicken;
count++;
//通知消費者消費
this.notifyAll();
}
//消費者消費產品
public synchronized Chicken pop(){
//判斷能否消費
if (count==0){
//等待生產者生產
try{
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消費
count--;
Chicken chicken = chinken[count];
//通知生產者生產
this.notifyAll();
return chicken;
}
}
訊號燈法
package com.thread.thread;
//測試訊號燈法 標誌位解決
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生產者 演員
class Player extends Thread{
TV tv;
public Player(TV tv){
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 24; i++) {
if (i%2==0){
this.tv.play("快樂大本營");
}else {
this.tv.play("抖音");
}
}
}
}
//消費者 觀眾
class Watcher extends Thread{
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 24; i++) {
tv.watch();
}
}
}
//節目
class TV{
//演員表演 觀眾等待
//觀眾觀看 演員等待
String voice;//節目
boolean flag = true;
//表演
public synchronized void play(String voice){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演員表演了:"+voice);
//通知觀眾觀看
this.notify();
this.voice = voice;
this.flag = !this.flag;
}
//觀看
public synchronized void watch(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("觀看了:"+voice);
//通知表演
this.notifyAll();
this.flag = !this.flag;
}
}
執行緒池
經常建立和銷燬,使用量特別大的資源,比如併發情況下的執行緒,對效能的影響特別大。
執行緒池:提前建立好多個執行緒,放入執行緒池中,使用時直接獲取,使用完放回池中,可以避免頻繁建立與銷燬,實現重複利用。
優點:
- 提高響應速度(減少建立新執行緒的時間)
- 降低資源消耗(重複利用執行緒池中的執行緒,不需要每次都建立)
- 便於執行緒管理
- corePoolSize:核心池大小
- maximumPoolSize:最大核心數
- keepAlive Time:執行緒沒有任務時最多保持多長時間後終止
ExecutorService:真正的執行緒池介面;常見子類ThreadPoolExecutor。
- void execute(Runnable command):執行任務/命令,沒有返回值,一般用來執行Runnable。
Future submit(Callable task):執行任務,有返回值,一般用來執行Callable。 - void shutdown():關閉連線池。
Execoturs:工具類,執行緒池的工廠類,用於建立並返回不同型別的執行緒池。
package com.thread.thread;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//測試執行緒池
public class TestPool {
public static void main(String[] args) {
//建立執行緒池 引數執行緒為池的大小
ExecutorService service = Executors.newFixedThreadPool(10);
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() {
System.out.println(Thread.currentThread().getName());
}
}