基本概念
一個process可以切割成很多thread,而thread被開啟時也不一定會執行,其調用與順序需要依靠CPU安排,稱為排程
三種可以實現多線程的方式
方法一 extends Thread
- class繼承Thread類別
- override run()
- 再建立物件使用 run()/start()
run()/start()差異
e.g. 以start()來啟動線程
public class Thread1 extends Thread{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("Thread1 is running now!");
}
}
public static void main(String args[]){
Thread1 t = new Thread1();
t.start();
for(int i=0;i<1000;i++){
System.out.println("main is running now!");
}
}
}
輸出是交錯的,表示main,t兩個thread是交替執行,符合start()的原則
e.g. 多線程執行
public class Thread2 extends Thread{
private String name;
public Thread2(String name){
this.name=name;
}
public void run(){
System.out.println(name+" is running");
}
public static void main(String args[]){
Thread2 t1= new Thread2("thread1");
Thread2 t2= new Thread2("thread2");
Thread2 t3= new Thread2("thread3");
t1.start();
t2.start();
t3.start();
}
}
輸出為
thread1 is running
thread3 is running
thread2 is running
表示開啟線程順序跟執行順序無關, 由CPU排程影響
方法二 implements Runnable
extends Thread方式雖然方便,但是由於java的單繼承原則造成侷限
因此有了implements的方式出現
- class implements Runnable介面
- override run()
- 創建Thread物件時以class作為參數,使用start()啟動
以上面兩個程式作為更改
public class Thread3 implements Runnable{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("Thread1 is running now!");
}
}
public static void main(String args[]){
//Thread1 t = new Thread1();
//t.start();
Thread3 thread3=new Thread3();
new Thread(thread3).start();
for(int i=0;i<1000;i++){
System.out.println("main is running now!");
}
}
}
public class Thread4 implements Runnable{
private String name;
public Thread4(String name){
this.name=name;
}
public void run(){
System.out.println(name+" is running");
}
public static void main(String args[]){
/*
Thread2 t1= new Thread2("thread 1");
Thread2 t2= new Thread2("thread 2");
Thread2 t3= new Thread2("thread 3");
t1.start();
t2.start();
t3.start();
*/
Thread4 t1= new Thread4("thread 1");
Thread4 t2= new Thread4("thread 2");
Thread4 t3= new Thread4("thread 3");
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
}
}
e.g.龜兔賽跑
1.烏龜兔子共用同一線程(跑道)
2.兔子會睡覺
public class Race implements Runnable{
//只有一個贏家
private static String winner;
public void run(){
for(int i=0;i<=10;i++){
if(winner!=null) break;
else{
if(Thread.currentThread().getName().equals("兔子")){
try{
Thread.sleep(1); //模擬兔子睡覺
}catch(Exception e){
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
if(i==10){
winner=Thread.currentThread().getName();
System.out.println("winner: "+winner);
}
}
}
}
public static void main(String args[]){
Race race=new Race();
new Thread(race,"烏龜").start();
new Thread(race,"兔子").start();
}
}
烏龜-->跑了0步
烏龜-->跑了1步
烏龜-->跑了2步
烏龜-->跑了3步
烏龜-->跑了4步
烏龜-->跑了5步
烏龜-->跑了6步
烏龜-->跑了7步
烏龜-->跑了8步
烏龜-->跑了9步
兔子-->跑了0步
烏龜-->跑了10步
winner: 烏龜
兔子-->跑了1步
<講解> Thread.currentThread()
很類似於class中指定本物件this的感覺
靜態代理模式
本位類別專注於自己要處理的事情,而將衍伸的任務都交由代理類別來處理
e.g.自己要結婚,但是婚禮流程及規劃由婚禮公司處理
模式:
- 本位及代理class都要implements同一interface
- 代理class要代理本位class
e.g.
public class StaticProxy {
public static void main(String args[]){
new company(new person()).getMarry();
//new Thread( ()-> System.out.println("我愛你") ).start();
}
}
interface Marry{
void getMarry();
}
class person implements Marry{
public void getMarry(){
System.out.println("get marry is happy!");
}
}
class company implements Marry{
private Marry customer;
public company(Marry customer){
this.customer=customer;
}
public void getMarry(){
before(); //代理class
this.customer.getMarry(); //本位class
after(); //代理class
}
private void before(){
System.out.println("結婚之前,布置現場");
}
private void after(){
System.out.println("結婚之後,收拾現場");
}
}
其中
new company(new person()).getMarry();
//new Thread( ()-> System.out.println("我愛你") ).start();
此處表明了Thread的原理,並且以代理類別進行模擬
Thread 五大狀態及控制語法
一個Thread的誕生到結束會經過以下五大階段:
- new :
Thread t = new Thread();
線程創立 - 就緒 : 使用start()方法,但是這是代表Thread準備好被執行,但是不一定是馬上,需要CPU去調度順序跟執行時間
- 運行 : CPU開始執行,此刻Thread才能算是真正啟動
- 阻塞 : sleep,wait等方法使thread不繼續往下執行,阻塞事件結束後進入就緒狀態等待CPU調度
- 死亡 : thread執行完畢,並且不能再次啟動
觀察線程狀態
thread.getState();
可以查看thread當前的線程狀況
e.g
public class Getstate {
public static void main(String args[]) throws Exception{
Thread t= new Thread(()->{
try{
for(int i=0;i<5;i++){
Thread.sleep(1000);
}
System.out.println("//////");
}catch(Exception e){
e.printStackTrace();
}
});
//觀察狀態
Thread.State state = t.getState();
System.out.println(state); //NEW
t.start();
state=t.getState();
System.out.println(state); //RUN
while(state!=Thread.State.TERMINATED){ //thread不中止就一直輸出狀態
Thread.sleep(100);
state=t.getState();
System.out.println(state);
}
}
}
NEW
RUNNABLE
TIMED_WAITING
...
TIMED_WAITING
//////
TERMINATED
優先權
java優先權參數設置為1-10, 1最高 10 最低
然而在執行順序上優先權參數越小是越有可能優先執行
但是主要還是要依照CPU排班所決定
thread.getPriority();
可以取得優先權參數
thread.setPriority();
可以修改優先權參數
e.g.
public class Priority {
public static void main(String args[]){
thread t= new thread();
Thread t1 =new Thread(t,"thread1");
Thread t2 =new Thread(t,"thread2");
Thread t3 =new Thread(t,"thread3");
Thread t4 =new Thread(t,"thread4");
//先設置好優先權,再啟動
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(10);
t3.start();
t4.setPriority(8);
t4.start();
}
}
class thread implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
}
}
thread3--->10
thread4--->8
thread2--->1
thread1--->5
stop語法
使執行中的thread停下進入阻塞狀態
- 建議利用for次數和立flag --> 讓thread可以自己自然的停止
- 不建議使用內建的stop/destroy等過時方法
public class Stop implements Runnable{
//1.設立flag
private boolean flag=true;
public void run(){
int i=0;
while(flag){
System.out.println("run thread"+i++);
}
}
//2.設置一個方法可以停止thread(轉換flag)
public void stop(){
this.flag=false;
}
public static void main(String args[]){
Stop s= new Stop();
new Thread(s).start();
for(int i=0;i<1000;i++){
System.out.println("main"+i);
if(i==900){
s.stop();
System.out.println("the test is over!");
}
}
}
}
sleep語法
將當前的thread進入指定的阻塞時間, 然後再進入就緒狀態
- 可以模擬網路的延遲實際狀況
- 可以實作計時等功能
e.g.
public class Sleep {
public static void main(String args[]){
try{
tenDown();
}catch(Exception e){
e.printStackTrace();
}
}
public static void tenDown() throws Exception{
int num=10;
while(true){
Thread.sleep(1000);
System.out.println(num--);
if(num<=0) break;
}
}
}
yield語法
當一個已經正在執行的thread使用yield方法,表示其會重新回到就緒狀態,跟其他就緒狀態thread經由CPU挑選的公平競爭,但是禮讓不一定會成功,因為是看CPU排班機制來決定
e.g.
public class Yield {
public static void main(String args[]){
myYield y=new myYield();
new Thread(y,"a").start();
new Thread(y,"b").start();
}
}
class myYield implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+"線程開始執行");
Thread.yield(); //禮讓
System.out.println(Thread.currentThread().getName()+"線程停止執行");
}
}
a線程開始執行
b線程開始執行
b線程停止執行
a線程停止執行
-------------------->照理說應是
a線程開始執行
b線程開始執行
a線程停止執行
b線程停止執行
但是b回到就緒狀態時又被CPU選中,因此禮讓失敗
join語法
直接插隊執行自己的線程,讓其他線程強制進入阻塞狀態
public class Join implements Runnable{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("thread vip is here "+i);
}
}
public static void main(String args[]) throws InterruptedException{
Join j=new Join();
Thread thread = new Thread(j);
thread.start();
for(int i=0;i<500;i++){
if(i==100) thread.join();
System.out.println("main "+i);
}
}
}
main 99 -->join 直接開搶
thread vip is here 0
...
thread vip is here 998
thread vip is here 999
main 100
main 101
main 102
Daemon 守護線程
java中thread分為守護線程及用戶線程,預設狀況下所有thread都是用戶線程
而java只會在用戶線程結束時代表執行完成,而不理會守護線程
thread.setDaemon(true);
將線程設定為守護線程, 一般預設為false
e.g.
public class Daemon {
public static void main(String args[]){
God god =new God();
Thread thread=new Thread(god);
// 程序默認所有thread為用戶線程,也就是false
thread.setDaemon(true);
Thread person = new Thread(()->{
for(int i=0;i<30000;i++){
System.out.println("Happy living");
}
System.out.println("Goodbye World!");
});
thread.start();
person.start();
}
}
class God implements Runnable{
public void run(){
while(true){
System.out.println("God bless you");
}
}
}
Happy living
God bless you
God bless you
Happy living
Happy living
God bless you
Happy living
Goodbye World! --->code已經執行完畢準備要關(關的延遲過程讓守護線程可以活動一陣子)
God bless you
God bless you
God bless you
同步問題
當多個thread因為同時或是極短時間內要共用同個限量的資源,就會造成同個資源同時分給不同thread的假象,形成不安全的程序
e.g.1 搶票
// 3 threads to controll same resource
public class Buyer {
public static void main(String args[]){
BuyTicket bt = new BuyTicket();
new Thread(bt,"t1").start();
new Thread(bt,"t2").start();
new Thread(bt,"t3").start();
}
}
class BuyTicket implements Runnable{
private int t=10;
boolean flag=true;
public void run(){
while(flag){
buy();
}
}
private void buy() {
if(t<=0){
flag=false;
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到"+t--);
}
}
t3拿到10
t2拿到9 ---> 重複拿到
t1拿到9 ---> 重複拿到
t2拿到8
t3拿到7
t1拿到6
t2拿到5
t3拿到4
t1拿到3
t2拿到2
t1拿到1
t3拿到0
e.g.2
public class Bank {
public static void main(String args[]){
Account a = new Account(100, "wedding fund");
Drawbank you = new Drawbank(a,50,"you");
Drawbank wife = new Drawbank(a,100,"wife");
you.start();
wife.start();
}
}
class Account{
int money;
String name;
public Account(int m,String n){
money=m;
name=n;
}
}
class Drawbank extends Thread{
Account account;
int drawMoney;
public Drawbank(Account account,int drawMoney,String name){
super(name);
this.account=account;
this.drawMoney=drawMoney;
}
public void run(){
if(account.money<drawMoney) System.out.println(this.getName()+"取錢時餘額不足");
else{
// sleep凸顯現實的問題
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money=account.money-drawMoney;
System.out.println(this.getName()+"取了"+drawMoney+" : "+account.name+"餘額"+account.money);
}
}
}
wife取了100 : wedding fund餘額-50
you取了50 : wedding fund餘額-50
兩個人都操作了這100 所以總共提了150
e.g.3
public class List {
public static void main(String args[]){
ArrayList<String> list = new ArrayList<String>();
for(int i=0;i<10000;i++){
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try{
Thread.sleep(1000);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(list.size());
}
}
9998 --> 還是沒有全部thread都有 (不安全)
如何解決同步問題 - 1.synchronized
為控制一個對象或是方法,使用中的thread將其資源上鎖,使用完再釋放,
因此資源同一時間不會被多個thread給操作
以此保證程序運行的安全,但是同時也會降低效率
- sysynchronized 方法
public synchronized type method(int args){...}
- synchronized 區塊
synchronized(Obj){...}
一般來說,我們是針對被曾刪改的對象去把它鎖起來
--修改剛剛的範例--
e.g.1 鎖住方法
// 3 threads to controll same resource
public class Buyer {
public static void main(String args[]){
BuyTicket bt = new BuyTicket();
new Thread(bt,"t1").start();
new Thread(bt,"t2").start();
new Thread(bt,"t3").start();
}
}
class BuyTicket implements Runnable{
private int t=10;
boolean flag=true;
public void run(){
while(flag){
buy();
}
}
private synchronized void buy() {
if(t<=0){
flag=false;
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到"+t--);
}
}
e.g.2 鎖住物件
public class Bank {
public static void main(String args[]){
Account a = new Account(100, "wedding fund");
Drawbank you = new Drawbank(a,50,"you");
Drawbank wife = new Drawbank(a,100,"wife");
you.start();
wife.start();
}
}
class Account{
int money;
String name;
public Account(int m,String n){
money=m;
name=n;
}
}
class Drawbank extends Thread{
Account account;
int drawMoney;
public Drawbank(Account account,int drawMoney,String name){
super(name);
this.account=account;
this.drawMoney=drawMoney;
}
public void run(){
synchronized(account){
if(account.money<drawMoney) System.out.println(this.getName()+"取錢時餘額不足");
else{
// sleep凸顯現實的問題
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.money=account.money-drawMoney;
System.out.println(this.getName()+"取了"+drawMoney+" : "+account.name+"餘額"+account.money);
}
}
}
}
you取了50 : wedding fund餘額50
wife取錢時餘額不足
e.g.3 鎖住物件
public class List {
public static void main(String args[]){
ArrayList<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(1000);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(list.size());
}
}
10000
如何解決同步問題 - 2.ReentrantLock
使用ReentrantLock創立的物件手動鎖定與釋放特定程式碼區域的資源
private final ReentrantLock lock = new ReentrantLock();
public void m(){
lock.lock();
try{
//保證thread安全之程式碼
}
finally{
lock.unlock();
}
}
e.g.
public class Lock {
public static void main(String args[]){
BuyTicket bt =new BuyTicket();
new Thread(bt).start();
}
}
class Buytickets implements Runnable{
int ticket = 10;
private final ReentrantLock lock = new ReentrantLock();
public void run(){
while(true){
try{
lock.lock();
if(ticket>0){
try{
Thread.sleep(1000);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(ticket--);
}
else break;
}
finally{
lock.unlock();
}
}
}
}
synchronized vs ReentrantLock
- lock的鎖定與釋放都需要手動設定 syn則是出了程式快就自動釋放
- lock只有區域鎖 syn有區域鎖跟方法鎖
- lock 性能跟擴展性較佳
- 在使用建議上: lock --> syn區域鎖 --> syn方法鎖
Deadlock
當thread握有別的thread想要的資源,卻又不放下自己手中的資源,此循環的資源請求情況,就形成了deadlock
e.g.
public class Deadlock{
public static void main(String args[]){
new Makeup(0, "Jassica").start();;
new Makeup(1, "Susan").start();;
}
}
class Lipstick{}
class Mirror{}
class Makeup extends Thread{
//表示口紅及鏡子都只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int chioce;
String girl;
public Makeup(int choice,String name){
this.chioce=choice;
girl=name;
}
public void run(){
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void makeup() throws InterruptedException{
if(chioce==0){ //先拿口紅再拿鏡子
synchronized(lipstick){ //鎖住lipstick
System.out.println(this.girl+" get lipstick!");
Thread.sleep(1000);
synchronized(mirror){ //鎖住lipstick又鎖住mirror
System.out.println(this.girl+" get mirror!");
}
}
}
else{
synchronized(mirror){ //鎖住mirror
System.out.println(this.girl+" get mirror!");
Thread.sleep(1000);
synchronized(lipstick){ //鎖住mirror又鎖住lipstick
System.out.println(this.girl+" get lipstick!");
}
}
}
}
}
Susan get mirror!
Jassica get lipstick!
Susan有了鏡子不放想要口紅
Jassica有了口紅不放想要鏡子 -->形成deadlock
解決方式: 持有資源a並且要請求資源b時,要釋放手中的資源a
public class Deadlock{
public static void main(String args[]){
new Makeup(0, "Jassica").start();;
new Makeup(1, "Susan").start();;
}
}
class Lipstick{}
class Mirror{}
class Makeup extends Thread{
//表示口紅及鏡子都只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int chioce;
String girl;
public Makeup(int choice,String name){
this.chioce=choice;
girl=name;
}
public void run(){
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void makeup() throws InterruptedException{
if(chioce==0){ //先拿口紅再拿鏡子
synchronized(lipstick){ //鎖住lipstick
System.out.println(this.girl+" get lipstick!");
Thread.sleep(1000);
}
synchronized(mirror){ //放掉lipstick鎖住mirror
System.out.println(this.girl+" get mirror!");
}
}
else{
synchronized(mirror){ //鎖住mirror
System.out.println(this.girl+" get mirror!");
Thread.sleep(1000);
}
synchronized(lipstick){ //放掉mirror鎖住lipstick
System.out.println(this.girl+" get lipstick!");
}
}
}
}
Jassica get lipstick!
Susan get mirror!
(頓一下)
Jassica get mirror!
Susan get lipstick!
生產者與消費者模型
code1 wait()/notifyAll()
public class PCproblem {
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;
}
public void run(){
for(int i=0;i<100;i++){
System.out.println("生產了"+i+"隻");
container.push(new Chicken(i));
}
}
}
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container=container;
}
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){
if(count==chickens.length){
//通知消費者消費,生產者等待
try{
this.wait();
}catch(Exception e){
e.printStackTrace();
}
}
chickens[count]=chicken;
count++;
//可以通知消費者了
this.notifyAll();
}
//消費者拿出產品
public synchronized Chicken pop(){
if(count==0){
//等待生產者生產
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
Chicken chicken =chickens[count];
//通知生產者可以生產
this.notifyAll();
return chicken;
}
}
code2 使用boolean flag
public class PCproblem2 {
public static void main(String args[]){
Stage stage = new Stage();
Actor1 a1 = new Actor1(stage);
Actor2 a2 = new Actor2(stage);
a1.start(); a2.start();
}
}
class Actor1 extends Thread{
Stage stage = new Stage();
public Actor1(Stage stage){
this.stage=stage;
}
public void run(){
for(int i=0;i<20;i++){
this.stage.play1("綜藝大集合");
}
}
}
class Actor2 extends Thread{
Stage stage = new Stage();
public Actor2(Stage stage){
this.stage=stage;
}
public void run(){
for(int i=0;i<20;i++){
this.stage.play2("快樂有go正");
}
}
}
//演員跟觀眾都會用到舞台 演員表演/觀眾觀賞
class Stage{
// 演員1表演 演員2等待 T
// 演員2觀看 演員1等待 F
String program;
boolean flag=true;
//演員1
public synchronized void play1(String program){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演員1表演了:"+program);
//通知演員2表演
this.notifyAll();
this.program=program;
this.flag=!this.flag;
}
//演員2
public synchronized void play2(String program){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演員2表演了:"+program);
//通知演員1表演
this.notifyAll();
this.program=program;
this.flag=!this.flag;
}
}
演員1表演了:綜藝大集合
演員2表演了:快樂有go正
演員1表演了:綜藝大集合
演員2表演了:快樂有go正
演員1表演了:綜藝大集合
演員2表演了:快樂有go正
演員1表演了:綜藝大集合
演員2表演了:快樂有go正
...
thread pool
public class Pool {
public static void main(String args[]){
//1.建立服務可以創建線程池 ,參數為pool中的線程數量
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new thread());
service.execute(new thread());
service.execute(new thread());
service.execute(new thread());
//2.關閉服務連接
service.shutdown();
}
}
class thread implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName());
}
}