Java多執行緒-01-執行緒的建立
目錄
一.執行緒簡介
二.執行緒建立(重點)
Thread類
package 執行緒建立.Thread; public class Thread_Test01 { public static void main(String[] args) { //main執行緒,主執行緒 //建立一個執行緒物件 MyThread t = new MyThread(); //呼叫start方法開啟執行緒 t.start(); for (int i = 0; i < 2000; i++) { System.out.println("當前遊戲場景執行中"+i); } } } //建立執行緒的方式一:繼承Thread類,重寫run方法,呼叫start開啟執行緒 class MyThread extends Thread{ @Override public void run() { //run方法執行緒體 for (int i = 0; i < 2000; i++) { System.out.println("後臺載入下一遊戲場景"+i); } } }
主執行緒和子執行緒並行交替執行
練習:同時下載三張網路圖片
需要用到工具類庫:commons-io
下載方法參考部落格:(54條訊息) commons-io的下載和使用_唯有一片炙熱的部落格-CSDN部落格_commons-io
匯入到IDEA使用:
1.新建包lib
2.拷貝外部庫到lib
3.右鍵lib,點選“新增為庫...”選項
public class Thread_WebPictureDown_Test { //main,主執行緒 public static void main(String[] args) { //建立三個執行緒 //地址太長這裡省略了 WebPictureDownLoad_Thread t1 = new WebPictureDownLoad_Thread("https:...", "1"); WebPictureDownLoad_Thread t2 = new WebPictureDownLoad_Thread("https:...", "2"); WebPictureDownLoad_Thread t3 = new WebPictureDownLoad_Thread("https:...", "3"); //開啟執行緒 t1.start(); t2.start(); t3.start(); } } //網圖下載執行緒 class WebPictureDownLoad_Thread extends Thread{ private String url;//網路圖片地址 private String name;//儲存的檔名 //構造器 public WebPictureDownLoad_Thread(String url, String name){ this.url = url; this.name = name; } //網圖下載執行緒的執行體 @Override public void run() { DownLoader downLoader = new DownLoader(); downLoader.downLoad(this.url, this.name); System.out.println(name+"下載完畢"); } } //下載器 class DownLoader{ //下載方法 public void downLoad(String url, String name){ try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO異常,downLoad方法出現問題"); } } }
Runnable介面(推薦)
具體原理在靜態代理部分介紹
public class Runnable_Test { public static void main(String[] args) { //建立Runnable介面的實現類物件 LoadScene Scene_city_of_tear = new LoadScene("City_Of_Tear"); //建立執行緒物件,通過執行緒物件來開啟我們的執行緒 (代理) Thread thread = new Thread(Scene_city_of_tear); thread.start(); for (int i = 0; i < 2000; i++) { System.out.println("當前遊戲場景執行情況:"+i); } } } //建立執行緒的方式二:實現Runnable介面,重寫run方法,執行執行緒需要丟入Runnable介面實現類,呼叫start方法 class LoadScene implements Runnable{ private String levelName; public LoadScene(String levelName){ this.levelName = levelName; } @Override public void run() { //run方法執行緒體 for (int i = 0; i < 2000; i++) { System.out.println("後臺載入場景:"+this.levelName+" 載入情況:"+i); } } }
小結:對比Thread類和Runnable介面
實現Runnable介面的好處
1.介面可以多繼承,更靈活方便
2.方便同一個物件被多個執行緒使用
初識併發問題
例子:買電影票
//《阿凡達2》就要上映了,電影院一共放出10張票,小明,小紅,小玉都想去看(並且想看很多次!,所以他們都要搶很多票)
public class BuyTicket {
public static void main(String[] args) {
Ticket_Selling_Service Buy_AFanDa = new Ticket_Selling_Service("阿凡達2");
//第二個引數是執行緒的name,可用.getName()獲得
new Thread(Buy_AFanDa, "小明").start();
new Thread(Buy_AFanDa, "小紅").start();
new Thread(Buy_AFanDa, "小玉").start();
}
}
//售票服務
class Ticket_Selling_Service implements Runnable{
//電影的名字
private String movieName;
//阿凡達電影票總數
private int ticketNums_AFanDa = 10;
public Ticket_Selling_Service(String movieName){
this.movieName = movieName;
}
@Override
public void run() {
while(true){
if(this.ticketNums_AFanDa <= 0){
System.out.println("尊敬的使用者"+Thread.currentThread().getName()+"你好,《阿凡達2》電影票已售完");
break;
}
//模擬延時(資料量小,cpu跑太快了,不便於測試)
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"搶到了"+this.movieName+"的第"+this.ticketNums_AFanDa-- +"張票");
}
}
}
發現問題:多個執行緒操作同一個資源的情況下,執行緒不安全,資料紊亂
在後面的執行緒同步部分解決這個問題
龜兔賽跑
//龜兔賽跑,兔子比烏龜跑快,兔子每隔10米睡一次覺
public class Race_Test {
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "兔子").start();
new Thread(race, "烏龜").start();
}
}
class Race implements Runnable{
//賽道總長100米
private static int TotalLength = 100;
//獲勝者
private static String winner = null;
@Override
public void run() {
if(Thread.currentThread().getName().equals("兔子")){
//模擬兔子跑步,一輪迴圈加25米
for (int howFarHasRuned = 0; howFarHasRuned <= 100; howFarHasRuned = howFarHasRuned + 25){
//模擬兔子睡覺,每10米睡1納秒
if(howFarHasRuned % 10 == 0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("兔子-->已經跑了"+howFarHasRuned+"米");
//判斷比賽是否已經結束了
if(GameOver(howFarHasRuned)) break;
}
}
else{
//模擬烏龜跑步,一輪迴圈加1米
for (int howFarHasRuned = 0; howFarHasRuned <= 100; howFarHasRuned++) {
System.out.println("烏龜-->已經跑了"+howFarHasRuned+"米");
//判斷比賽是否已經結束了
if(GameOver(howFarHasRuned)) break;
}
}
}
//判斷比賽是否結束
private boolean GameOver(int howFarHasRuned){
if(winner != null) return true;//已經有獲勝者了
else{
if(howFarHasRuned >= 100){//獲勝者產生
winner = Thread.currentThread().getName();
System.out.println("獲勝者是-->"+winner);
}
}
return false;
}
}
Callable介面
靜態代理
結婚的例子
//靜態代理模式總節
//真實物件和代理物件都要實現同一個介面
//代理物件要代理真實角色
//好處
//代理物件可以做很多真實物件做不了的事情
//真實物件只需要專注於做自己的事情
//一下是我目前的理解:
//舉個現實生活中的例子
//個人遊戲開發者A是程式高手可是不會其他的
//那麼A可以把音樂代理給音樂創作者B,把美術代理給美術創作者C
//A自己只需要專注於把程式部分做好就可以了
//簡而言之:把專業的事情代理給專業的物件去做,每個物件專精於自己的事情
public class Marry_Test {
public static void main(String[] args) {
MarryPeople people_Alice = new MarryPeople("Alice");
WeddingCompany weddingCompany = new WeddingCompany(people_Alice);
weddingCompany.HappyMarry();
}
}
//結婚介面
interface Marry{
void HappyMarry();
}
//真實角色:結婚的人
class MarryPeople implements Marry{
private String name;
//構造器
public MarryPeople(String name){
this.name = name;
}
@Override
public void HappyMarry() {
System.out.println(this.name+"-->結婚");
}
}
//代理角色:婚慶公司幫助別人結婚
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("---結婚後---");
}
}
靜態代理模式總節:
真實物件和代理物件都要實現同一個介面
代理物件要代理真實角色
好處:
代理物件可以做很多真實物件做不了的事情
真實物件只需要專注於做自己的事情
我目前的理解:
舉個現實生活中的例子
個人遊戲開發者A是程式高手可是不會其他的
那麼A可以把音樂代理給音樂創作者B,把美術代理給美術創作者C
A自己只需要專注於把程式部分做好就可以了
簡而言之:把專業的事情代理給專業的物件去做,每個物件專精於自己的事情
多執行緒中的靜態代理
第一行程式碼是多執行緒中的靜態代理
第二行程式碼是之前結婚的例子
Thread(代理物件)和藍色框裡的東西(真實物件)都實現了Runnnable介面
(這裡用到了Lamda表示式,之後會介紹)
猜測:
Thread呼叫start()方法後會呼叫自己的run()方法 (具體怎麼調的不太清楚)
在run()方法中呼叫真實物件的run()方法
Lamda表示式
推導Lamda表示式
使用前提:介面為函式式介面(介面只有一個方法且是抽象方法)
場景:名叫Apple的類只使用一次
逐步優化,把程式碼變簡單
1.外部類
public class Lamda_Test01 {
public static void main(String[] args) {
Food apple = new Apple();
apple.lamda();
}
}
//外部類
class Apple implements Food{
@Override
public void lamda() {
System.out.println("吃了蘋果");
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void lamda();
}
2.靜態內部類
public class Lamda_Test02 {
//靜態內部類
static class Apple implements Food{
@Override
public void lamda() {
System.out.println("吃了蘋果");
}
}
public static void main(String[] args) {
Food apple = new Apple();
apple.lamda();
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void lamda();
}
3.區域性內部類
public class Lamda_Test03 {
public static void main(String[] args) {
//區域性內部類
class Apple implements Food{
@Override
public void lamda() {
System.out.println("吃了蘋果");
}
}
Food apple = new Apple();
apple.lamda();
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void lamda();
}
4.匿名內部類
public class Lamda_Test04 {
public static void main(String[] args) {
//匿名內部類
Food apple = new Food() {//new了一個繼承Food介面但是沒有名稱的實現類
@Override
public void lamda() {
System.out.println("吃了蘋果");
}
};
apple.lamda();
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void lamda();
}
5.用lamda簡化
public class Lamda_Test05 {
public static void main(String[] args) {
//lamda
Food apple = ()->{
System.out.println("吃了蘋果");
};
apple.lamda();
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void lamda();
}
6.簡化大括號(當方法內只有一行語句時)
public class Lamda_Test05 {
public static void main(String[] args) {
//lamda
Food apple = ()->System.out.println("吃了蘋果");
apple.lamda();
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void lamda();
}
推導完畢
帶引數的lamda表示式
public class Lamda_Test06 {
public static void main(String[] args) {
//lamda
Food apple = (int i)->System.out.println("吃了"+i+"個蘋果");
apple.eat(3);
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void eat(int i);
}
1.簡化引數型別
public class Lamda_Test07 {
public static void main(String[] args) {
//lamda
Food apple = (i)->System.out.println("吃了"+i+"個蘋果");
apple.eat(3);
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void eat(int i);
2.簡化小括號(函式只有一個引數時)
public class Lamda_Test08 {
public static void main(String[] args) {
//lamda
Food apple = i->System.out.println("吃了"+i+"個蘋果");
apple.eat(3);
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void eat(int i);
}
兩個引數,有多行語句的例子
public class Lamda_Test09 {
public static void main(String[] args) {
//lamda
Food apple = (name,i)->{
System.out.println(name+"吃了"+i+"個蘋果");
System.out.println(name+"吃飽了");
};
apple.eat("Alice", 7);
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void eat(String name, int i);
}
使用舉例
無引數
public class Lamda_Test {
public static void main(String[] args) {
//lamda
Food apple = ()->System.out.println("吃了蘋果");
apple.eat();
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void eat();
}
一個引數
public class Lamda_Test {
public static void main(String[] args) {
//lamda
Food apple = i->System.out.println("吃了"+i+"個蘋果");
apple.eat(7);
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void eat(int i);
}
多個引數,多行語句
public class Lamda_Test {
public static void main(String[] args) {
//lamda
Food apple = (name,i)->{
System.out.println(name);
System.out.println("吃了"+i+"個蘋果");
};
apple.eat("Alice", 7);
}
}
//定義函式式介面
interface Food{
//隱式宣告為 public abstract
void eat(String name, int i);
}