java 多線程詳解

關於線程的概念

1、程序一般是指一段靜態代碼,靜態對象;進程是程序的一次執行過程或正在運行的程序動態的的過程,有它的產生、存在和消亡的過程;線程是指一個程序內部的一條執行路徑,也就是說每一個任務就代表一個線程。進程與線程相互聯繫然而又有區別,從本質上來說每個進程都有自己的一整套變量,而線程間可以共享變量,共享變量使得線程之間的通信比進行之間通信更有效、更容易。
2、並不是每種情況都需要多線程,當程序需要同時執行兩個或多個任務;當程序需要實現一些等待任務,如用戶的輸入,文件讀寫,網絡操作,搜索;當程序需要運行一些後臺程序。在這些情況下我們需要使用多線程。
3、線程的生命週期:每個線程都有新建、就緒、運行、阻塞、死亡這五種狀態


創建線程的兩種方式

1、繼承Thread類:
a.創建一個類繼承Thread;b.重寫run()方法,將要執行的業務邏輯代碼放在run()方法體中;c.在主類中創建子線程類的實例;d.調用線程方法start(),將此線程變爲就緒狀態。
package com.thread;
public class TestThread {
	public static void main(String [] args){
	       //3、創建一個子類對象
		SubThread st=new SubThread();
		//4、調用線程的start()方法,開始啓動線程;調用相應的run()方法
		st.start();
		//這個是主線程的方法 
		for(int i=1;i<=100;i++){
			System.out.println(Thread.currentThread().getName()+":"+i);
			
		}
	}
}
//1、創建一個繼承Thread的子類
class SubThread extends Thread{
	//2、重寫run()方法
	@Override
	public void run() {
            for(int i=1;i<=100;i=i+2){
		 System.out.println(Thread.currentThread().getName()+":"+i);
	    }
	}
}
2、實現Runnable接口
a.創建一個類實現Runnable接口;b.重寫run()方法,將業務邏輯代碼放入在run()方法中;c.創建一個Runbable接口實現類的對象的實例 d.將這個對象作爲形參傳遞給Thread類的構造器中,創建Thread類對象,然後再啓動線程start()方法。
package com.thread;
public class ThreadTest {
    public static void main(String[] args) throws Exception{
    	MyRunnable r=new MyRunnable();//c.創建一個Runbable接口實現類的對象的實例 
    	Thread t=new Thread(r);//d.將這個對象作爲形參傳遞給Thread類的構造器中,創建Thread類對象,然後再啓動線程start()方法。
    	t.setName("自定義線程:");
    	t.start();
    	System.out.println(Thread.currentThread().getName());
	}
}
class MyRunnable implements Runnable{//a.創建一個類實現Runnable接口
	@Override
	public void run() {//b.重寫run()方法,將業務邏輯代碼放入在run()方法中
           for(int i=0;i<10;i++){
        	   
    	   System.out.println(Thread.currentThread().getName()+i);
       }
	}
}

3、這兩種方式創建線程的異同在於a.Thread類其實也是實現Runnable接口;b.實現Runnable接口的方式要優於繼承Thread類,首先實現的方式避免java中單繼承的缺點,其次如果多個線程操作同一份數據資源,更加適合用實現的方式

線程中Thread類常用的方法

1、start():啓動線程並執行相應的run方法
2、Thread.currentThread()   Thread.currentThread().getName()、Thread.currentThread().setName("設置的name")           獲取當前線程/獲取當前線程的名字/設置當前線程的名字
3、sleep(long time):顯式的讓線程休眠,其中time以毫秒爲單位
4、yield():讓調用這個方法的線程釋放當前cpu的執行權利
5、線程通信的方法:wait():另當前線程掛起並放棄cpu、同步資源使別的線程可以訪問並修改共享資源,而當前線程排隊等待再一前次對資源的訪問;
                                   notify():喚醒正在排隊等待同步資源的線程中優先級別最高者結束等待;
                                   notifyAll():喚醒所有正在排隊等待資源的線程結束等待。

6、join():在A線程中調用B線程的Join()方法,表示強制執行B線程,這個時候A線程停止執行,直到B執行完畢A纔可以繼續執行。

線程的安全問題

