一、線程簡介
請看:https://blog.csdn.net/qq_33157666/article/details/103949005
二、線程狀態
請看:https://blog.csdn.net/qq_33157666/article/details/103949045
三、線程同步
請看:https://blog.csdn.net/qq_33157666/article/details/103949120
四、死鎖
多個線程各自佔有一些共享資源,並且互相等待其他線程佔有的資源才能運行,而導致兩個或者多個線程都在等待對方釋放資源,都停止執行的情形。某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能會發生“死鎖”的問題。
示例:
/**
* 死鎖:多個線程互相抱着對方需要的資源,然後形成僵持
*
*
*/
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來保證只有一份
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
int choice;//選擇
String girlName;//使用化妝品的人
public Makeup(int choice,String girlName) {
this.choice=choice;
this.girlName=girlName;
}
@Override
public void run() {
//化妝
try {
makeup();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//化妝,互相持有對方的鎖,就是需要拿到對方的資源
private void makeup() throws InterruptedException{
if(choice==0){
synchronized(lipstick){//獲得口紅的鎖
System.out.println(this.girlName+"獲得口紅的鎖");
Thread.sleep(1000);
synchronized (mirror) {//一秒鐘後獲得鏡子的鎖,此時是在包住了對方鎖
System.err.println(this.girlName+"獲得鏡子的鎖");
}
}
}else{
synchronized(mirror){//獲得鏡子的鎖
System.out.println(this.girlName+"獲得鏡子的鎖");
Thread.sleep(2000);
synchronized (lipstick) {//一秒鐘後獲得口紅的鎖
System.err.println(this.girlName+"獲得口紅的鎖");
}
}
}
}
}
運行結果:
可以看到運行結果是處於僵持的狀態,程序卡死。
解決辦法:
將上面代碼中的makeup方法改爲:
//化妝,互相持有對方的鎖,就是需要拿到對方的資源
private void makeup() throws InterruptedException{
if(choice==0){
synchronized(lipstick){//獲得口紅的鎖
System.out.println(this.girlName+"獲得口紅的鎖");
Thread.sleep(1000);
}
synchronized (mirror) {//一秒鐘後獲得鏡子的鎖,
System.err.println(this.girlName+"獲得鏡子的鎖");
}
}else{
synchronized(mirror){//獲得鏡子的鎖
System.out.println(this.girlName+"獲得鏡子的鎖");
Thread.sleep(2000);
}
synchronized (lipstick) {//一秒鐘後獲得口紅的鎖
System.err.println(this.girlName+"獲得口紅的鎖");
}
}
}
小結:
產生死鎖的四個必要條件:
- 互斥條件:一個資源每次只能被一個進程使用。
- 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
- 不剝奪條件:進程已獲得的資源,在爲使用完之前,不能強行剝奪。
- 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。
五、線程通信及線程池
5.1 線程通信
應用場景:生產者和消費者問題
假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中的產品取走消費、
如果倉庫中沒有產品,則生產者將產品放入倉庫,否則停止生產並等待,直到倉庫中的產品被消費者取走爲止;
如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費並等待,直到倉庫中再次放入產品爲止。
java提供了幾個方法解決線程之間的通信爲題:
wait():表示線程一直等待,直到其他線程通知,與sleep不同,會釋放鎖
wait(long timeout):指定等待的毫秒數
notify():喚醒一個處於等待狀態的線程
notifyAll():喚醒同一個對象上所有調用wait()方法的線程,優先級較高的線程優先調度
方法一:利用緩衝區解決:管程法
管程法:
生產者:負責生產數據的模塊(可能是方法、對象、線程、進程);
消費者:負責處理數據的模塊(可能是方法、對象、線程、進程);
緩衝區:消費者不能直接使用生產這的數據,他們之間有個"緩衝區";
生產者將生產好的數據放入緩衝區,消費者從緩衝區拿出數據。
類似於我們去肯德基買漢堡,如果有做好的漢堡,我們去了可以直接買,如果沒有需要等大廚做好,在買。
示例:
/**
* 併發協作模型"生產者/消費者模式"--》管程法
* 生產者:負責生產數據的模塊(可能是方法、對象、線程、進程)
* 消費者:負責處理數據的模塊(可能是方法、對象、線程、進程)
* 緩衝區:消費者不能直接使用生產這的數據,他們之間有個"緩衝區"
* 生產者將生產好的數據放入緩衝區,消費者從緩衝區拿出數據
*
*/
public class TestPC {
public static void main(String[] args) {
SynContaniner contaniner=new SynContaniner();
new Productor(contaniner).start();
new Comsumer(contaniner).start();
}
}
//生產者
class Productor extends Thread{
SynContaniner container;
public Productor(SynContaniner contaniner){
this.container=contaniner;
}
//生產
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生產了"+i+"只雞");
}
}
}
//消費者
class Comsumer extends Thread{
SynContaniner container;
public Comsumer(SynContaniner contaniner){
this.container=contaniner;
}
//消費
@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 SynContaniner{
//需要一個容器大小
Chicken[] chickens=new Chicken[10];
//容器計數器
int count=0;
//生產者放入產品
public synchronized void push(Chicken chicken){
//如果容器滿了,就需要等待消費者消費
//System.out.println("生產者:"+count+"---"+chickens.length);
if(count==chickens.length){
//通知消費者消費,生產者等待
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果沒有滿,我們就需要丟入產品
chickens[count]=chicken;
count++;
//可以通知消費者消費了
this.notifyAll();
}
//消費者消費產品
public synchronized Chicken pop(){
//System.out.println("消費者:"+count);
//判斷能否消費
//System.out.println("count:----"+count);
if(count==0){
//等待生產者生產,消費者等待
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果可以消費
count--;
Chicken chicken=chickens[count];
//吃完了,通知生產者生產
this.notifyAll();
return chicken;
}
}
方法二:信號燈法,標誌位解決
設置一個標誌位,如果標誌位爲true,則生產者生產,生產完把標誌位置爲false,否則生產者等待;
標誌位爲false,消費者消費,消費完把標誌位置爲true,否則等待。
示例:
//測試生產者消費者問題2:信號燈法,標誌位解決
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<20;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<20;i++){
tv.watch();
}
}
}
//產品->節目
class Tv{
//演員表演,觀衆等待
//觀衆觀看,演員等待
String voice;//演員的節目
boolean flag=true;
//表演
public synchronized void play(String voice){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("演員表演了:"+voice);
//通知觀衆觀看
this.notifyAll();//通知
this.voice=voice;
this.flag=!this.flag;
}
//觀看
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("觀看了:"+voice);
//通知演員表演
this.notifyAll();
this.flag=!this.flag;
}
}
5.2 線程池
背景:經常創建和銷燬線程,使用量特別大的資源,比如併發情況下的線程,對性能影響很大。
思路:提前創建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創建銷燬、實現重複利用。類似生活中交通工具。
好處:
提高響應速度(減少了創建新線程的時間)
降低資源消耗(重複利用線程池中的線程,不需要每次都創建)
便於線程管理:corePoolSize:線程池的大小
maximumPoolSize:最大線程數
keepAliveTime:線程沒有任務時最多保持多長時間會終止
示例:
/**
* 測試線程池
*
*/
public class TestPool {
public static void main(String[] args) {
//1.創建服務,創建線程池
//newFixedThreadPool 參數爲:線程池大小
ExecutorService service=Executors.newFixedThreadPool(10);
//執行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}