1. 程式人生 > >java學習day18~19---多執行緒

java學習day18~19---多執行緒

一.執行緒和程序(單核)
    程序:CPU中有多個程式“同時”在使用,CPU在一個時間點上只能執行一件事情,而CPU分片段執行不同程式,
            但是切換速率十分快,給人感覺是在同時使用
          程序的作用不是提高執行速度,而是提高CPU的使用率
    執行緒:一個程序中的執行場景,一個程序有多個執行緒,如淘寶搶購,都在使用百度搜索
            執行緒的作用不是提高執行速度,而是提高應用程式的使用率

    執行緒的存在:
    執行緒之間的堆記憶體和方法區記憶體是共享的,但不同執行緒都是不同的棧記憶體,
二.執行緒的建立和啟動
方式1:子類繼承Thread,重寫裡面的run方法,呼叫start方法啟動(也可以不用子類,直接new一個Thread,用匿名內部類的方式)
注意:run方法不能丟擲異常

public class ThreadWork
{
    public static void main(String[] args) {
        Thread t = new Processer();
        t.start();   //這個程式碼告訴JVM給t執行緒分配一個新的棧,啟動之後自動呼叫run方法
        for(int i = 0; i < 100; i++) {
            System.out.println("main---" + i);
        }
    }
}

class Processer extends Thread
{
    public void run() {
        for(int i = 0; i < 100; i++) {
            System.out.println("thread---" + i);
        }
    }
}


//匿名內部類的方式

public class ThreadWork
{
    public static void main(String[] args) {
        Thread t = new Thread() {
            public void run() {
                for(int i = 0; i < 100; i++) {
                    System.out.println("thread---" + i);
                }
            }
        };

        t.start();
        for(int i = 0; i < 100; i++) {
            System.out.println("main---" + i);
        }
    }
}

//拉姆達表示式簡化後

public class ThreadWork
{
    public static void main(String[] args) {
        new Thread(() -> {
            for(int i = 0; i < 100; i++) {
                System.out.println("thread---" + i);
            }
        }).start();

        for(int i = 0; i < 100; i++) {
            System.out.println("main---" + i);
        }
    }
}

//執行結果:兩個迴圈隨機互動執行
//結論:main方法結束之後只是主執行緒結束,但是其他執行緒還有棧幀,所以main方法結束,程式可能還在執行
//注意:如果只調用run方法,那麼這就只是普通的方法呼叫,整個程式只有一個主執行緒,等到run方法執行結束之後,才會執行main方法中的迴圈

方式2:子類繼承Runnable介面,實現裡面的run方法(推薦使用)

public class ThreadWork
{
    public static void main(String[] args) {
        Thread t = new Thread(new Processer());   //Thread t = new Thread(Runnable介面);
        t.start();   //這個程式碼告訴JVM給t執行緒分配一個新的棧,啟動之後自動呼叫run方法
        for(int i = 0; i < 100; i++) {
            System.out.println("main---" + i);
        }
    }
}

class Processer implements Runnable
{
    public void run() {
        for(int i = 0; i < 100; i++) {
            System.out.println("thread---" + i);
        }
    }
}

三.執行緒的生命週期

進入就緒狀態之後,執行緒有權利去搶奪CPU時間片,這個時間片就是執行權,搶到之後就可以進入執行狀態,當時間
用完而執行還沒有結束之後,就會繼續返回到就緒狀態,繼續搶奪時間片,然後繼續接著執行run方法,直至方法執行完畢

用第一個建立執行緒的例子來講,得到的結果是兩個迴圈交替輸出,這就是因為兩個執行緒搶奪時間片,先搶到的先執行,
搶到的時間不夠返回來繼續搶奪執行,直至執行緒結束

四.常用的方法

getName();   獲取執行緒名字
currentThread();  獲取當前的執行緒
sleep();   讓當前執行緒休眠一段指定時間,阻塞執行緒,讓CPU騰出時間片,讓其他執行緒執行,是一個靜態方法
getPriority();   獲取執行緒優先順序
join();  等待某個執行緒執行結束
interrupt() 可以打斷正在等待的執行緒(包括sleep, join的等待)
yield()      讓位,給同一個優先順序的執行緒讓位,但是讓位時間不固定,也是靜態方法

不推薦使用的方法
stop() 讓執行緒停止
suspend() 讓執行緒暫停
resume() 讓執行緒繼續


sleep()詳解

public class ThreadWork
{
    public static void main(String[] args) throws Exception{
        Thread t = new Thread(() -> {
            for(int i = 0; i < 30; i++) {
                System.out.println(Thread.currentThread().getName() + "---" + i);
            }
        });
        
        t.setName("t1");    //將執行緒命名為t1
        t.start();   //讓執行緒執行
        t.sleep(5000);  //此時呼叫sleep方法會讓t執行緒休眠5秒嗎   

        System.out.println("hello");
    }
}

解析:不會,因為sleep是靜態方法,與物件無關,t.start()相當於是Thread.start(), 而Thread當前是主執行緒
       所以t執行緒不會休眠,而是主執行緒休眠

//interrupt()詳解