一、線程安全問題的原因:由於一個線程正在操作共享數據,未執行完畢的情況下,另外一個線程參與進來,導致共享數據在安全上出了問題。如銀行的轉賬存款業務,賣票業務等等。
二、解決線程安全的辦法 
1、同步塊synchronized:將需要同步的業務邏輯代碼放在synchronized(this){ ...}這個同步塊中
package com.thread;
public class Demo {
	public static void main(String[] args) {
		sell_Window sell=new sell_Window();
		Thread t1=new Thread(sell);//開啓三個窗口同時賣票
		Thread t2=new Thread(sell);
		Thread t3=new Thread(sell);
		t1.start();
		t2.start();
		t3.start();
	}
}
class sell_Window implements Runnable{//實現Runnable接口,把買票的業務邏輯放在run()方法中
    int num=100; 
    @Override
    public void run(){
      while(true){
	 synchronized(this) {//利用同步塊來實現線程安全,this表示當前類,我們也可以使用一個對象來做鎖對象,其中利用</span><span style="font-size:12px;">Demo.class字節碼也是比較好的 
	    if(num>0){
                   try{
			Thread.sleep(100);
     	                System.out.println("窗口 "+Thread.currentThread().getName()+":售第"+(num--)+"張票");
		      }catch (Exception e) {
			e.printStackTrace();
		      }  
	    }else{
		 break;
	    }
        }
     } 
  }
}

2、同步方法synchronized:將需要同步的方法加上關鍵字:synchronized如publicsynchronized void set(String name){...}
package com.thread;
public class Demo {
	public static void main(String[] args) {
		sell_Window sell=new sell_Window(new SellTicket());
		Thread t1=new Thread(sell);//開啓三個窗口同時賣票
		Thread t2=new Thread(sell);
		Thread t3=new Thread(sell);
		t1.start();
		t2.start();
		t3.start();
	}
}
class SellTicket{//共享數據區,以及買票的方法
	int num=100;
	public synchronized void sell(){//同步方法
		if(num>0){
			System.out.println("窗口 "+Thread.currentThread().getName()+":售第"+(num--)+"張票");
		}else{
			Thread.currentThread().stop();
		}
		
	}
}
class sell_Window implements Runnable{//實現Runnable接口,把買票的業務邏輯放在run()方法中
	SellTicket sellTicket;
	public sell_Window(SellTicket sellTicket) {
		this.sellTicket=sellTicket;
	}
	@Override
	public void run(){
		  while(true){
			  try{
				  Thread.sleep(100);
				  sellTicket.sell();
			  }catch (Exception e) {
				e.printStackTrace();
			}
			 
		  }
	}
}
3、鎖Lock,在線程同步中的Lock其實功能上和synchronized有着異曲同工的效果,不過Lock可以提供更加細度的控制,在java中使用Lock鎖的方式如下:
package com.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*線程同步中的鎖Lock */
public class ThreadLock {
	public static void main(String[] args) {
	     new ThreadLock().init();
	}
	public void init(){
	   final Outputer outputer=new Outputer();
	   new Thread(new Runnable() {//匿名線程
         	@Override
		public void run() {//run()方法
		     while(true){
   			try {
			       Thread.sleep(10);
                              outputer.output("zhangxiaoxiang");//執行的任務
                } catch (InterruptedException e) { 
                            e.printStackTrace(); 
                        }
               }
             }
          }).start();
            new Thread(new Runnable() {
                  @Override
                 public void run() {
                      while(true){
                           try {
                                  Thread.sleep(10);
                                        outputer.output("admin");
                                } catch (InterruptedException e) {
                                       e.printStackTrace();
                               }
                       }
                 }
           }).start();
        }
   static class Outputer{
          Lock lock=new ReentrantLock();//獲取鎖
          public void output(String name) {
              int len=name.length();
              lock.lock();//加鎖
              try{
                  for(int i=0;i<len;i++){
                    System.out.print(name.charAt(i));
                  }
                  System.out.println();
              }catch(Exception e){
                  e.printStackTrace();
              }finally{   
                  lock.unlock();//釋放鎖
              }
            }
       }
   }



在Lock中存在比較有用的兩種鎖,讀寫鎖(ReadWriteLock),使用這樣的鎖可以保證讀文件的時候可以併發,讀寫和寫文件不可以併發。下面用一個比較實用的例子來說明讀寫鎖的優點。
設計一個簡易的cache系統:
package com.thread;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*設置緩存系統:
 * 多個人進行讀取數據時,可以並行,所以這時候只需要讀鎖;當有人發現沒有數據時,這時候需釋放掉
 * 讀鎖加上一個寫鎖,在寫的時候不能被讀取,寫完之後就還原成讀鎖讓其它人可以讀取
 *  */
