【Java】day23--同步函數、守護線程、線程通訊、IP地址類部分知識點總結

(一)同步函數

同步函數:使用synchronized修飾該函數稱爲同步函數
同步函數要注意的事項:
    1.非靜態同步函數的鎖對象是this對象,靜態同步函數的鎖對象是當前所屬類的class文件對象。
    (任何一個class文件被加載到內存時,jvm都會爲該class文件創建一個對應的對象描述。(在方法區,只有一個,可以作爲鎖對象))
    2.同步函數的鎖對象是固定的,無法更改。
    
推薦使用:同步代碼塊
原因:
    1.同步代碼塊的鎖對象可以自己制定,而同步函數的鎖對象是固定的。
    2.同步代碼塊可以隨意指定哪個範圍需要被同步,而同步函數必須是整個函數都同步,代碼不靈活。

class getCash extends Thread{
	static int money=5000;//共享資源
	static Object o=new Object();
	public getCash(String name) {
		super(name);//調用父類一個參數的構造函數,給線程初始化
	}
	//線程的任務代碼
	//非靜態的同步函數---鎖對象是this(),會出現一個線程一直佔據資源的情況
	@Override
	public synchronized void run() {
		while(true) {
			//synchronized (o) {
				if(money>0) {
					money-=100;
					System.out.println(Thread.currentThread().getName()+"取走100元,還有"+money+"元");
				}else {
					System.out.println("5000元全部取完...");
					break;
				}
			//}
		}
	}
	//靜態同步函數-----當前方法所屬類的class文件對象
	public synchronized static void run1() {
		
	}
}
public class demo5 {

	public static void main(String[] args) {
		//創建線程對象
		getCash wife=new getCash("妻子用信用卡");
		getCash husband=new getCash("丈夫用存摺");
		//開啓線程
		wife.start();
		husband.start();
	}
}

(二)死鎖現象

java同步機制解決了線程安全問題,但是同時也引發了死鎖現象。
死鎖現象只能儘量避免,無法解決。
死鎖現象出現的根本原因:    
    1.存在多線程。
    2.多個線程必須共享兩個或兩個以上的資源。(纔會存在等待的情況)

(三)線程的第二種創建方式

自定義線程的創建方式:
    方式一:
        1.自定義一個類繼承Thread.
        2.子類重寫run方法,把自定義線程的任務定義在run方法上。
        3.創建thread子類的對象,並且調用start方法開啓線程。
    方式二:
        1.自定義一個類實現Runnable接口。
        2.實現了Runnable接口的方法,把自定義線程的任務定義在run方法上。
        3.創建Runnable實現類的對象。
        4.創建Thread對象,並把Runnable實現類對象作爲參數傳遞進去
        5.調用Thread對象的start方法開啓線程。

Runnable的實現類對象是線程對象麼?
    Runnable的實現類對象並不是線程對象,只是實現了Runnable接口的對象。(只有Thread類和其子類的對象纔是線程對象)    
爲什麼把Runnable實現類的對象作爲參數傳遞給Thread對象呢?作用是什麼?
    作用:是把Runnable實現類的對象的run方法作爲任務代碼執行。
推薦使用:第二種線程創建方式。因爲java是單繼承的。

public class demo2 implements Runnable{