public class ThreadWork
{
    public static void main(String[] args) throws Exception{
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(1000000000000L);  //很長時間段的休眠
            }
            catch (Exception e) {
                e.printStackTrace();
            }
            
            for(int i = 0; i < 30; i++) {
                System.out.println("thread" + i);
            }
        });
        
        t.start();   //讓執行緒執行
        Thread.sleep(5000);  //主執行緒休眠五秒
        t.interrupt();  //五秒後打斷t執行緒的休眠

        System.out.println("hello");
    }
}

//結果:五秒後t執行緒執行語句,因為休眠被打斷
五.執行緒同步
同步程式設計模型:t1和t2執行緒必須等待一個執行完畢之後,再執行另一個
非同步程式設計模型:t1和t2執行緒各自執行各自的,誰也不等誰引入執行緒同步的原因:為了資料安全
使用執行緒同步的條件:
    1.多執行緒環境    2.共享一個數據        3.共享的資料涉及到修改操作

public class ThreadWork
{
    public static void main(String[] args) {
        Account a = new Account("張三", 5000);
        Thread t1 = new Thread(new Processor(a));
        Thread t2 = new Thread(new Processor(a));
        t1.start();
        t2.start();
    }
}

class Processor implements Runnable
{
    Account a;

    public Processor(Account a) {
        this.a = a;
    }
    public void run() {
        a.withdraw(200);
        System.out.println("取了200,還剩" + a.getBalance());
    }
}

//賬戶類
class Account
{
    private String actno;
    private int balance;

    public Account(String actno, int balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public String getActno() {
        return actno;
    }

    public void setBalance(int balance) {
        this.balance = balance; 
    }

    public int getBalance() {
        return balance;
    }

    //提供一個取款方法
    public void withdraw(int money) {
        int after = balance - money;
        this.setBalance(after);
    }
}

//執行結果:隨機輸出4600,4800
//因為兩個執行緒互動了,一個取款完畢之後,還沒有更新,第二個有可能也已經執行,所以餘額有問題

此時需要兩個執行緒分別執行,引入執行緒同步

//其他程式碼不變,修改取款操作

public void withdraw(int money) {   
    synchronized(this) {   //用鎖將取款的具體操作鎖住
        try{
            Thread.sleep(1000);
        } catch(Exception e) {
        }
        int after = balance - money;
        this.setBalance(after);
    }
}

用synchronized(this)鎖住,那麼程式執行的時候,就回去尋找this物件鎖,找到則執行,執行完畢歸還物件鎖,否則等待,直到獲取物件鎖

六.類鎖
關於類鎖:類鎖只有一把,且與物件無關

雖然t1和t2是兩個不同的執行緒,但是公用一個myClass物件,而該類中的方法用的是類鎖,類鎖只有一把,與物件沒有關係

public class ThreadWork {
	public static void main(String[] args) throws Exception {
		myClass mc = new myClass();
		Processor p1 = new Processor(mc);
		Processor p2 = new Processor(mc);

		Thread t1 = new Thread(p1);
		Thread t2 = new Thread(p2);
		t1.setName("t1");
		t2.setName("t2");

		t1.start();
		Thread.sleep(1000); //為了保證t1執行緒先執行
		t2.start();
	}
}

class Processor implements Runnable {
	myClass mc;
	Processor(myClass mc) {
		this.mc = mc;
	} 
	public void run() {
		if(Thread.currentThread().getName().equals("t1")) {
			mc.m1();
		}
		if(Thread.currentThread().getName().equals("t1")) {
			mc.m2();
		}
	}
}

class myClass
{
	public synchronized static void m1() {
		try{
			Thread.sleep(5000);
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("m1...");
	}
	
	public synchronized static void m2() {
		System.out.println("m2...");
	}
}

七.死鎖

兩個執行緒互相鎖住,如兩個執行緒t1,t2,兩個物件o1,o2,t1執行緒先鎖住o1物件,然後鎖o2物件,t2執行緒先鎖o2物件,再鎖o1物件,但是因為兩個執行緒誰都不肯先放,所以進入了僵持狀態,形成死鎖

public class ThreadWork 
{
	public static void main(String[] args) {
		Object o1 = new Object();
		Object o2 = new Object();

		Thread t1 = new Thread(new T1(o1, o2));
		Thread t2 = new Thread(new T2(o1, o2));

		t1.start();
		t2.start();
	}
}

class T1 implements Runnable
{
	Object o1;
	Object o2;
	T1(Object o1, Object o2) {
		this.o1 = o1;
		this.o2 = o2;
	}

	public void run() {
		synchronized(o1) {
			try {
				Thread.sleep(1000);
			} catch(Exception e) {
					e.printStackTrace();
				}
			synchronized(o2) {}
		}
	}
}

class T2 implements Runnable
{
	Object o1;
	Object o2;
	T2(Object o1, Object o2) {
		this.o1 = o1;
		this.o2 = o2;
	}

	public void run() {
		synchronized(o2) {
			try {
				Thread.sleep(1000);
			} catch(Exception e) {
					e.printStackTrace();
				}
			synchronized(o1) {}
		}
	}
}