黑馬程序員_Java_多線程

線程的理解

 1、同一個應用中,多個任務同時進行。就像editplus編輯工具,打開一個文件窗口就是一個線程。

 2、線程可以有多個,但cpu每時每刻只做一件事(多核除外)。由於cpu處理速度很快,我們就感覺是同時進行的。所以宏觀上,線程是併發進行的;從微觀角度看,線程是異步執行的。

 3、使用線程的目的是最大限度的利用cpu資源。想想當你在editplus中按下"ctrl + shift +S"保存全部文件的時候,如果要保存的文件比較多,沒有多線程的話,前面的文件沒有完成操作的話,後面的操作是執行不了的~!

創建線程

實現多線程,有兩種手段:

一:繼承Thread類(啓動線程方法:new MyThread().start();)

步驟:
1,定義類繼承Thread。
2,複寫Thread類中的run方法。
目的:將自定義代碼存儲在run方法。讓線程運行。
3,調用線程的start方法,
該方法兩個作用:啓動線程,調用run方法。

線程中,start和run的區別
start():是開啓線程並調用run方法
run():存放的是線程執行的代碼,如果單純的調用run方法,只是普通的創建對象調用方法,而並沒有開啓線程

class 類名 extends Thread{
    @Override
    public void run() {
     //code
    }
}
class Show extends Thread 
{
	public void run()
	{
		for (int i =0;i<5 ;i++ )
		{
			System.out.println(name +"_" + i);
		}
	}

	public Show(){}
	public Show(String name)
	{
		this.name = name;
	}
	private String name;

	public static void main(String[] args) 
	{
		new Show("csdn").run();
		new Show("黑馬").run();
	}
}

【運行結果】:



發現這些都是順序執行的,說明調用run()方法不對,應該調用的是start()方法。

把上面的主函數修改爲如下:

        public static void main(String[] args) 
	{
		new Show("csdn").start();
		new Show("黑馬").start();
	}

在命令行執行:javac Show.java  java Show,輸出的可能的結果如下:


因爲需要用到CPU的資源,多個線程都獲取cpu的執行權,cpu執行到誰,誰就運行。所以每次的運行結果基本是都不一樣的;

這也是多線程的一個特性:隨機性。誰搶到誰執行,至於執行多長,cpu說了算。

那麼:爲什麼我們不能直接調用run()方法呢?

我的理解是:線程的運行需要本地操作系統的支持。

如果你查看start的源代碼的時候,會發現:

 public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

 private native void start0();
在start0()方法中jvm調用run()方法,這個這個方法用了native關鍵字,native表示調用本地操作系統的函數,多線程的實現需要本地操作系統的支持。

二.實現Runable接口,(啓動線程方法:new Thread(new MyRunnable()).start();)

class 類名 implements Runnable {
    @Override
    public void run() {
        //code
    }
}
class Show implements Runnable 
{
	public Show(){}
	public Show(String name)
	{
		this.name = name;
	}

	@Override
	public void run()
	{
		for (int i =0;i<5 ;i++ )
		{
			System.out.println(name +"_" + i);
		}
	}
	private String name;

	public static void main(String[] args) 
	{
		new Thread(new Show("csdn")).start();
		new Thread(new Show("黑馬")).start();
	}
}
【可能的運行結果】:

關於選擇繼承Thread還是實現Runnable接口?

其實Thread也是實現Runnable接口的

public class Thread implements Runnable {
    /* Make sure registerNatives is the first thing <clinit> does. */
    private static native void registerNatives();
    static {
        registerNatives();
    }
 
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
}
其實Thread中的run方法調用的是Runnable接口的run方法。不知道大家發現沒有,ThreadRunnable都實現了run方法,這種操作模式其實就是代理模式。

Thread和Runnable的區別:

如果一個類繼承Thread,則不能資源共享(有可能是操作的實體不是唯一的);但是如果實現了Runable接口的話,則可以實現資源共享。

class Show extends Thread
{
	@Override
	public void run()
	{
		for (int i = 0; i < 5 ; i++ )
		{
			if (count > 0)
			{
				System.out.println(Thread.currentThread().getName()+": count=" + count--);
			}
		}
	}
	private int count = 5;

	public static void main(String[] args) 
	{
		new Show().start();//new 出來多個實體
		new Show().start();
	}
}
【運行結果】:

public static void main(String[] args) 
	{
		Show s = new Show();
		s.start();
		s.start();
	}


我們再以實現Runnable接口的方式修改上面的程序:

class Show implements Runnable
{
	private int count = 10;//假設有10張票
	@Override
	public void run()
	{
		for (int i = 0; i < 5 ; i++ )
		{
			if (this.count > 0)
			{
				System.out.println(Thread.currentThread().getName()+"正在賣票" + this.count--);
			}
		}
	}

	public static void main(String[] args) 
	{
		Show s = new Show(); //注意必須保證只對1個實體s操作
		new Thread(s,"窗口1").start();
		new Thread(s,"窗口2").start();
		new Thread(s,"窗口3").start();
	}
}

【運行結果】:


總結:

實現Runnable接口比繼承Thread類所具有的優勢:

1):適合多個相同的程序代碼的線程去處理同一個資源

2):可以避免java中的單繼承的限制

3):增加程序的健壯性,代碼可以被多個線程共享,代碼和數據獨立。

所以,還是以實現接口的方式來創建好些。

java中所有的線程都是同時啓動的,至於什麼時候,哪個先執行,完全看誰先得到CPU的資源;每次程序運行至少啓動2個線程。一個是main線程,一個是垃圾收集線程。因爲每當使用java命令執行一個類的時候,實際上都會啓動一個JVM,每一個jVM實際上就是在操作系統中啓動了一個進程。

設置線程優先級主線程的優先級是5,不要誤以爲優先級越高就先執行。誰先執行還是取決於誰先去的CPU的資源

<span style="font-size:14px;">Thread t = new Thread(myRunnable);
t.setPriority(Thread.MAX_PRIORITY);//一共10個等級,Thread.MAX_PRIORITY表示最高級10
t.start();</span>

判斷線程是否啓動
class Show implements Runnable
{
	
	public void run()
	{
		for (int i = 0; i < 5 ; i++ )
		{
			System.out.println(Thread.currentThread().getName());
		}
	}

	public static void main(String[] args) 
	{
		Show s = new Show();
		Thread t = new Thread(s);

		System.out.println("線程啓動前:" + t.isAlive());
		t.start();
		System.out.println("線程啓動後:" + t.isAlive());
		
	}
}

【運行結果】:



主線程也有可能在子線程結束之前結束。並且子線程不受影響,不會因爲主線程的結束而結束。

join,sleep,yield的用法與區別

join方法:假如你在A線程中調用了B線程的join方法B.join();,這時B線程繼續運行,A線程停止(進入阻塞狀態)。等B運行完畢A再繼續運行。

sleep方法:線程中調用sleep方法後,本線程停止(進入阻塞狀態),運行權交給其他線程。

yield方法:線程中調用yield方法後本線程並不停止,運行權由本線程和優先級不低於本線程的線程來搶。(不一定優先級高的能先搶到,只是優先級高的搶到的時間長)


後臺線程

<span style="font-size:14px;">Thread t = new Thread(new Show());
t.setDaemon(true);
t.start();</span>
java程序中,只要前臺有一個線程在運行,整個java程序進程不會消失,所以此時可以設置一個後臺線程,這樣即使java進程消失了,此後臺線程依然能夠繼續運行。
結束線程(修改標示符flag爲false來終止線程的運行)

