(一)同步函數
同步函數:使用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));
}
}