死鎖
什么是死鎖
- 死鎖是指兩個或兩個以上的線程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象吵聪,
若無外力作用,它們都將無法推進(jìn)下去。 - 此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖匪傍,這些永遠(yuǎn)在互相等待的線程稱為死鎖線程。
產(chǎn)生死鎖的四個必要條件:
- 互斥條件:一個資源每次只能被一個線程使用
- 請求與保持條件:一個線程因請求資源而阻塞時觉痛,對已獲得的資源保持不放
- 不剝奪條件:線程已獲得的資源役衡,在未使用完之前,不能強(qiáng)行剝奪
- 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相連的循環(huán)等待資源關(guān)系
代碼示例
寫一個死鎖代碼薪棒,只有會寫手蝎,才會在以后的開發(fā)中注意這個事。
public class DeadLockTest01 {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
MyThread1 thread1 = new MyThread1(o1,o2);
MyThread2 thread2 = new MyThread2(o1,o2);
thread1.start();
thread2.start();
}
}
class MyThread1 extends Thread{
Object obj1;
Object obj2;
public void run(){
synchronized (obj1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
}
}
}
public MyThread1(Object obj1, Object obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
}
class MyThread2 extends Thread{
Object obj1;
Object obj2;
public void run(){
synchronized (obj2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
}
}
}
public MyThread2(Object obj1, Object obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
}
守護(hù)線程
-
java語言中線程分為兩大類
- 用戶線程:
- 主線程main方法就是一個用戶線程
- 守護(hù)線程:
- 最具有代表性的:垃圾回收線程(守護(hù)線程)俐芯。
- 用戶線程:
-
守護(hù)線程的特點:
- 一般守護(hù)線程是一個死循環(huán)棵介,所有的用戶線程結(jié)束了,守護(hù)線程才會自動結(jié)束吧史。
-
守護(hù)線程用在什么地方邮辽?
每天00:00的時候系統(tǒng)數(shù)據(jù)自動備份。 這個需要用到定時器,并且我們可以將定時器設(shè)置為守護(hù)線程吨述。一直在那守著岩睁, 每到00:00的時候就備份一次;所有的用戶線程結(jié)束了揣云,守護(hù)線程自動退出捕儒,沒 必要繼續(xù)數(shù)據(jù)備份了。
代碼示例
public class DaemonThreadTest01 {
public static void main(String[] args) {
BakDataThread bakDataThread = new BakDataThread();
bakDataThread.setName("守護(hù)線程");
// 啟動線程之前邓夕,將該線程設(shè)置為守護(hù)線程
bakDataThread.setDaemon(true);
bakDataThread.start();
for (int i = 0;i < 10; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
class BakDataThread extends Thread{
public void run(){
int i= 0;
// 即使是死循環(huán)刘莹,但由于該線程是守護(hù)者,當(dāng)用戶線程結(jié)束焚刚,守護(hù)線程自動終止点弯。
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "-->" + i++);
}
}
}
定時器
-
定時器的作用:
- 間隔特定的時間,執(zhí)行特定的程序矿咕。
- 在實際開發(fā)中蒲拉,沒隔多久執(zhí)行一段特定的程序,這是常見的痴腌。
-
java中實現(xiàn)定時器有多種方式:
- 1雌团,可以使用sleep方法,睡眠士聪,設(shè)置睡眠時間锦援;每到這個時間點醒來,執(zhí)行任務(wù)剥悟;
這種方式是最原始的定時器灵寺。(比較low) - 2,使用java類庫中寫好的定時器区岗,java.util.Timer略板,可以直接拿來用。這種方式
在目前的開發(fā)中也很少用慈缔,因為現(xiàn)在有很多高級框架都是支持定時任務(wù)的叮称。 - 3,例如:現(xiàn)在常用的spring框架中提供的springTask框架藐鹤,這個框架只有進(jìn)行簡單
的配置瓤檐,就可以完成定時器任務(wù)。
- 1雌团,可以使用sleep方法,睡眠士聪,設(shè)置睡眠時間锦援;每到這個時間點醒來,執(zhí)行任務(wù)剥悟;
代碼示例
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerThreadTest01 {
public static void main(String[] args) {
// 創(chuàng)建定時器對象
Timer timer = new Timer();
// Timer timer = new Timer(true); //守護(hù)線程寫法
//指定定時任務(wù)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = null;
try {
firstTime = sdf.parse("2021-06-03 17:53:15");
} catch (ParseException e) {
e.printStackTrace();
}
// schedule(定時任務(wù),開始執(zhí)行時間,間隔多久執(zhí)行一次);
timer.schedule(new LogTaskTimer(),firstTime,1000 * 10);
}
}
// 編寫一個定時任務(wù)
class LogTaskTimer extends TimerTask{
@Override
public void run() {
// 這里編寫需要執(zhí)行的任務(wù)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(new Date());
System.out.println(time + "定時任務(wù)開始執(zhí)行了");
}
}
實現(xiàn)線程的第三種方式:
-
實現(xiàn)Callable接口娱节。(JDK8新特性)
- 在java.util.concurrent包下挠蛉,屬于java并發(fā)包,老版JDK沒有肄满。
- 優(yōu)點:這種方式實現(xiàn)的線程可以獲取線程執(zhí)行的返回值谴古。
- 之前講解的那兩種方式是無法獲取線程返回值的质涛,因為run方法返回的是void。
- 缺點:效率比較低掰担,在獲取某個線程執(zhí)行結(jié)果的時候汇陆,當(dāng)前線程受阻塞,效率較低恩敌。
代碼示例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest09 {
public static void main(String[] args) {
// 第一步:創(chuàng)建一個"未來任務(wù)類"對象
MyThreadCallable callable = new MyThreadCallable();
FutureTask task = new FutureTask(callable);
//創(chuàng)建線程對象
Thread t = new Thread(task);
t.setName("task");
t.start();// 啟動線程
// 在main方法中 獲取t線程的返回結(jié)果
// task.get()方法的執(zhí)行會阻塞當(dāng)前線程
try {
Object obj = task.get();
System.out.println(Thread.currentThread().getName() + "線程返回值是" + obj);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// task.get()方法的執(zhí)行會阻塞main方法下面程序的執(zhí)行
// main方法下面的程序想要執(zhí)行,必須等待get()方法執(zhí)行結(jié)束横媚,
// get()方法可能需要很久纠炮,因為get()方法是為了拿另一個線程的執(zhí)行結(jié)果。
System.out.println("hello world");
}
}
class MyThreadCallable implements Callable{
@Override
public Object call() throws Exception {
// call()方法就相當(dāng)于run方法灯蝴。只不過這個有返回值
// 線程執(zhí)行一個任務(wù)恢口,執(zhí)行之后可能會有一個執(zhí)行結(jié)果
// 模擬執(zhí)行
System.out.println("call method begin");
Thread.sleep(1000 * 5);
System.out.println("call method end");
int a = 100;
int b = 200;
return a + b ;
}
}
關(guān)于Object類中的wait和notify方法(生產(chǎn)者和消費者模式!)
-
什么是"生產(chǎn)者和消費者模式"穷躁?
- 生產(chǎn)線程負(fù)責(zé)生產(chǎn)耕肩,消費線程負(fù)責(zé)消費。
- 生產(chǎn)線程和消費線程要達(dá)到均衡问潭。
- 這是一種特殊的業(yè)務(wù)需要猿诸,在這種特殊的情況下需要使用wait方法和notify方法。
wait方法和notify方法不是線程對象的方法狡忙,是java對象都有的方法梳虽,是Object類自帶的。
wait方法和notify方法是建立在synchronized線程同步的基礎(chǔ)之上灾茁,因為多線程要同時操作一個廠庫窜觉,有線程安全問題。
-
wait方法的作用:
Object o = new Object(); o.wait();
- 表示讓正在o對象上活動的線程進(jìn)入等待狀態(tài)北专,并且釋放掉線程之前占用o對象的鎖禀挫;直到被喚醒為止。
-
notify方法的作用:
- notify方法的調(diào)用可以讓正在o對象上等待的線程喚醒拓颓,只是通知语婴,不會釋放o對象上之前占有的鎖。
- notifyAll方法:喚醒o對象上處于等待的所有線程驶睦。
代碼示例
模擬需求:
倉庫采用list集合腻格,list集合中假設(shè)只能存儲一個元素;一個元素表示倉庫滿了啥繁,
如果list集合中元素個數(shù)是0菜职,就表示倉庫空了;保證list集合永遠(yuǎn)都是最多存儲一個元素旗闽。
做到:生產(chǎn)一個消費一個酬核。
import java.util.ArrayList;
import java.util.List;
public class ThreadTest10 {
public static void main(String[] args) {
//創(chuàng)建一個共享對象
List list = new ArrayList();
// 創(chuàng)建兩個線程對象
// 生產(chǎn)者線程
Thread t1 = new Thread(new Producer(list));
// 消費者線程
Thread t2 = new Thread(new Consumer(list));
t1.setName("生產(chǎn)者線程");
t2.setName("消費者線程");
// 啟動線程
t1.start();
t2.start();
}
}
// 生產(chǎn)線程
class Producer implements Runnable{
// 倉庫
private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() { //一直生產(chǎn)
while (true){
// 給倉庫對象list加鎖
synchronized (list){
if (list.size() > 0){// 倉庫已滿
try {
list.wait(); // 當(dāng)前線程進(jìn)入等待狀態(tài)蜜另,并釋放Producer之前占有l(wèi)ist集合的鎖
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序執(zhí)行到這里說明倉庫是空的,可以生產(chǎn)
Object obj = new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName() + "-->" + obj);
// 喚醒消費者進(jìn)行消費
list.notify();
// list.notifyAll();
}
}
}
}
// 消費線程
class Consumer implements Runnable{
// 倉庫
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {//一直消費
while (true){
synchronized (list) {
if (list.size() == 0) {// 倉庫空了
try {
list.wait(); // 消費者線程等待嫡意,釋放掉list集合的鎖
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序執(zhí)行到此處說明倉庫中有數(shù)據(jù)举瑰,進(jìn)行消費。
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName() + "-->" + obj);
//喚醒生產(chǎn)者生產(chǎn)
list.notify();
// list.notifyAll();
}
}
}
}