<span style="font-size:14px;">class Show implements Runnable
{
	private boolean flag = true;
	public void run()
	{
		while(flag)
		{
			System.out.println(Thread.currentThread().getName()+" is living");
		}
	}

	public void shutDown()
	{
		this.flag = false;
	}

	public static void main(String[] args) 
	{
		Show s = new Show();
		Thread t = new Thread(s);

		t.start();

		try
		{
			Thread.sleep(2);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		s.shutDown();
	}
}</span><span style="color: rgb(128, 128, 128); font-size: 15px;">
</span>


線程同步synchronized

  synchronized可以修飾方法,或者方法內部的代碼塊。被synchronized修飾的代碼塊表示:一個線程在操作該資源時,不允許其他線程操作該資源。

【問題引出】:比如說對於賣票系統,有下面的代碼:

class Show implements Runnable
{
	private int count =5;//5張票要賣
	public void run()
	{
		for (int i = 0; i < 10 ; i++ )
		{
			if (count > 0)
			{
				try
				{
					Thread.sleep(200);
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+","+count--);
		}

	}

	
	public static void main(String[] args) 
	{
		Show s = new Show();
		new Thread(s,"A").start();
		new Thread(s,"B").start();
		new Thread(s,"C").start();
	}
}
【運行結果】:


這裏出現了負數,顯然這個是錯的。,應該票數不能爲負值。

如果想解決這種問題,就需要使用同步

所謂同步就是在統一時間段中只有有一個線程運行,其他的線程必須等到這個線程結束之後才能繼續執行。


【使用線程同步解決問題】

採用同步的話,可以使用同步代碼塊和同步方法兩種來完成。

同步代碼塊

語法格式:

synchronized(同步對象){

 //需要同步的代碼

}

但是一般都把當前對象this作爲同步對象。

class Show implements Runnable
{
	private int count =5;
	public void run()
	{
		for (int i = 0; i < 20 ; i++ )
		{
			synchronized(this)
			{
				if (count > 0)
				{
					try
					{
						//Thread.currentThread().yield();
						Thread.sleep(300);
					}
					catch (Exception e)
					{
						e.printStackTrace();
					}
				System.out.println(Thread.currentThread().getName()+","+count--);
				}
			}
		}

	}

	
	public static void main(String[] args) 
	{
		Show s = new Show();
		Thread t1 = new Thread(s);
		Thread t2 = new Thread(s);
		Thread t3 = new Thread(s);
		t3.start();
		t1.start();
		t2.start();

	}
}

【運行結果】:


也可以採用同步方法

語法格式爲synchronized 方法返回類型方法名(參數列表){

    // 其他代碼

}

class Show implements Runnable
{
	private int count =5;
	public void run()
	{
		for (int i = 0; i < 20 ; i++ )
		{
			sale();
		}

	}

	public synchronized void sale()
	{
		if (count > 0)
				{
					try
					{
						//Thread.currentThread().yield();
						Thread.sleep(300);
					}
					catch (Exception e)
					{
						e.printStackTrace();
					}
				System.out.println(Thread.currentThread().getName()+","+count--);
				}
	}

	
	public static void main(String[] args) 
	{
		Show s = new Show();
		Thread t1 = new Thread(s);
		Thread t2 = new Thread(s);
		Thread t3 = new Thread(s);
		t3.start();
		t1.start();
		t2.start();

	}
}
同步的前提:
1,必須要有兩個或者兩個以上的線程。
2,必須是多個線程使用同一個鎖。
必須保證同步中只能有一個線程在運行。

好處:解決了多線程的安全問題。
弊端:多個線程都需要判斷鎖,較爲消耗資源,

wait、notify、notifyAll的用法

    wait方法:當前線程轉入阻塞狀態,讓出cpu的控制權,解除鎖定。

    notify方法:喚醒因爲wait()進入阻塞狀態的其中一個線程。

  notifyAll方法: 喚醒因爲wait()進入阻塞狀態的所有線程。

 這三個方法都必須用synchronized塊來包裝,而且必須是同一把鎖,不然會拋出java.lang.IllegalMonitorStateException異常。


當多個線程共享一個資源的時候需要進行同步,但是過多的同步可能導致死鎖同步中嵌套同步容易引發死鎖

此處列舉經典的【生產者和消費者問題】:

class Info {
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    private String name = "lfz";
    private int age = 20;
}
/**
 * 生產者
 */
class Producer implements Runnable{
    private Info info=null;
    Producer(Info info){
        this.info=info;
    }
     
    public void run(){
        boolean flag=false;
        for(int i=0;i<10;++i){
            while (true)
            {
               if(flag){
                this.info.setName("adanac");
                this.info.setAge(20);
                flag=false;
                }else{
                    this.info.setName("jean");
                    this.info.setAge(30);
                    flag=true;
               }
           }
        }
    }
}
/**
 * 消費者類
 * */
class Consumer implements Runnable{
    private Info info=null;
    public Consumer(Info info){
        this.info=info;
    }
     
    public void run(){
        for(int i=0;i<10;++i){
           System.out.println(this.info.getName()+"<---->"+this.info.getAge());
        }
    }
}


class Show 
{

    public static void main(String[] args) 
    {
        Info info=new Info();
        Producer pro=new Producer(info);
        Consumer con=new Consumer(info);
        new Thread(pro).start();
        new Thread(con).start();
    }
}


從結果中看到,名字和年齡並沒有對應。

那麼如何解決呢?

1)加入同步

2)加入等待和喚醒

