天天看點

黑馬程式員_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();
	}
}
           

【運作結果】:

黑馬程式員_Java_多線程
黑馬程式員_Java_多線程
黑馬程式員_Java_多線程

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

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

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

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

黑馬程式員_Java_多線程
黑馬程式員_Java_多線程

因為需要用到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();
	}
}
           

【可能的運作結果】:

黑馬程式員_Java_多線程
黑馬程式員_Java_多線程

關于選擇繼承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 方法。不知道大家發現沒有, Thread 和 Runnable 都實作了 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();
	}
}
           

【運作結果】:

黑馬程式員_Java_多線程
黑馬程式員_Java_多線程
public static void main(String[] args) 
	{
		Show s = new Show();
		s.start();
		s.start();
	}
           
黑馬程式員_Java_多線程
黑馬程式員_Java_多線程

我們再以實作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();
	}
}
           

【運作結果】:

黑馬程式員_Java_多線程
黑馬程式員_Java_多線程

總結:

實作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());
		
	}
}
           

【運作結果】:

黑馬程式員_Java_多線程
黑馬程式員_Java_多線程

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

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>
           
黑馬程式員_Java_多線程

線程同步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();
	}
}
           

【運作結果】:

黑馬程式員_Java_多線程
黑馬程式員_Java_多線程

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

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

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

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

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

同步代碼塊:

文法格式:

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();

	}
}
           

【運作結果】:

黑馬程式員_Java_多線程
黑馬程式員_Java_多線程

也可以采用同步方法:

文法格式為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();
    }
}
           
黑馬程式員_Java_多線程
黑馬程式員_Java_多線程

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

那麼如何解決呢?

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();

	}
}
           
黑馬程式員_Java_多線程
黑馬程式員_Java_多線程

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

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

如果想解決這個問題,就需要使用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();

	}
}
           

【運作結果】:

黑馬程式員_Java_多線程
黑馬程式員_Java_多線程