public class Thread_CacheDemo {
	private Map<String,Object> cache=new HashMap<String,Object>();
	public static void main(String[] args) {
	}
	//得到數據
	private ReadWriteLock rwl=new ReentrantReadWriteLock();
	public Object getData(String key){//多線程讀取時
		rwl.readLock().lock();//讀鎖
		Object value=null;
		try{
			value=cache.get(key);
			if(value == null){
				rwl.readLock().unlock();//釋放讀鎖
				rwl.writeLock().lock();//寫鎖
				try {
				    if(value == null){
				    	value="aaaa";//這裏實際上是查詢數據庫
				    }
				} catch (Exception e) {
					
				}finally{
					rwl.writeLock().unlock();//釋放寫鎖
					rwl.readLock().lock();//加上讀鎖
				}
			}
		}catch (Exception e) {
			e.printStackTrace();
		}finally{
			rwl.readLock().unlock();//釋放讀鎖
		}
	    return value;
	}
}

線程間通信

1、線程通信的概念:線程通信是指線程間相互交流的過程,一個線程可以釋放自己的cpu權利來給其它被喚醒的線程使用。

2、線程通信的方法,線程通信主要用到了wait(),notif(),notifyAll(),線程通信的一個經典案例就是生產者和消費者調度算法。下面是生產者和消費者調度算法的實現過程:

package com.thread;
/*生產者與消費者的問題
 *     生產者(Productor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品
 *   ,店員一次只能持有固定數量的產品(比如20),如果生產者試圖生 產更多的產品,店員會叫生產者停一下,
 *   如果店中有空位放產品了再通知生產者繼續生產;如果店中沒產品了,店員會告訴消費者等一下,如果店中有了產品再
 *   通知消費者。
 *    1、多線程:生產者,消費者
 *    2、共享數據(資源):產品的數量 
 *    3、需要線程安全
 *    4、需要線程通信:生產者與消費者通信
 * */
public class Produce_consume {
	public static void main(String[] args) {
	Clerk clerk=new Clerk();//共享數據
	Producer p=new Producer(clerk);//生產者
	Consumer c=new Consumer(clerk);//消費者
	Thread t1=new Thread(p);//生產者線程
	Thread t3=new Thread(p);//生產者線程
	Thread t2=new Thread(c);//消費者線程
	t1.setName("生產者1");
	t2.setName("消費者");
	t3.setName("生產者2");
	t1.start();
	t2.start();
	t3.start();
	}
}
//生產者
class Producer implements Runnable{
	Clerk clerk;
	public Producer(Clerk clerk) {
		this.clerk=clerk;
	}
	@Override
	public void run() {
		System.out.println("生產者開始生產產品");
		while(true){
		  try{
			Thread.currentThread().sleep(10);
                       clerk.addProduct();//生產者生產產品
                  } catch (InterruptedException e) {
                       e.printStackTrace(); 
                  }
               }
             }
           }

//消費者
class Consumer implements Runnable{
    Clerk clerk;
    public Consumer(Clerk clerk){
         this.clerk=clerk;
    }
    @Override
    public void run(){
        System.out.println("消費者消費產品"); 
        while(true){ 
            try {  
                   Thread.currentThread().sleep(10);
                   clerk.consumeProduct(); //消費者消費產品
            } catch(InterruptedException e) {
                   e.printStackTrace();
            } 
        }
   }
}
//店員,這是一個共享的數據,供生產者和消費者使用
class Clerk{
    int product;
    public synchronized void addProduct(){
             //生產產品
            if(product>=20){
              try{
               //產品數大於或等於20,暫停生產
               wait();//釋放cpu資源
               } catch (InterruptedException e){
                e.printStackTrace();
              }
            }else{
                product++;
                System.out.println(Thread.currentThread().getName()+":生產了第"+product+"個產品");
                notifyAll();//一旦有產品生產,去喚醒消費者進行消費,notifyAll()喚醒其它線程
            }
           }
   public synchronized void consumeProduct(){
    //消費產品
     if(product<=0){ 
           try {
               wait();//產品數少於或等於0,停止消費
            } catch (InterruptedException e) { 
               e.printStackTrace();
           }
    }else{
       System.out.println(Thread.currentThread().getName()+":消費了第"+product+"個產品");
       product--;
       notifyAll();//有消費,就去喚醒生產者進行生產
    }
   }
}


線程範圍的共享數據ThreadLocal類