先來看看加入同步會是如何:

class Info {
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
	public synchronized void set(String name, int age){
        this.name=name;
        this.age=age;
    }
     
    public synchronized void get(){
        System.out.println(this.getName()+"<===>"+this.getAge());
	} 
    private String name = "lfz";
    private int age = 20;
}
/**
 * 生產者
 */
class Producer implements Runnable{
    private Info info=null;
    Producer(Info info){
        this.info=info;
    }
     
    public void run(){
        boolean flag=false;
        for(int i=0;i<10;++i){
			while (true)
			{
				if(flag){
					this.info.set("adanac",10);
					
					flag=false;
				}else{
					this.info.set("lfz",20);
					
					flag=true;
				}
			}
        }
    }
}
/**
 * 消費者類
 * */
class Consumer implements Runnable{
    private Info info=null;
    public Consumer(Info info){
        this.info=info;
    }
     
    public void run(){
        for(int i=0;i<10;++i){
            try{
                Thread.sleep(100);
            }catch (Exception e) {
                e.printStackTrace();
            }
            this.info.get();
        }
    }
}

class Show 
{
	
	public static void main(String[] args) 
	{
		Info info=new Info();
        Producer pro=new Producer(info);
        Consumer con=new Consumer(info);
        new Thread(pro).start();
        new Thread(con).start();

	}
}


運行結果來看,錯亂的問題解決了,現在是adanac對應20,jean對於30。

/*
對於多個生產者和消費者。
爲什麼要定義while判斷標記。
原因:讓被喚醒的線程再一次判斷標記。

爲什麼定義notifyAll,
因爲需要喚醒對方線程。
因爲只用notify,容易出現只喚醒本方線程的情況。導致程序中的所有線程都等待。
*/

但是還是出現了重複讀取的問題,也肯定有重複覆蓋的問題。

如果想解決這個問題,就需要使用Object類幫忙了,我們可以使用其中的等待和喚醒操作。

要完成上面的功能,我們只需要修改Info類飢渴,在其中加上標誌位,並且通過判斷標誌位完成等待和喚醒的操作,代碼如下:
class Info {
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
	public synchronized void set(String name, int age){
        if (!flag)
        {
			try
			{
				super.wait();
				
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}

        }
		this.name=name;
		this.age=age;
		flag = false;
		super.notify();
    }
     
    public synchronized void get(){
       if (flag)
       {
		   try
		   {
			 super.wait();
		   }
		   catch (Exception e)
		   {
			   e.printStackTrace();
		   }
       }
		System.out.println(this.getName()+"<===>"+this.getAge());
		flag = true;
		super.notify();
	} 
    private String name = "xiaoli";
    private int age = 22;
	private boolean flag = false;
}
/**
 * 生產者
 */
class Producer implements Runnable{
    private Info info=null;
    Producer(Info info){
        this.info=info;
    }
     
    public void run(){
        boolean flag=false;
        for(int i=0;i<10;++i){
			while (true)
			{
				if(flag){
					this.info.set("adanac",10);
					
					flag=false;
				}else{
					this.info.set("lfz",20);
					
					flag=true;
				}
			}
        }
    }
}
/**
 * 消費者類
 * */
class Consumer implements Runnable{
    private Info info=null;
    public Consumer(Info info){
        this.info=info;
    }
     
    public void run(){
        for(int i=0;i<10;++i){
            this.info.get();
        }
    }
}

class Show 
{
	
	public static void main(String[] args) 
	{
		Info info=new Info();
        Producer pro=new Producer(info);
        Consumer con=new Consumer(info);
        new Thread(pro).start();
        new Thread(con).start();

	}
}

【運行結果】:



/*
JDK1.5 中提供了多線程升級解決方案。
將同步Synchronized替換成現實Lock操作。
將Object中的wait,notify notifyAll,替換了Condition對象。

Lock:替代了Synchronized
lock 
unlock
newCondition()

Condition:替代了Object wait notify notifyAll
await();
signal();
signalAll();
*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章