2、線程、進程、多線程:
程序、進程、線程的關係
程序是靜態的,程序跑起來成爲進程,進程是系統分配資源的單位,進程包含若干個線程,一個進程至少包含一個線程,否則沒有存在的意義
一個進程內的線程之間是可以共享資源的。
線程:線程也存在併發、並行(單個CPU時間片輪轉、一個時間點,多個CPU上的真同時)
一些概念:
線程就是獨立的執行路徑
在程序運行時,即使沒有自己創建線程,後臺也會有多個線程,如主線程、GC線程;
main()稱之爲主線程,是系統的入口,用於執行整個程序;
在一個進程中,如果開闢了多個線程,線程的運行由調度器安排調度,調度器是與操作系統密切相關的,先後順序是不能人爲干預的;
對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制;
線程會帶來額外的開銷,如cpu調度時間,併發控制開銷;
每個線程在自己的工作內存交互,內存控制不當會造成數據不一致。
3、繼承Thread類
創建線程的三種方式
創建線程方式一:
繼承Thread類,重寫run()方法,調用start開啓線程
總結:線程開啓不一定立即執行,有CPU調度執行
不建議使用繼承Thread類:避免OOP單繼承侷限性
public class TestThread1 extends Thread{
@Override
public void run() {
//run方法線程體
for (int i = 0; i < 200; i++) {
System.out.println("我在看代碼----"+ i);
}
}
public static void main(String[] args) {
//main線程,主線程
//創建線程對象
TestThread1 testThread1 = new TestThread1();
//調用start()方法開啓線程
testThread1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("我在學習多線程-------"+ i);
}
}
}
4、網圖下載
package com.fang.demo01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//練習Thread,實現多線程同步下載圖片
public class TestThread2 extends Thread{
private String url; //網絡圖片地址
private String name; //保存的文件名
public TestThread2(String url, String name){
this.url = url;
this.name = name;
}
//下載圖片線程的執行體
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下載量文件名爲:"+name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("https://i0.hdslb.com/bfs/sycp/creative_img/202006/6d586392ee5c3b537d5b5c832fc02c19.jpg","1.jpg");
TestThread2 t2 = new TestThread2("https://i0.hdslb.com/bfs/sycp/creative_img/202002/6bc0da8a84ece9e200fce8b7ec8b08c8.png","2.png");
TestThread2 t3 = new TestThread2("https://i0.hdslb.com/bfs/sycp/creative_img/202006/dde761ef0291bc064d8ba955e0b266a7.jpg","3.jpg");
t1.start();
t2.start();
t3.start();
}
}
//下載器
class WebDownloader{
//下載方法
public void downloader(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO異常,downloader方法出現問題");
}
}
}
5、實現Runnable
定義MyRunnable類實現Runnable接口
實現run()方法,編寫線程執行體
創建線程對象,調用start()方法啓動線程
創建線程方式二:
推薦使用:避免單繼承侷限性,靈活方便,方便同一個對象被多個線程使用
package com.fang.demo01;
public class TestThread3 implements Runnable{
@Override
public void run() {
//run方法線程體
for (int i = 0; i < 200; i++) {
System.out.println("我在看代碼----"+ i);
}
}
public static void main(String[] args) {
//main線程,主線程
//創建Runnable接口的實現類對象
TestThread3 testThread3 = new TestThread3();
//創建線程對象,調用start()方法開啓線程
new Thread(testThread3).start();
for (int i = 0; i < 1000; i++) {
System.out.println("我在學習多線程-------"+ i);
}
}
}
6、初識併發問題
package com.fang.demo01;
//多個線程同時操作同一個對象
//買火車票的例子
public class TestThread4 implements Runnable{
//票數
private int ticketNums = 10;
@Override
public void run() {
while(true){
if(ticketNums <= 0){
break;
}
//模擬延時
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums-- +"張票");
}
}
public static void main(String[] args) {
TestThread4 tiket = new TestThread4();
new Thread(tiket,"小明").start();
new Thread(tiket,"老師").start();
new Thread(tiket,"黃牛黨").start();
}
}
出現了安全問題
7、模擬龜兔賽跑
package com.fang.demo01;
//模擬龜兔賽跑
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 % 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();
}
}
8、實現Callable接口(瞭解)
創建線程方式三:實現callable接口
callable的好處
1、可以定義返回值
2、可以拋出異常
package com.fang.demo02;
import com.fang.demo01.TestThread2;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestCallable implements Callable<Boolean> {
private String url; //網絡圖片地址
private String name; //保存的文件名
public TestCallable(String url, String name){
this.url = url;
this.name = name;
}
//下載圖片線程的執行體
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下載量文件名爲:"+name);
return true;
}
public static void main(String[] args) {
TestCallable t1 = new TestCallable("https://i0.hdslb.com/bfs/sycp/creative_img/202006/6d586392ee5c3b537d5b5c832fc02c19.jpg","1.jpg");
TestCallable t2 = new TestCallable("https://i0.hdslb.com/bfs/sycp/creative_img/202002/6bc0da8a84ece9e200fce8b7ec8b08c8.png","2.png");
TestCallable t3 = new TestCallable("https://i0.hdslb.com/bfs/sycp/creative_img/202006/dde761ef0291bc064d8ba955e0b266a7.jpg","3.jpg");
//創建執行服務
ExcutorService ser = Executors.newFixedThreadPool(3);
//提交執行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t1);
Future<Boolean> r3 = ser.submit(t1);
//獲取結果
boolean ret1 = r1.get();
boolean ret2 = r2.get();
boolean ret3 = r3.get();
//關閉服務
ser.shutdownNow();
}
}
//下載器
class WebDownloader{
//下載方法
public void downloader(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO異常,downloader方法出現問題");
}
}
}
9、Labmda表達式
理解Functional Iterface(函數式接口)是學習Java8 Lambda表達式的關鍵所在
函數式接口定義:
任何接口,如果只包含唯一一個抽象方法,那麼他就是一個函數式接口。
對於函數式接口,我們可以通過lambda表達式來創建該接口的對象
public interface Runnable{
public abstract void run();
}
看一段代碼,推導lambda表達式,其中1~6逐步優化,最後一個使用lambda表達式
public class TestLambda1 {
//3、靜態內部類
static class Like2 implements ILike{
//重寫方法:Alt + Insert建
@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();
//4、局部內部類
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}
like = new Like3();
like.lambda();
//5、匿名內部類,沒有類的名稱,必須藉助接口或者父類
like = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda4");
}
};
like.lambda();
//6、用lambda簡化
like = ()-> System.out.println("I like lambda5");
like.lambda();
}
}
//1、定義一個函數式接口
interface ILike{
void lambda();
}
//2、實現類
class Like implements ILike{
//重寫方法:Alt + Insert建
@Override
public void lambda() {
System.out.println("I like lambda");
}
}
爲什麼要用Lambda表達式
避免匿名內部類定義過多
讓代碼簡潔,去掉無意義代碼,留下核心邏輯
public class TestLambda2 {
public static void main(String[] args) {
ILove love = null;
// //1、lambda表達式
// ILove love = (String s)->{
// System.out.println("I love you--->"+ s);
// };
// //2、參數類型
// love = (s) -> System.out.println("I love you--->"+ s);
//3、簡化括號()和{}都可以簡化
// 如果代碼有多行就不能簡化花括號{ }
love = s -> System.out.println("I love you--->"+ s);
//接口=(參數)->方法體
//總結
// lambda表達式只有一行代碼時纔可以簡化爲一行,如果有多行,就要用代碼塊包裹
// 前提是接口爲函數式接口
// 多個參數也可以去掉參數類型,要去掉就要都去掉,但必須加上圓括號( )
love.love("WYQ");
}
}
interface ILove{
void love(String s);
}
10、靜態代理模式
實現靜態代理對比Thread
靜態代理模式總結
// 真實對象和代理對象都要事先同一個接口
// 代理對象要代理真實角色
好處:
// 代理對象可以做很多真實對象坐不了的事情
// 真實對象專注做自己的事情
Thread線程的底部的實現原理:
// 下面的HappMarry()方法就相當於線程裏面的start()方法
// Thread代理了Runnable接口
// 同樣,婚慶公司代理了Marry接口,它們的共同方法叫HappyMarry()
public class StaticProxy {
public static void main(String[] args) {
You you = new You();//你要結婚
new Thread(()-> System.out.println("我愛你")).start();
new WeddingCompany(new You()).HappyMarry();
// WeddingCompany weddingCompany = new WeddingCompany(you);
// weddingCompany.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真實的你去結婚
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("阿芳要結婚了,超級開心");
}
}
//婚慶公司代理,幫助你結婚
class WeddingCompany implements Marry{
//代理誰-->真實目標角色
private Marry target;
public WeddingCompany(Marry target){
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();//這就是真實對象
after();
}
//婚慶公司幫你做的一些事情
private void before(){
System.out.println("結婚之前,佈置現場");
}
private void after(){
System.out.println("結婚之後,收尾款");
}
}
11、線程的狀態
線程方法
停止線程
不建議使用JDK提供的stop()、destroy()方法,已廢棄
推薦線程自己停止下來
建議使用一個標誌位進行終止變量,當flag=false,則終止線程運行。
我們的思路:使用一個標誌位,自己寫一個stop方法
//測試線程停止
//1、建議線程正常停止--->利用次數,不建議死循環
//2、建議使用標誌位--->設置一個標誌位
//3、不要使用stop或者destroy等過時的或者JDK不建議使用的方法
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++);
}
}
//2、設置一個公開的方法停止線程,轉換標誌位
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("線程該停止啦·");
}
}
}
}
12、線程休眠
獲取系統當前時間
package com.fang.demo03;/*
* @Program:untitled
* @Description:description
* @Author:Pufang
* @Time:2020-06-18 19-24-03
**/
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestSleep2 {
public static void main(String[] args) {
//模擬倒計時
//tendown();
//打印當前系統時間
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(){
int num = 10;
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
if(num <= 0){
break;
}
}
}
}
13、線程禮讓yield()
//測試禮讓線程,禮讓不一定成功,看CPU心情(調度)
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()+"線程結束");
}
}
14、線程強制執行join()
Join合併線程,待此線程執行完畢後,其他線程再執行, 其他線程阻塞
可以想象成打飯插隊
//測試join方法---想象爲插隊
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("VIP大哥來了"+i);
}
}
public static void main(String[] args) {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 500; i++) {
if(i == 200){
try {
thread.join();//插隊
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main"+i);
}
}
}
15、觀察線程狀態Thread.State
//測試觀察線程狀態
public class TestState {
public static void main(String[] args) {
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);//RUNNABLE
//只要線程不終止,就一直輸出狀態
while(state != Thread.State.TERMINATED){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
state = thread.getState();//更新線程狀態
System.out.println(state);//TERMINATED
}
thread.start();//一旦線程死亡就不能再次啓動了,一個線程只能死亡一次
}
}
16、線程優先級
//測試線程優先級
public class TestPriority{
public static void main(String[] args) {
//主線程優先級默認爲5
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);
Thread t6 = 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();
t6.setPriority(7);
t6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
17、守護線程(daemon)
線程分爲用戶線程和守護線程
虛擬機必須確保用戶線程執行完畢
虛擬機不用等待守護線程執行完畢
如,後臺記錄操作日誌,監控內存,垃圾回收等
//測試守護線程
//上帝守護你
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 < 36500; i++) {
System.out.println("你一生都開心的活着");
}
System.out.println("----goodbye! world-----");
}
}
18、線程同步機制
多個線程操作同一個資源
併發:同一個對象被多個線程同時操作
線程同步需要:隊列+鎖
19、三大不安全案例
每個線程在自己的工作內存交互,內存控制不當會造成數據不一致
//線程不安全,有負數
//每個線程在自己的工作內存交互,內存控制不當會造成數據不一致
public class UnsafeTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"苦逼的我").start();
new Thread(station,"牛逼的你們").start();
new Thread(station,"可惡的黃牛黨").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
while(flag){
buy();
}
}
private void buy(){
//判斷是否有票
if(ticketNums <= 0){
flag = false;
return;
}
//模擬延時
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//買票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
public class UnsafeBank {
public static void main(String[] args) {
//賬戶
Account account = new Account(100, "結婚基金");
Drawing you = new Drawing(account, 50, "你");
Drawing GF = new Drawing(account, 100, "GF");
you.start();
GF.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;
}
//sleep可以放大問題的發生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡內餘額 = 餘額 - 你取的前
account.money = account.money - drawingMoney;
//你手裏的錢
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"餘額爲"+account.money);
//this.getName() 等價於 Thread.currentThread().getName();
System.out.println(this.getName()+"手裏的錢"+nowMoney);
}
}
import java.util.ArrayList;
import java.util.List;
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
20、同步方法及同步塊
先來看下利用synchronized實現同步的基礎:Java中的每一個對象都可以作爲鎖。具體表現
爲以下3種形式。
·對於普通同步方法,鎖是當前實例對象。
·對於靜態同步方法,鎖是當前類的Class對象。
·對於同步方法塊,鎖是Synchonized括號裏配置的對象。
當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。
對上面三個不安全的案例進行加鎖
//每個線程在自己的工作內存交互,內存控制不當會造成數據不一致
public class UnsafeTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"苦逼的我").start();
new Thread(station,"牛逼的你們").start();
new Thread(station,"可惡的黃牛黨").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums = 10;
boolean flag = true;
@Override
public void run() {
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized默認鎖的是this. //-----------------------------------------------------加鎖變爲安全的
private synchronized void buy() throws InterruptedException{
//判斷是否有票
if(ticketNums <= 0){
flag = false;
return;
}
//模擬延時
Thread.sleep(100);
//買票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
public class UnsafeBank {
public static void main(String[] args) {
//賬戶
Account account = new Account(100, "結婚基金");
Drawing you = new Drawing(account, 50, "你");
Drawing GF = new Drawing(account, 100, "GF");
you.start();
GF.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;
}
//取錢
//synchronized默認鎖的是this.
@Override
public void run() {
//這裏鎖的對象是變化的量,需要增刪改的對象
synchronized (account){//-----------------------------------------------------加鎖變爲安全的
//判斷有沒有錢
if(account.money - drawingMoney < 0){
System.out.println(Thread.currentThread().getName()+"錢不夠,取不了");
return;
}
//sleep可以放大問題的發生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡內餘額 = 餘額 - 你取的前
account.money = account.money - drawingMoney;
//你手裏的錢
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"餘額爲"+account.money);
//this.getName() 等價於 Thread.currentThread().getName();
System.out.println(this.getName()+"手裏的錢"+nowMoney);
}
}
}
import java.util.ArrayList;
import java.util.List;
public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list){//-----------------------------------------------------加鎖變爲安全的
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
21、CopyOnWriteArrayList
併發安全的list
import java.util.concurrent.CopyOnWriteArrayList;
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add((Thread.currentThread().getName()));
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
22、死鎖
//死鎖:多個線程互相抱着對方需要的資源,互相僵持
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;//使用化妝工具的人
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塊寫在外面就不會死鎖
// 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塊寫在外面就不會死鎖
// synchronized (lipstick){
// System.out.println(this.girlName+"獲得口紅的鎖");
// }
}
}
}
23、Lock(鎖)
import java.util.concurrent.locks.ReentrantLock;
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 ticketNums = 10;
//定義Lock鎖
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try {
lock.lock();
if(ticketNums > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
}else{
break;
}
}finally {
//解鎖
lock.unlock();
}
}
}
}
24、生產者消費者問題
線程通信
25、管程法
//生產者消費者模型-利用緩衝區解決--管程法
//生產者 消費者 產品 緩衝區
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[] chickens = new Chicken[10];
//容器計數器
int count = 0;
//生產者放入產品
public synchronized void push(Chicken chicken){
//如果滿了,就需要等待消費者消費
while(count == chickens.length){
//通知消費者消費,生產等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果沒有滿,就需要放入產品
chickens[count] = chicken;
count++;
//可以通知消費者消費了
this.notifyAll();
}
//消費者消費產品
public synchronized Chicken pop(){
//判斷能否消費
while(count == 0){
//等待生產者生產,消費者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消費
count--;
Chicken chicken = chickens[count];
//喫完了,通知生產者生產
this.notifyAll();
return chicken;
}
}
26、信號燈法
//測試生產者消費者問題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) {
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) {
e.printStackTrace();
}
}
System.out.println("觀看了"+voice);
this.notifyAll();
this.flag = !this.flag;
}
}
27、線程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
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());
//2、關閉連接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
28、總結
鎖一共有4種狀態,級別從低到高依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨着競爭情況逐漸升級。鎖可以升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是爲了提高獲得鎖和釋放鎖的效率。
**無鎖:**沒有對資源進行鎖定,所有的線程都能訪問並修改同一個資源,但同時只有一個線程能修改成功,其他修改
失敗的線程會不斷重試直到修改成功。
**偏向鎖:**對象的代碼一直被同一線程執行,不存在多個線程競爭,該線程在後續的執行中自動獲取鎖,降低獲取鎖
帶來的性能開銷。偏向鎖,指的就是偏向第一個加鎖線程,該線程是不會主動釋放偏向鎖的,只有當其他線程嘗試
競爭偏向鎖纔會被釋放。
偏向鎖的撤銷,需要在某個時間點上沒有字節碼正在執行時,先暫停擁有偏向鎖的線程,然後判斷鎖對象是否處於
被鎖定狀態。如果線程不處於活動狀態,則將對象頭設置成無鎖狀態,並撤銷偏向鎖;
如果線程處於活動狀態,升級爲輕量級鎖的狀態。
**輕量級鎖:**輕量級鎖是指當鎖是偏向鎖的時候,被第二個線程 B 所訪問,此時偏向鎖就會升級爲輕量級鎖,線程 B
會通過自旋的形式嘗試獲取鎖,線程不會阻塞,從而提高性能。
當前只有一個等待線程,則該線程將通過自旋進行等待。但是當自旋超過一定的次數時,輕量級鎖便會升級爲重量
級鎖;當一個線程已持有鎖,另一個線程在自旋,而此時又有第三個線程來訪時,輕量級鎖也會升級爲重量級鎖。
**重量級鎖:**指當有一個線程獲取鎖之後,其餘所有等待獲取該鎖的線程都會處於阻塞狀態。
重量級鎖通過對象內部的監視器(monitor)實現,而其中 monitor 的本質是依賴於底層操作系統的 Mutex Lock
實現,操作系統實現線程之間的切換需要從用戶態切換到內核態,切換成本非常高