	@Override
		public void run() {
			for(int i=0;i<100;i++) {
				System.out.println(Thread.currentThread().getName()+":"+i);
			}
			System.out.println("當前線程對象:"+Thread.currentThread());//線程對象:t
			System.out.println("當前對象:"+this);//this對象:d,線程調用者對象
		}
	public static void main(String[] args) {
		//創建Runnable實現類的對象
		demo2 d=new demo2();
		//創建Thread對象,並把Runnable實現類對象作爲參數傳遞進去
		Thread  t=new Thread(d,"小貓");
		//調用Thread對象的start方法開啓線程。
		t.start();
		/*
			1.Thread類使用了target變量記錄了Runnable實現類對象。
			run方法的代碼是屬於線程的任務代碼。
		 * 
		 * */
		//主線程執行的
		for(int i=0;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

(四)守護線程

守護線程(後臺線程):當一個java應用程序只剩下守護線程的時候,那麼守護線程馬上結束。
守護線程的應用場景:
    1.新的軟件版本下載。
需求:模擬qq在下載更新包

守護線程要注意的事項:
    1.所有線程默認都不是守護線程.

ublic class demo4 extends Thread{
	
	public demo4(String name) {
		super(name);
	}

	@Override
		public void run() {//子類拋出的異常類型必須小於等於父類拋出的異常類型
			for(int i=0;i<100;i++) {
				System.out.println(this.getName()+"已經下載了:"+i+"%");
				//爲什麼可以捕獲,不能拋出(因爲父類沒有拋出異常,子類當然更不能拋)
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println("下載完畢,正在更新安裝包...");
		}
	public static void main(String[] args) {
		//創建線程對象
		demo4 d=new demo4("守護線程");
		d.setDaemon(true);//設置一個線程爲守護線程
		System.out.println("是守護線程麼?"+d.isDaemon());//isDaemon 可以判斷一個線程是否爲守護線程
		//啓動線程
		d.start();
		for(int i=0;i<100;i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
		//main線程結束之後,只剩下守護線程,也會立即結束,不再執行。
	}
}

(五)join方法

join方法:線程讓步。
需求:模擬小時候打醬油。

class Mother extends Thread{
	@Override
	public void run() {
		System.out.println("媽媽洗菜");
		System.out.println("媽媽切菜");
		System.out.println("媽媽發現沒有醬油了。。。");
		//通知兒子去打醬油
		Son s=new Son();
		s.start();
		try {
			s.join();//語句由Mother線程執行
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("媽媽炒菜");
		System.out.println("全家一起吃飯");
	}
}
class Son extends Thread{
	@Override
	public void run() {
		try {
			System.out.println("兒子下樓梯");
			Thread.sleep(1000);
			System.out.println("一直往前走");
			System.out.println("買到醬油");
			System.out.println("跑回來");
			Thread.sleep(1000);
			System.out.println("把醬油給老媽");
		}catch(InterruptedException e) {
			e.printStackTrace();
		}
	}
}
public class demo5 {

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

(六)線程通訊

線程通訊:當一個線程完成了一個任務的時候,要通知另外一個線程去處理其他事情。
線程通訊的方法:
    wait()        執行了wait方法的線程,會使該線程進入以鎖對象建立的線程池中等待。
    notify()     如果一個線程執行了notify方法,該線程會喚醒以鎖對象建立的線程池中等待線程中的一個。
    notifyAll() 喚醒所有的線程。(以鎖對象爲標識的線程池中的線程)
線程通訊要注意的事項:
    1.wait notify notify方法都是屬於Object對象的方法。(Object的方法纔可以分配給任意一個類)
    2.wait notify方法必須要在同步代碼塊或是同步函數中調用。(鎖對象)
    3.wait notify方法必須由鎖對象調用,否則報錯。(需要鎖對象標識線程池)
    4.一個線程執行了wait方法會釋放鎖對象。
一個線程如果執行了wait方法,那麼該線程會進入以鎖對象作爲標識的一個線程池中等待。
一個線程執行了notify方法,會喚醒以鎖對象建立的線程池中等待線程中的其中一個。

需求:生產者生成一個產品,消費者消費一個。

//產品類
class Product{
	String name;
	int price;
	boolean flag;//產品是否生成完畢,false爲沒有
}
//生產者類
class Producer extends Thread{
	public Producer(Product p) {
		this.p=p;
	}
	//維護一個產品
	Product p;
	//任務代碼
	@Override
	public void run() {
		int i=0;
		while(true) {
			synchronized (p) {//共享代碼塊
				if(p.flag==false) {
					if(i%2==0) {
						p.name="摩托車";
						p.price=4000;
					}else {
						p.name="自行車";
						p.price=300;
					}
					System.out.println("生產了"+p.name+" 價格:"+p.price);
					i++;
					//生產完畢--改標識
					p.flag=true;
					//喚醒消費者消費
					p.notify();
				}else {
					//如果產品已經生成完畢,應該等消費者先消費,再生產
					try {
						p.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}	
			}	
		}
	}
}
//消費者類
class Consumer extends Thread{
	//維護一個產品
	Product p;
	public Consumer(Product p) {
		this.p=p;
	}
	@Override
	public void run() {
		while(true) {
			synchronized (p) {
				if(p.flag==true) {
					System.out.println("消費者消費了"+p.name+",價格"+p.price+"元");
					//改標識
					p.flag=false;
					p.notify();
				}else {
					//等待生產商生成完畢
					try {
						p.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}	
		}
	}
}
public class demo6 {

	public static void main(String[] args) {
		//創建一個產品對象
		Product p=new Product();
		//創建線程對象(同一個p)
		Producer producer=new Producer(p);
		Consumer consumer=new Consumer(p);
		//啓動線程
		producer.start();
		consumer.start();
	}
}

(七)停止線程

停止線程:
    注意事項:
        1.停止線程一般通過變量控制。
        2.如果停止一個等待狀態下的線程,需要配合interrupt方法。

public class demo7 extends Thread{

	boolean flag=true;
	public demo7(String name) {
		super(name);
	}
	@Override
	public synchronized void run() {
		int i=0;
		while(flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				System.out.println("接收到一個InterruptedException");
				//e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+":"+i);
			i++;
		}
	}
	public static void main(String[] args) {
		//創建線程對象
		demo7 d=new demo7("小米");
		d.start();
		//當主線程的i到80的時候,停止小米線程
		for(int i=0;i<100;i++) {
			if(i==80) {
				//d.stop();//可以實現,但過時了
				d.flag=false;
				d.interrupt();//強制清除一個線程的wait sleep狀態,可以指定清除哪個線程,但notify不能指定
				/*
				synchronized (d) {
					d.notify();
				}
				*/
			}
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}

(八)IP地址類

java是面向對象的語言,所以java使用了一個類描述了IP地址。

InetAddress IP地址類
    需要掌握的方法:
        static getLocalHost()        返回本機ip地址對象    
        getByName(String host)        指定字符串形式的ip地址或是主機名創建一個ip地址對象
        String getHostName()        返回主機名
        getHostAddress                返回本機ip地址字符串的表示形式
        getAllByName(String host)

public class demo1 {

	public static void main(String[] args) throws UnknownHostException {
		InetAddress address=InetAddress.getLocalHost();//獲取到本機的ip地址對象
		//InetAddress address=InetAddress.getByName("jjjj");//主機名不保險,會報錯
		System.out.println("本機的ip地址:"+address.getHostAddress());
		System.out.println("主機名:"+address.getHostName());
		InetAddress[] address1=InetAddress.getAllByName("https://www.baidu.com");
		
		//System.out.println(Arrays.toString(address1));
	}
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章