一、死鎖
1.同步鎖 解決了線程安全問(wèn)題,但會(huì)造成性能低下,還可能會(huì)引發(fā)一個(gè)問(wèn)題(罕見(jiàn)):死鎖
2.例子
a.一個(gè)地痞子死后,來(lái)到了地獄,到了吃飯的時(shí)候,他發(fā)現(xiàn)飯桌上面的飯菜非常豐盛,然后好多人圍著一張大桌子,一起吃飯,飯菜都上齊了,非常美味,
他們非常餓,開(kāi)吃吧...但是只有兩支筷子,而且是兩個(gè)人同時(shí)各拿一支筷子...如果沒(méi)有人愿意共享自己的筷子,那么他們就只能餓著...看著...饞著...留著口水.煎熬著...
b.爸爸說(shuō):“給我成績(jī)單,就給你零花錢”,兒子說(shuō):“給我零花錢,就給你成績(jī)單”,如果兩人互不相讓...
3.多個(gè)線程之間彼此占用對(duì)方想使用的資源
4.要使用非常規(guī)的手段演示b
5.分析b案例
a.爸爸要成績(jī)單 和 兒子要零花錢 這兩個(gè)事情是同時(shí)發(fā)生的 多線程
b.爸爸是一個(gè)線程 兒子是一個(gè)線程
c.嵌套鎖 + 交叉鎖
public class Son extends Thread {
public void run() {
for (int i = 0; i < 3; i++) {
synchronized (MyLock.lock02_reportCart) {
System.out.println("son:兒子有成績(jī)單");
try {
com.afinalstone.MyLock.lock02_reportCart.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (MyLock.lock02_money) {
System.out.println("son:兒子想要爸爸的零花錢");
}
}
}
}
}
public class Father extends Thread {
public void run() {
for (int i=0; i<3; i++){
synchronized (MyLock.lock02_money) {
System.out.println("Father:爸爸有零花錢");
try {
MyLock.lock02_money.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (MyLock.lock02_reportCart) {
System.out.println("Father:爸爸想要兒子的成績(jī)單");
}
}
}
}
public class MyLock {
public static Object lock02_money = new Object();
public static Object lock02_reportCart = new Object();
}
public class TestFatherSon {
public static void main(String[] args) {
new Father().start();
new Son().start();
}
}
二、解決死鎖問(wèn)題
1.同步代碼塊,一旦某個(gè)線程執(zhí)行了這個(gè)同步代碼塊,該代碼塊會(huì)自動(dòng)上鎖,當(dāng)前線程把代碼塊里面所有的代碼全部執(zhí)行完,鎖會(huì)自動(dòng)解開(kāi)
2.解決思路:不能等著自然解鎖,而是有程序員主動(dòng)的、及時(shí)的釋放鎖(解鎖-開(kāi)鎖)
3.開(kāi)鎖的方法:wait() wait(long) 開(kāi)鎖 在Object類中
4.該方法的作用:
a.釋放鎖
b.讓當(dāng)前線程休息
5.sleep()方法也可以讓當(dāng)前線程休息,沒(méi)有釋放鎖的功能
6.任意對(duì)象都有一把鎖,也有解鎖的方法
三、notify方法
1.無(wú)參的wait()一旦執(zhí)行了,會(huì)造成當(dāng)前線程一直睡.....
2.notify() 喚醒某個(gè)被wait()的線程
3.自身無(wú)法喚醒自身
4.wait()和notify()必須使用的是同一把鎖
5.之前的同步代碼塊是解決線程安全問(wèn)題,還可以實(shí)現(xiàn)線程間的通信(打電話、發(fā)消息)
6.結(jié)合wait()和notify()并依靠同一把鎖,可以實(shí)現(xiàn)線程間的通信(可不是互相發(fā)消息)。
鎖相當(dāng)于中間人的作用,兩個(gè)線程必須用同一把鎖(認(rèn)識(shí)同一個(gè)中間人)
public class Thread1 extends Thread{
public void run() {
for (int i = 1; i <= 20; i++) {
synchronized (MyLock.lock01_thread) {
System.out.println(i);
if(i%5==0)
{
try {
MyLock.lock01_thread.notify();
MyLock.lock01_thread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Thread2 extends Thread{ //喚醒Thread1的線程
public void run(){
for(int j=0;j<10;j++){
synchronized (MyLock.lock01_thread) {
try {
MyLock.lock01_thread.notify();
System.out.println("喚醒");
MyLock.lock01_thread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class TestWaitNotify {
public static void main(String[] args) {
Thread1 t1=new Thread1();
t1.start();
Thread2 t2=new Thread2();
t2.start();
}
}
四、sleep方法使用實(shí)例
public class ThreadSleep {
public static void main(String args[]) {
MyThread thread = new MyThread();
thread.start();//調(diào)用start()方法啟動(dòng)新開(kāi)辟的線程
try {
/*Thread.sleep(10000);
sleep()方法是在Thread類里面聲明的一個(gè)靜態(tài)方法,因此可以使用Thread.sleep()的格式進(jìn)行調(diào)用
*/
/*MyThread.sleep(10000);
MyThread類繼承了Thread類,自然也繼承了sleep()方法,所以也可以使用MyThread.sleep()的格式進(jìn)行調(diào)用
*/
/*靜態(tài)方法的調(diào)用可以直接使用“類名.靜態(tài)方法名”
或者“對(duì)象的引用.靜態(tài)方法名”的方式來(lái)調(diào)用*/
MyThread.sleep(10000);
System.out.println("主線程睡眠了10秒種后再次啟動(dòng)了");
//在main()方法里面調(diào)用另外一個(gè)類的靜態(tài)方法時(shí),需要使用“靜態(tài)方法所在的類.靜態(tài)方法名”這種方式來(lái)調(diào)用
/*
所以這里是讓主線程睡眠10秒種
在哪個(gè)線程里面調(diào)用了sleep()方法就讓哪個(gè)線程睡眠,所以現(xiàn)在是主線程睡眠了。
*/
} catch (InterruptedException e) {
e.printStackTrace();
}
//thread.interrupt();//使用interrupt()方法去結(jié)束掉一個(gè)線程的執(zhí)行并不是一個(gè)很好的做法
thread.flag = false;//改變循環(huán)條件,結(jié)束死循環(huán)
/**
* 當(dāng)發(fā)生InterruptedException時(shí),直接把循環(huán)的條件設(shè)置為false即可退出死循環(huán),
* 繼而結(jié)束掉子線程的執(zhí)行,這是一種比較好的結(jié)束子線程的做法
*/
/**
* 調(diào)用interrupt()方法把正在運(yùn)行的線程打斷
相當(dāng)于是主線程一盆涼水潑上去把正在執(zhí)行分線程打斷了
分線程被打斷之后就會(huì)拋InterruptedException異常,這樣就會(huì)執(zhí)行return語(yǔ)句返回,結(jié)束掉線程的執(zhí)行
所以這里的分線程在執(zhí)行完10秒鐘之后就結(jié)束掉了線程的執(zhí)行
*/
}
}
class MyThread extends Thread {
boolean flag = true;// 定義一個(gè)標(biāo)記,用來(lái)控制循環(huán)的條件
public void run() {
/*
* 注意:這里不能在run()方法的后面直接寫(xiě)throw Exception來(lái)拋異常,
* 因?yàn)楝F(xiàn)在是要重寫(xiě)從Thread類繼承而來(lái)的run()方法,重寫(xiě)方法不能拋出比被重寫(xiě)的方法的不同的異常。
* 所以這里只能寫(xiě)try……catch()來(lái)捕獲異常
*/
while (flag) {
System.out.println("==========" + new Date().toLocaleString() + "===========");
try {
/*
* 靜態(tài)方法的調(diào)用格式一般為“類名.方法名”的格式去調(diào)用 在本類中聲明的靜態(tài)方法時(shí)調(diào)用時(shí)直接寫(xiě)靜態(tài)方法名即可。 當(dāng)然使用“類名.方法名”的格式去調(diào)用也是沒(méi)有錯(cuò)的
*/
// MyThread.sleep(1000);//使用“類名.方法名”的格式去調(diào)用屬于本類的靜態(tài)方法
sleep(1000);//睡眠的時(shí)如果被打斷就會(huì)拋出InterruptedException異常
// 這里是讓這個(gè)新開(kāi)辟的線程每隔一秒睡眠一次,然后睡眠一秒鐘后再次啟動(dòng)該線程
// 這里在一個(gè)死循環(huán)里面每隔一秒啟動(dòng)一次線程,每個(gè)一秒打印出當(dāng)前的系統(tǒng)時(shí)間
} catch (InterruptedException e) {
/*
* 睡眠的時(shí)一盤(pán)冷水潑過(guò)來(lái)就有可能會(huì)打斷睡眠
* 因此讓正在運(yùn)行線程被一些意外的原因中斷的時(shí)候有可能會(huì)拋被打擾中斷(InterruptedException)的異常
*/
return;
// 線程被中斷后就返回,相當(dāng)于是結(jié)束線程
}
}
}
}
五、join方法使用實(shí)例
public class TestThreadJoin {
public static void main(String args[]) {
MyThread_Join thread2 = new MyThread_Join("MyThread_Join");
// 在創(chuàng)建一個(gè)新的線程對(duì)象的同時(shí)給這個(gè)線程對(duì)象命名為mythread
thread2.start();// 啟動(dòng)線程
try {
thread2.join();// 調(diào)用join()方法合并線程,將子線程mythread合并到主線程里面
// 合并線程后,程序的執(zhí)行的過(guò)程就相當(dāng)于是方法的調(diào)用的執(zhí)行過(guò)程
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i <= 5; i++) {
System.out.println("I am main Thread");
}
}
}
class MyThread_Join extends Thread {
MyThread_Join(String s) {
super(s);
/*
* 使用super關(guān)鍵字調(diào)用父類的構(gòu)造方法
* 父類Thread的其中一個(gè)構(gòu)造方法:“public Thread(String name)”
* 通過(guò)這樣的構(gòu)造方法可以給新開(kāi)辟的線程命名,便于管理線程
*/
}
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("I am a\t" + getName());
// 使用父類Thread里面定義的
//public final String getName(),Returns this thread's name.
try {
sleep(1000);// 讓子線程每執(zhí)行一次就睡眠1秒鐘
} catch (InterruptedException e) {
return;
}
}
}
}
六、yield方法使用實(shí)例
public class TestThreadYield {
public static void main(String args[]) {
MyThread_Yield t1 = new MyThread_Yield("t1");
/* 同時(shí)開(kāi)辟了兩條子線程t1和t2,t1和t2執(zhí)行的都是run()方法 */
/* 這個(gè)程序的執(zhí)行過(guò)程中總共有3個(gè)線程在并行執(zhí)行,分別為子線程t1和t2以及主線程 */
MyThread_Yield t2 = new MyThread_Yield("t2");
t1.start();// 啟動(dòng)子線程t1
t2.start();// 啟動(dòng)子線程t2
for (int i = 0; i <= 5; i++) {
System.out.println("I am main Thread");
}
}
}
class MyThread_Yield extends Thread {
MyThread_Yield(String s) {
super(s);
}
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(getName() + ":" + i);
if (i % 2 == 0) {
yield();// 當(dāng)執(zhí)行到i能被2整除時(shí)當(dāng)前執(zhí)行的線程就讓出來(lái)讓另一個(gè)在執(zhí)行run()方法的線程來(lái)優(yōu)先執(zhí)行
/*
* 在程序的運(yùn)行的過(guò)程中可以看到,
* 線程t1執(zhí)行到(i%2==0)次時(shí)就會(huì)讓出線程讓t2線程來(lái)優(yōu)先執(zhí)行
* 而線程t2執(zhí)行到(i%2==0)次時(shí)也會(huì)讓出線程給t1線程優(yōu)先執(zhí)行
*/
}
}
}
}
七、生產(chǎn)者消費(fèi)者模式
1.研究什么問(wèn)題?農(nóng)夫不停的摘水果放到框里,小孩不停的從框里拿水果吃
2.生產(chǎn)速度 跟 消費(fèi)速度
3.農(nóng)夫和小孩都需要依靠wait()和notify()調(diào)節(jié)進(jìn)度
public class Child extends Thread {
public void run() {
while (true) {
synchronized (Factory.numOfFactory) {
if(Factory.numOfFactory.size()==0)
{
try {
Factory.numOfFactory.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Factory.numOfFactory.remove(0);
Factory.numOfFactory.notify();
System.out.println("小孩吃了一個(gè)水果,還有" + Factory.numOfFactory.size() + "個(gè)水果");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class FruitFactory extends Thread{
public void run(){
while (true) {
synchronized (Factory.numOfFactory) {
if(Factory.numOfFactory.size()>=10)
{
try {
Factory.numOfFactory.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Factory.numOfFactory.add(1);
Factory.numOfFactory.notify();
System.out.println("水果場(chǎng)生產(chǎn)一個(gè)水果,現(xiàn)在有" + Factory.numOfFactory.size()
+ "個(gè)水果");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class Factory {
public static ArrayList<Integer> numOfFactory = new ArrayList<>(); //規(guī)定長(zhǎng)度不能超過(guò)30
}
public class TestChild_FruitFactory {
public static void main(String[] args) throws Exception {
new FruitFactory().start();
new Child().start();
}
}
八、單例設(shè)計(jì)模式
1.實(shí)例化 實(shí)例
2.單個(gè)實(shí)例 只能創(chuàng)建出來(lái)一個(gè)對(duì)象
3.步驟
a.限制構(gòu)造方法
b.把創(chuàng)建對(duì)象的代碼放到一個(gè)靜態(tài)的方法中并返回這個(gè)對(duì)象
c.增加一個(gè)靜態(tài)屬性,當(dāng)該屬性的值為null時(shí)才創(chuàng)建對(duì)象
4.懶漢式這種寫(xiě)法是否存在線程安全問(wèn)題?會(huì)存在
5.只需要把getInstance方法改為同步方法即可解決線程安全問(wèn)題
6.餓漢式?jīng)]有線程安全問(wèn)題,因此推薦使用
7.一般情況,懶漢式更常見(jiàn)
//餓漢式
public class Student_hunger {
private static Student_hunger s=new Student_hunger();
private Student_hunger() {
}
public static Student_hunger getInstance()
{
return s;
}
}
//懶漢式
public class Student_lazybones {
private static Student_lazybones s=null;
private Student_lazybones() {
}
public synchronized static Student_lazybones getInstance()
{
if(s==null)
{
s=new Student_lazybones();
}
return s;
}
}
public class ThreadStudent extends Thread{
public void run(){
for (int i = 0; i < 10; i++) {
Student_lazybones s= Student_lazybones.getInstance();
System.out.println(s.hashCode());
}
}
}
public class Main {
public static void main(String[] args) {
testLazyBones();
testHunger();
testThreadStudent();
}
private static void testLazyBones(){
Student_lazybones s1= Student_lazybones.getInstance();
System.out.println(s1.hashCode());
Student_lazybones s2= Student_lazybones.getInstance();
System.out.println(s2.hashCode());
}
private static void testHunger(){
Student_hunger ss1= Student_hunger.getInstance();
System.out.println(ss1.hashCode());
Student_hunger ss2= Student_hunger.getInstance();
System.out.println(ss2.hashCode());
}
private static void testThreadStudent(){
for (int i = 0; i < 3; i++) {
new ThreadStudent().start();
}
}
}
項(xiàng)目地址:傳送門