多執行緒學習
核心概念
-
執行緒就是獨立的執行路徑
-
Main()稱之為主執行緒,為系統的入口,用於執行整個程式
-
在一個程序中,如果開闢了多個執行緒,執行緒的執行有排程器安排排程,排程器是與作業系統緊密相關的,先後順序是不能人為干預的
-
對同一份資源操作時,會存在資源搶奪的問題,需要加入併發控制
-
執行緒會帶來額外的開銷,如cpu排程時間,併發控制開銷
-
每個執行緒在自己的工作記憶體互動,記憶體控制不當會造成資料不一致
Demo1 簡單理解執行緒
public class Demo1 extends Thread { @Override public void run() { //super.run(); for (int i = 0; i < 20; i++) { System.out.println(" 子執行緒在跑 "+ i); } } public static void main(String[] args) { Demo1 demo1 =new Demo1(); //demo1.run();//普通的執行run,正常順序 demo1.start();//建立子執行緒,執行run,主執行緒和子執行緒的執行,由CPU進行排程,無法人為干預 // for (int i = 0; i < 100; i++) { System.out.println("主執行緒在跑"+i); } } } /* 主執行緒在跑0 子執行緒在跑 0 子執行緒在跑 1 子執行緒在跑 2 子執行緒在跑 3 子執行緒在跑 4 主執行緒在跑1 主執行緒在跑2 主執行緒在跑3 主執行緒在跑4 主執行緒在跑5 主執行緒在跑6 主執行緒在跑7 主執行緒在跑8 主執行緒在跑9 主執行緒在跑10 主執行緒在跑11 主執行緒在跑12 主執行緒在跑13 主執行緒在跑14 主執行緒在跑15 主執行緒在跑16 主執行緒在跑17 子執行緒在跑 5 子執行緒在跑 6 主執行緒在跑18 主執行緒在跑19 子執行緒在跑 7 子執行緒在跑 8 子執行緒在跑 9 子執行緒在跑 10 子執行緒在跑 11 子執行緒在跑 12 子執行緒在跑 13 子執行緒在跑 14 子執行緒在跑 15 子執行緒在跑 16 子執行緒在跑 17 子執行緒在跑 18 子執行緒在跑 19 主執行緒在跑20 主執行緒在跑21 */
Demo2 下載網圖
import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; public class Demo2 { int k=0; public static void main(String[] args) { WebDownloader wd =new WebDownloader("https://i0.hdslb.com/bfs/album/92033a2dfd1630bf3d013965e1769258ee88eb35.png","1.png"); WebDownloader wd2 =new WebDownloader("https://i0.hdslb.com/bfs/album/cebd6f76c2317c472e6a0a5610fea0882931cbc3.jpg","2.jpg"); wd.start(); wd2.start(); /* 下載了:1.png 下載了:2.jpg */ } } class WebDownloader extends Thread{ private String Url; private String ServerFileName; public WebDownloader(String url,String serverFileName) { this.Url = url; this.ServerFileName=serverFileName; } @Override public void run() { //super.run(); try { //copyURLToFile 方法丟擲了異常,上層函式一定要處理這個異常 FileUtils.copyURLToFile(new URL(this.Url), new File(this.ServerFileName)); System.out.println("下載了:"+this.ServerFileName); } catch (IOException e) { e.printStackTrace(); System.out.println("下載圖片報錯"); } } }
Demo3 使用Runable介面實現多執行緒
public class Demo3 implements Runnable { @Override public void run() { //super.run(); for (int i = 0; i < 20; i++) { System.out.println(" 子執行緒在跑 "+ i); } } public static void main(String[] args) { Demo3 demo3 =new Demo3(); new Thread(demo3).start();//呼叫方式稍作改變 // for (int i = 0; i < 100; i++) { System.out.println("主執行緒在跑"+i); } } }
Demo4 使用Callable的介面實現多執行緒
public class Demo5 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
WebDownloader2 wd =new WebDownloader2("https://i0.hdslb.com/bfs/album/92033a2dfd1630bf3d013965e1769258ee88eb35.png","1.png");
WebDownloader2 wd2 =new WebDownloader2("https://i0.hdslb.com/bfs/album/cebd6f76c2317c472e6a0a5610fea0882931cbc3.jpg","2.jpg");
ExecutorService service = Executors.newFixedThreadPool(2);
Future<Boolean> res1 = service.submit(wd);
Future<Boolean> res2 = service.submit(wd2);
Boolean r1 = res1.get();
Boolean r2 = res2.get();
System.out.println(r1);
System.out.println(r2);
}
}
class WebDownloader2 implements Callable<Boolean> {
private String Url;
private String ServerFileName;
public WebDownloader2(String url,String serverFileName)
{
this.Url = url;
this.ServerFileName=serverFileName;
}
@Override
public Boolean call() {
//super.run();
try {
//copyURLToFile 方法丟擲了異常,上層函式一定要處理這個異常
FileUtils.copyURLToFile(new URL(this.Url), new File(this.ServerFileName));
System.out.println("下載了:"+this.ServerFileName);
} catch (IOException e) {
e.printStackTrace();
System.out.println("下載圖片報錯");
}
return true;
}
}
靜態代理模式
Thread 實現物件執行方法的模式
//總結:
//真是執行物件和代理物件都要整合同一個介面
//代理物件要代理真實角色
//好處
//1.代理物件可以做很多真實物件做不了的事,或者說不好做的事
//2.真實物件專注自己的業務
public class Demo6 {
public static void main(String[] args) {
Person person = new Person();
//靜態代理類傳入 被代理物件
MarryCompany marryCompany = new MarryCompany(person);
marryCompany.HappyMarry();//靜態代理類,幫助被代理物件執行
//Tread 類實現新執行緒執行物件方法就是靜態代理模式
new Thread(()-> System.out.println("Tread 類實現新執行緒執行物件方法就是靜態代理模式")).start();
}
}
/**
* Marry介面
*/
interface Marry
{
void HappyMarry();
}
/**
* Person類,靜態代理實際執行的物件類
*/
class Person implements Marry
{
@Override
public void HappyMarry() {
System.out.println("結婚啦");
}
}
/**
* MarryCompany Marry代理類
*/
class MarryCompany implements Marry
{
private Marry _marryPerson;
public MarryCompany(Marry marryPerson)
{
this._marryPerson =marryPerson;
}
@Override
public void HappyMarry() {
System.out.println("收定金,佈置會場");
this._marryPerson.HappyMarry();
System.out.println("收尾款");
}
}
執行緒狀態
五種狀態
建立狀態 就緒狀態 阻塞狀態 執行狀態 死亡狀態
Sleep
Sleep阻塞執行緒,模擬網路延時可以放大問題的放生性。
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Date currentTime = new Date(System.currentTimeMillis());
for (int i = 0; i < 10; i++) {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(currentTime));//java操作時間和c#寫起來感覺還是有很大區別的
Thread.sleep(1000);
currentTime = new Date(System.currentTimeMillis());
}
}
}
執行緒禮讓 yeild
讓CPU重新排程,禮讓不一定成功,看CPU排程
public class Demo8 {
public static void main(String[] args) {
TestYeild testYeild1 = new TestYeild();
TestYeild testYeild2 = new TestYeild();
new Thread(testYeild1,"a").start();
new Thread(testYeild2,"b").start();
}
}
class TestYeild implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"開始執行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"結束執行");
}
}
Join
Join合併執行緒,待此執行緒執行完成之後,在執行其他執行緒
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
for (int i = 0; i < 100; i++) {
if(i==0)
{
thread.start();
}
if(i==10)
{
System.out.println("開始插隊=================");
thread.join();
}
System.out.println("主執行緒"+i);
}
}
}
class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("插隊執行緒"+i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("插隊還不快點,還睡覺==============");
}
}
/*
主執行緒0
主執行緒1
主執行緒2
主執行緒3
主執行緒4
主執行緒5
主執行緒6
主執行緒7
主執行緒8
插隊執行緒0
主執行緒9
開始插隊=================
插隊執行緒1
插隊執行緒2
插隊執行緒3
插隊執行緒4
插隊執行緒5
插隊執行緒6
....
插隊執行緒43
插隊執行緒44
插隊執行緒45
插隊執行緒46
插隊執行緒47
插隊執行緒48
插隊執行緒49
插隊還不快點,還睡覺==============
主執行緒10
主執行緒11
主執行緒12
....
主執行緒88
主執行緒89
主執行緒90
主執行緒91
主執行緒92
主執行緒93
主執行緒94
主執行緒95
主執行緒96
主執行緒97
主執行緒98
主執行緒99
Process finished with exit code 0
*/
執行緒優先順序
執行緒優先順序高不一定先執行,但權重高!
min=1 max=10 normal=5
先設定優先順序再啟動
守護執行緒
執行緒分為使用者執行緒和守護執行緒。虛擬機器必須確保使用者執行緒執行完畢。虛擬機器不用等待守護執行緒執行完畢。如後臺記錄操作日誌,監控記憶體,垃圾回收等待。
public class Demo10 {
public static void main(String[] args) {
DeamonThread deamon = new DeamonThread();
UserThread user = new UserThread();
Thread deamonThread = new Thread(deamon);
deamonThread.setDaemon(true);//守護執行緒預設為false
deamonThread.start();
Thread userThread = new Thread(user);
userThread.start();
}
}
class UserThread implements Runnable
{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("使用者執行緒執行第"+i);
}
}
}
class DeamonThread implements Runnable
{
@Override
public void run() {
//當主執行緒和其他使用者執行緒執行完畢之後,守護執行緒也隨之關閉
while(true){
System.out.println("守護執行緒執行中");
}
}
}
同步方法和同步快
public class Demo4 implements Runnable {
int ticket = 10;
@Override
public synchronized void run() {//鎖住run方法
while (ticket>0)
{
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"張票");
}
}
public static void main(String[] args) {
Demo4 demo4 = new Demo4();
new Thread(demo4,"小明").start();
new Thread(demo4,"小紅").start();
new Thread(demo4,"小藍").start();
}
}
import java.util.ArrayList;
import java.util.List;
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();//這裡例項化跟C#不太一樣
for (int i = 0; i < 20000; i++) {
new Thread(()->{
synchronized (list)
{
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(6000);
System.out.println(list.size());//不加 synchronized的情況下 19997!=20000,add當前物件的相同位置,覆蓋了
}
}
public class Demo12 {
public static void main(String[] args) {
Account account = new Account("共同賬戶",100);
Withdraw withdraw = new Withdraw(account,100);
new Thread(withdraw).start();
new Thread(withdraw).start();
new Thread(withdraw).start();
new Thread(withdraw).start();
}
}
class Account{
String name;
int money;
public Account(String name, int money) {
this.name = name;
this.money = money;
}
}
class Withdraw extends Thread
{
Account account;
int withdraw;
int nowMoney;
public Withdraw(Account account, int withdraw) {
this.account = account;
this.withdraw = withdraw;
}
@Override
public void run()
{
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (account)
{
if(account.money>0 && account.money>=withdraw)
{
int rs = this.account.money-this.withdraw;
this.account.money -=this.withdraw;
this.nowMoney += this.withdraw;
System.out.println("餘額"+rs+";現在手中的錢有:"+nowMoney);
}
else
{
System.out.println("餘額不足");
}
}
}
}
import java.util.concurrent.CopyOnWriteArrayList;
public class Demo13 {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();//這裡例項化跟C#不太一樣
for (int i = 0; i < 20000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(6000);
System.out.println(list.size());//CopyOnWriteArrayList 執行緒安全類
}
}
生產者消費者問題
解決方案一:
併發寫作模型“生產者/消費者模式”-->管程法
生產者:負責生產資料的模組
消費者:負責處理資料的模組
緩衝區:消費者不能直接使用生產者的資料,消費者從緩衝區拿出資料
解決方案二:
併發協作模型“生產者/消費者模式”-->訊號燈法
通過標誌位,判斷是否可以通行
public class Demo14 {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
Cook cook =new Cook(synContainer);
Custom custom = new Custom(synContainer);
new Thread(cook,"cook").start();
new Thread(custom,"Custom").start();
}
}
//產品 雞
class Chicken
{
public int id;
public Chicken(int id) {
this.id = id;
}
}
//生產者
class Cook implements Runnable
{
SynContainer synContainer;
public Cook(SynContainer synContainer)
{
this.synContainer = synContainer;
}
@Override
public void run() {
// 生產者生產 產品
for (int i = 0; i < 100; i++) {
Chicken chicken = new Chicken(i);
this.synContainer.push(chicken);
System.out.println("生產者生產了第"+i+"只雞");
}
}
}
//消費者 顧客
class Custom implements Runnable
{
SynContainer synContainer;
public Custom(SynContainer synContainer)
{
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Chicken chicken = this.synContainer.pop();
System.out.println("消費者消費了第"+chicken.id+"只雞");
}
}
}
class SynContainer
{
Chicken[] chickens = new Chicken[10];
int count = 0;
public synchronized void push(Chicken chicken)
{
//容器滿了,等待生產者消費
if(chickens.length == count)
{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.chickens[count]=chicken;
count++;
//通知消費者,可以消費了
this.notifyAll();
}
public synchronized Chicken pop() {
if (count == 0) {
//等待生產者生產
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Chicken chicken = this.chickens[count-1];
//this.chickens[count] = null;
count--;
this.notifyAll();
return chicken;
}
}
notify和notifyAll的差別:notify只喚醒一個執行緒,且不保證能那個執行緒會被喚醒,這取決於執行緒排程器。notifyAll會讓等待該鎖的所有執行緒都會被喚醒。
執行緒池
思路:提前建立好多個執行緒池,放入執行緒池中,使用時直接獲取,使用完放回池中,可以避免頻繁建立銷燬、實現重複利用。類似生活中的公共交通工具。
好處:
提高相應速度(減少了建立新執行緒的時間)
降低消耗資源(重複利用執行緒池中執行緒,不需要每次建立)
便於管理(核心池的大小(放多少執行緒)、最大執行緒數(同時跑多少執行緒)、執行緒沒有任務時最多保持多長時間)