1、ThreadLocal類表示可以存放當前線程的變量,同時這個變量可以讓這個線程中的所有對象共享。這個類的底層實現方式和HashMap()大致相同,它分別有get(),set(),remove(),setInitialValue()四個方法,這四個方法分別表示得到當前線程相關的變量、設置當前線程的相關變量、把與當前線程有關的變量全部remve、設置初始化的值。當我們遇到需要在一個線程中放置多個變量的情況時,可以把這些變量封裝成一個對象來存儲。
package com.thread;
import java.util.Random;
public class ThreadLocal_fun {
	   private static ThreadLocal<Integer> x=new ThreadLocal<Integer>();//表示放置一個變量
	   private static ThreadLocal<MyThreadScopeDate> xObj=new ThreadLocal<MyThreadScopeDate>();//放置一個變量對象
	   public static void main(String[] args) {
		  for(int i=0;i<2;i++){//開啓兩個線程
			 new Thread(new Runnable() {
			    @Override
			    public void run() {
		                int data=new Random().nextInt();
		                System.out.println(Thread.currentThread().getName()+" has put data:"+data);
		                x.set(data);
		                MyThreadScopeDate.getThreadInstance().setName("admin");//爲每個線程放置變量
		                MyThreadScopeDate.getThreadInstance().setAge(data);
		                new A().get();
			        new B().get();
			     }
			   }).start();
			}
	      }		
		static class A{
			public void get(){
				 int data=x.get();
				 System.out.println("A:"+Thread.currentThread().getName()+" has put data:"+data);
				 MyThreadScopeDate obj=MyThreadScopeDate.getThreadInstance();
				 System.out.println("A"+Thread.currentThread().getName()+":"+obj);
			}
		}
		static class B{
			public void get(){
				 int data=x.get();
				 System.out.println("B:"+Thread.currentThread().getName()+" has put data:"+data);
				 MyThreadScopeDate obj=MyThreadScopeDate.getThreadInstance();
				 System.out.println("A:"+Thread.currentThread().getName()+":"+obj);
			}
		}
	}
//封裝的變量對象 ,單例
class MyThreadScopeDate{
	private MyThreadScopeDate(){}//私有化的構造函數不允許實例化
	private static ThreadLocal<MyThreadScopeDate> map=new ThreadLocal<MyThreadScopeDate>();
	public static  MyThreadScopeDate getThreadInstance(){
		MyThreadScopeDate instance=map.get();
		if(instance == null){
			instance=new MyThreadScopeDate();
			map.set(instance);
		}
		return instance;
	}
	private String name;
	private  int age;
	
	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;
	}
	@Override
	public String toString() {
		return "MyThreadScopeDate [name=" + name + ", age=" + age + "]";
	}
}

線程池

多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閒置時間,增加處理器單元的吞吐能力;一般來說可以創建三種不同類型的線程池,同時線程池也爲我們提供了關閉的方法:
a.創建固定大小 的線程池:一次只能執行三個任務,其他任務等待。 Executors.newFixedThreadPool(3)
b.創建緩存線程池:線程數動態量化,空閒線程自動回收。  Executors.newCachedThreadPool()
c.創建單一線程池:池中只有一個線程,如果線程意外終止就新產生一個。Executors.newSingleThreadExecutor()
d.shutdown();//當所有的任務都運行完後,就關閉線程池
e.shutdownNow();//立刻關閉線程池
package com.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
	public static void main(String[] args) {
           //ExecutorService threadPool=Executors.newFixedThreadPool(3);//創建固定大小的線程池,這裏表明只能同時運行3個線程。
	   //ExecutorService threadPool=Executors.newCachedThreadPool();//創建緩存線程池,爲所有的任務都分配一個線程,自動關閉空閒的線程
	     ExecutorService threadPool=Executors.newSingleThreadExecutor();//創建單一線程池。
	     for(int i=1;i<10;i++){//循環往線程池中放10個任務
    	        final int task=i;
    	        threadPool.execute(new Runnable() {//在線程池中放任務
    			@Override
    			public void run() {
    				for(int j=5;j<10;j++){
    					 try {
							Thread.sleep(20);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
    					 System.out.println("線程"+Thread.currentThread().getName()+" loop of"+j+"次,在執行第 "+task+"個任務!");
    				}		
    			}
    		});  
       }
   				 System.out.println("提交了10個任務");
   				//threadPool.shutdown();//當所有的任務都運行完後,就關閉線程池
   			        //threadPool.shutdownNow();//立刻關閉線程池
	}
}

發佈了109 篇原創文章 · 獲贊 39 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章