Java基礎學習第八天

應用程序

-------------

 

 

進程

-------------

1.運行時(runtime)應用程序。

2.進程之間的內存不是共享(獨佔)

3.進程間通信使用的socket(套接字).

 

多線程

-------------

1.同一個進程內併發執行的代碼段。

2.線程之間共享內存。

3.創建靈活響應的桌面程序。(帶操作界面的程序)

4.每個運行着的線程對應一個stack(棧)。

5.應用程序至少有一個線程(主線程)

 

java.lang.Thread   ( Thread 線程類)

----------------

學習多線程時,要保持一個不要較真的心態,因爲在程序調用程序的線程時,什麼時候調用是不好說的

1.Thread.yield()方法

  讓當前線程讓出cpu搶佔權,具有謙讓之意,瞬時的動作,讓出後,立即重新開始搶佔。

------------------------------

示例

//使用yield()方法的示例

class ThreadDemo2{

public static void main(String [] args){

//創建2個線程對象,直接傳參

MyThread t1 = new MyThread("Thread-111");

MyThread t2 = new MyThread("Thread-222");

//線程對象中run方法是由CPU調用的,我們需要調的方法是start方法

//這裏的t1.start和t2.start()是通知CPU,你可以去調線程了。不要糾結先輸出哪個

t1.start();

t2.start();

}

}

//線程1

class MyThread extends Thread{

private String name ;

public MyThread (String name){

this.name = name ;

}

public void run(){

for (; ; ){

System.out.println(name);

//yield  放棄CPU搶佔權,但讓的只是很短暫的時間

Thread.yield();

}        

}

}

-------------------------------

2.Thread.sleep()

  讓當前線程休眠指定的毫秒數.

  釋放cpu搶佔權,和鎖旗標的監控權沒有關係。

 

3.Thread.join()

  當前線程等待指定的線程結束後才能繼續運行。(讓另外一個線程加入到我當前線程裏來,只要調用join代碼,後面代碼必須等待join執行完後,纔開始執行)

  Thread t = ...

  t.join();

  ....

---------------------------------------------------

 

23示例

/*使用sleep()方法和join()方法的示例,一個Boss找四個人打麻將,Boss作爲麻將館老闆其作爲主函數main,玩家爲Player類

*/

class ThreadDemo3{

public static void main(String [] args){

//建立四個玩家對象,直接傳參,代入玩家姓名和時間

Player p1 = new Player("甲", 1000);

Player p2 = new Player("乙", 2000);

Player p3 = new Player("丙", 2500);

Player p4 = new Player("丁", 3000);

//調用start()方法,開啓線程

p2.start();

p4.start();

p1.start();

p3.start();

//調入join()方法,等待所有玩家都完成。join()方法也會報異常,需使用try方法包上

try{

p4.join();

p2.join();

p1.join();        

p3.join();

}

catch(Exception e){

}

//因爲在所有人就緒之前,是不能開局的,所以輸出“開局”這段內容,是在所有人都就緒後纔可以。所以前面給四個對象都調用了join方法

System.out.println("人到齊,開局!");

}

}

//玩家類

class Player extends Thread{

private String name ;

//這個tmie變量表示從出發到麻將館的時候爲time時間

private int time ;

public Player (String name, int time){

this.name = name ;

this.time = time ;

}

//run()方法通過start()方法調用

public void run(){

System.out.println(name + "出發了!");

try{

//讓當前線程休眠多長時間,時間單位爲毫秒,但個方法是有異常的,需要拋出異常,這個異常不是運行時異常,只能用try 加 catch操作。用try將sleep方法包起來。

Thread.sleep(time);

}

//catch內有沒有不用管

catch ( Exception e ) {

}

 

System.out.println(name + "到了! 耗時" + time);

}

}

-----------------------------------------------------

4.daemon

守護線程,作用服務員。

爲其他線程提供服務的線程。

若進程中剩餘的線程都是守護線程的話,則進程就終止了。

Thread.setDaemon(true); //調用daemo線程

-------------------------------------------------------------

4 示例

/*

演示守護線進程daemon

*/

class ThreadDemo4{

public static void main(String [] args){

//創三個對象,兩個包房,一個服務員

BoxFang no1 = new BoxFang("01號",3000);

BoxFang no2 = new BoxFang("02號",7000);

Waiter w = new Waiter();

//設置服務員線程爲守護線程

w.setDaemon(true);

no1.start();

no2.start();

w.start();

}

}

//定義一個包房類,繼承Thread線程類

class BoxFang extends Thread{

//包房編號

private String no ;

//包房消費時間

private int time ;

public BoxFang (String no, int time){

this.no = no ;

this.time = time ;

}

public void run(){

System.out.println( no + "號包房開始消費。");

try{

//包房消費的時間

Thread.sleep(time);

System.out.println(no + "號包房的消費時間" + time);

}

//catch內有沒有不用管

catch ( Exception e ) {

}

System.out.println(no + "號包房的消費時間:" + time  + ",結束消費。");

}

}

 

//定義一個服務員類 Waiter  演示d

class Waiter extends Thread{

public void run(){

//死循環,保證進程一直存在,每隔一秒打印一次系統時間

while(true){

//打印當前系統時間

System.out.println(new java.util.Date());

try{

//打印當前時間後,休眠1秒(1000毫秒)

Thread.sleep(1000);

}

catch(Exception e ){

}

}

}

}

------------------------------------------------------------

5.--操作 (就是兩個連續的減號)

原子性操作。

 

6.線程間通信,共享資源的問題。

加鎖,防止併發訪問。由並行改爲串行。

(加鎖指的就是一個參照物的概念或叫鎖旗標)。

//同步代碼塊

synchronized(){

...

}

同步代碼塊執行期間,線程始終持有對象的監控權,其他線程處於阻塞狀態。

注:多線程是爲了併發執行提升效率,但由於有些工作是必須串行執行的,例如示例中的賣票是不可以賣重複的,所以,只將有特殊需求的代碼放入同步代碼塊中

-------------------------------------

5.6  示例:

/*

2個售票員共同賣100張票,演示線程間通信,共享資源的問題

每個售票員是一個線程,這個演示沒有賣重票的原因是因爲tickets-- 這個原子性操作。(這裏不論有幾個線程,所有對象都需要做tickets--的減減操作,因爲減減操作是原子性的,只有當前線程執行完後,另一個線程纔可以執行,存在這樣的情況。

但往往在實際開發中併發執行資源訪問情況是很複雜的,並不是一個減減操作就可以實現的,這樣就需要有一個過程,但一旦涉及到過程,就不是原子性的了,這樣就容易出現賣重票的可能。)

*/

class ThreadDemo5{

public static void main(String [] args){

//創建兩個售票員對象

Saler s1 = new Saler("1號");

Saler s2 = new Saler("2號");

//Saler s3 = new Saler("3號");

s1.start();

s2.start();

//s3.start();

}

}

 

//定義售票員類 繼承 Thread線程類

class Saler extends Thread {

//因爲要個售票員共同賣100張票,就存在一個共享的問題,靜態變量跟對象無關,跟類有關,就相當於一個全局變量,但在JAVA中不存在全局變量,在這裏就設置一個類上的靜態變量。

static int tickets = 100 ;

//鎖旗標,在同步代碼塊中使用,這個對象是作爲鎖出現的,所有售票員都要看這個對象來搶售票的權限,要保證所有對象看同一個成員變量的鎖定狀態,所以這個鎖必須是靜態變量

static Object lock = new Object();

//售票員的編號

private String num ;

//創建構造函數

public Saler( String num ){

this.num = num ;

}

//賣票操用

public void run(){

/*

當tickets票,小於等於0時,表示票售空

while( tickets > 0){

int temp = tickets ;

//使用減減操作情況下

//System.out.println(num + "售票員出售了" + (tickets--) + "號票。");

System.out.println(num + "售票員出售了" + temp + "號票。");

tickets = tickets - 1 ;

*/

while( true){

int t = getTickets();

//票號小於1 表示票售空,停止

if ( t == -1){

return ;

}else{

System.out.println(num + "售票員出售了" + t + "號票。");

 

}

}

}

//建立一個取票的方法,改造run()方法中的while循環中的取消方式

public int getTickets(){

/*同步代碼塊,需要對同一個對象加鎖,鎖是無所謂的,在前面加一個Object對象就叫lock

加同步代碼塊後,大括號中的內容,同一時刻只能有一個線程訪問。*/

synchronized(lock){

//保持當前的票號

int temp = tickets ;

try{

Thread.sleep(500);

}

catch(Exception e){

}

tickets = tickets - 1 ;  //票數減1

return temp < 1 ? -1 : temp ;  //返回票號

}

}

}

---------------------------------------

 

7.同步方法是以當前所在對象做鎖旗標。

synchronized(this) === 同步方法。

---------------------------------------------------

7 示例  體驗票池與程序代碼塊加鎖  在方法定義首行使用synchronized關鍵加鎖的使用

/*

這裏售票員出售的票都來自TicketPool票池中

*/

class ThreadDemo7{

public static void main(String [] args){

TicketPool tp = new TicketPool();

Saler s1 = new Saler("S1",tp);

Saler s2 = new Saler("S2",tp);

Saler s3 = new Saler("S3",tp);

Saler s4 = new Saler("S4",tp);

s1.start();

s2.start();

s3.start();

s4.start();

}

}

 

//定義售票員類 繼承 Thread線程類

class Saler extends Thread {

//售票員名字

private String name ;

//定義票池變量;

private TicketPool pool ;

//構造函數

public Saler(String name , TicketPool pool){

this.name = name ;

this.pool = pool ;

}

 

public void run(){

//死循環,保證一直賣票

while(true){

int no = pool.getTicket();

if ( no == 0 ){

return ;

}else{

System.out.println(name + "售票員出售了" + no + "號票。");

//謙讓,這段代碼可以放在同步代碼塊中,但不是必須串行的最好不放在同步代碼塊中,那樣會影響性能。

Thread.yield();

}

}

}

}

 

//票池類

class TicketPool{

//總票數

private int tickets = 9 ;

 

//取票方法,使用synchronized()保證串行取票

/*另一種同步方式,就是將synchronized關鍵字放在方法定義中,不採用代碼塊的形式,這樣synchronized將類所創建的對象作爲鎖旗標*/

public  synchronized int getTicket(){

//這裏的this作爲鎖,這個this指的是TicketPool票池,synchronized所包含的就是同步代碼塊,以票池類自身作爲鎖旗標。

 

//synchronized(this){

int temp = tickets ;

tickets = tickets - 1 ;

return temp > 0 ? temp : 0 ;

//}

}

}

--------------------------------------------------

 

8.同步靜態方法,使用類作爲同步標記。

public static synchronized xxxx(...){

...

}

java -Xmx10m ThreadDemo9

--------------------------------

8 示例同7  

由於採用靜態方法,那所使用的票的數量也要轉爲靜態的,因爲靜態方法只能訪問靜態變量,由於是靜態了,所以對象使用時也不需要聲明創建對象了,直接通過類名就可以訪問了。

/*

這裏售票員出售的票都來自TicketPool票池中

*/

class ThreadDemo8{

public static void main(String [] args){

Saler s1 = new Saler("S1");

Saler s2 = new Saler("S2");

Saler s3 = new Saler("S3");

s1.start();

s2.start();

s3.start();

}

}

//定義售票員類 繼承 Thread線程類

class Saler extends Thread {

//售票員名字

private String name ;

//構造函數

public Saler(String name ){

this.name = name ;

}

public void run(){

//死循環,保證一直賣票

while(true){

int no = TicketPool.getTicket();

if ( no == 0 ){

return ;

}else{

System.out.println(name + "售票員出售了" + no + "號票。");

Thread.yield();

}

}

}

}

//票池類

class TicketPool{

//總票數  方法爲靜態,成員變量也必須爲靜態

private static int tickets = 9 ;

//取票方法,使用synchronized()保證串行取票

/*

靜態方法,以類作鎖旗標

*/

public  synchronized static int getTicket(){

int temp = tickets ;

tickets = tickets - 1 ;

return temp > 0 ? temp : 0 ;

}

}

 

--------------------------------

9.wait

只有在對象擁有鎖旗標的監控權下纔可以調用這個方法。(這個方法只能在同步代碼塊裏面調用。)

讓當前線程進入鎖旗標的等待隊列。釋放cpu搶佔權,還釋放鎖旗標的監控權。等到有對象調用object.notify()方法時,通知等待對列中的其中一個線程,你們可以喚醒了,被喚醒的線程是隨機的,讓被喚醒的線程有搶佔CPU的和鎖旗標的監控權。

 

 

10.解決死鎖的問題。

  notify() 只能知一個線程可以搶佔cpu和鎖旗標監控權

   爲解決死鎖問題,改用 notifyAll()

    notifyAll()通知所有線程可以搶佔cpu和鎖旗標監控權。

   wait(1000);可以指定一個固定時間,時間一到立即喚醒自己不在等 了。

 

------------------------------------

9  10  示例:

/*

生產者與消費者,生產者產出放集合裏放,消費者從對象裏往外取

當生產過快,導致內存溢出,解決方式就是給集合設置一個最大值,不讓其無限制增加,當生產在給集合增加元素時,如果發現集合已達最大值,停止增加進行等待消費者將元素消費掉,消費者取元素時如果元素個數爲0,也進行等待生產者向集合增加元素。這個集合最好是作爲中間的一個對象,就像前面示例中的票池一樣,

*/

class ThreadDemo10{

public static void main(String [] args){

//選創建一個集合,暫是先不用管,記住它就是一個可以變化的容器。

Pool pool = new Pool();

//生產者和消費者用的是同一個集合

Productor p1 = new Productor("生產者1" , pool);

//Productor p2 = new Productor("生產者2" , pool);

Consumer c1 = new Consumer("消費者1",pool);

Consumer c2 = new Consumer("消費者2",pool);

p1.start();

    //p2.start();

c1.start();

c2.start();

 

}

}

 

//生產者類,繼承線程類  說明這個類是一個線程

class Productor extends Thread{

//生產者的名字

private String name;

static int i = 0 ;

//還需要一個存放對象的地方,創建一個集合

private Pool pool ;

//創建帶參構造方法,傳入名字和集合對象

public Productor(String name , Pool pool){

this.name = name ;

this.pool = pool ;

}

//作爲一個線程,它應該有run方法,生產者在不斷向裏面產生數字

public void run(){

//int i = 0 ;

while(true){

//每次實例化一個對象i+1,向集合中添加。

pool.add(i ++); 

try{

Thread.sleep(1000);

}catch(Exception e){

e.printStackTrace();

}

/*運行時會發現,生產的速度比消費的速度快,很快就把棧區佔滿了,就會造成溢出,下面要採用通知者模式(我生產一個就通知,快取走,再知道我,我再生產,這就是典形的生產消費關係),這時需要給集合設一個最大值*/

System.out.println(name + "填加 " + i + "  個");

}

}

}

 

//消費者

class Consumer extends Thread{

//消費者的名字

private String name;

//使用Pool類創建一個集合變量

private Pool pool ;

//創建帶參構造方法,傳入名字和集合對象

public Consumer(String name , Pool pool){

this.name = name ;

this.pool = pool ;

}

//消費者從集合中不斷的取出對象

public void run(){

while(true){

int i = pool.remove();

try{

//Thread.sleep(50);

}

catch(Exception e ){

e.printStackTrace();

}

System.out.println(name + "~~~取出了~~~~~~:" + i );

}

}

}

 

//增加一個對象池類,就像票池

class Pool{

private java.util.List<Integer> list = new java.util.ArrayList<Integer>();

//容器的最大值(這個對象池最多可以放100個)

private int MAX = 1 ;

//增加兩個方法,一個是增加元素的方法,

public void add( int n ){

//同步代碼塊,鎖旗標爲自己,說明同一時刻只能有一個線程調用這個方法。

synchronized (this){

//讓當前線程進入鎖旗標的等待隊列。釋放cpu搶佔權,還釋放鎖旗標的監控權。

try{

//判斷集合的元素數量是否大於最大值,進行等待

while (list.size() > MAX){

this.wait();

}

list.add( n ) ;

System.out.println("list.size大小" + list.size());

this.notify();

}catch(Exception e){

//出現異常打印棧跟蹤信息

e.printStackTrace();

}

}

}

//一個是刪除元素的方法

public int remove(){

synchronized (this){

try{

while(list.size() == 0 ){

this.wait();

}

int i = list.remove(0);

this.notify();

return i ;

}

catch(Exception e){

e.printStackTrace();

}

return -1 ;

}

}

}

-------------------------------------

 

多線程演示:

————————————————

示例1

class ThreadDemo1{

public static void main(String [] args){

//創建線程對象

MyThread t1 = new MyThread();

YourThread t2 = new YourThread();

//線程對象中run方法是由CPU調用的,我們需要調的方法是start方法

//這裏的t1.start和t2.start()是通知CPU,你可以去調線程了。不要糾結先輸出哪個

t1.start();

t2.start();

}

}

//線程1

class MyThread extends Thread{

public void run(){

for (; ; ){

System.out.println("第1個線程");

}        

}

}

//線程2

class YourThread extends Thread{

public void run(){

for (; ; ){

System.out.println("第2個線程");

}

}

}

------------------------------------------

示例2

 

--------------------------------------------

集合:簡要了解 之後再講

List列表,可變長的

 

————————————————

作業

--------------

1.一共100個饅頭,40個工人,每個工人最多能吃3個饅頭。使用多線程輸出所有工人吃饅頭的情況。

 

2.5輛汽車過隧道,隧道一次只能通過一輛汽車,每輛汽車通過時間不固定,

  機動車通過時間3秒,

  三輪車通過時間5秒,

  畜力車通過時間10秒,

  5輛車分別是2輛機動車,2輛畜力車,1輛三輪車。

  通過多線程模擬通過隧道的情況。

  提示:Car ThreeCar CowCar

 

3.用多線程模擬蜜蜂和熊的關係。

   蜜蜂是生產者,熊是消費者。蜜蜂生產蜂蜜是累加的過程,熊吃蜂蜜是批量(滿20吃掉)的過程。

   生產者和消費者之間使用通知方式告知對方。注意不能出現死鎖的現象。

100只蜜蜂,每次生產的蜂蜜是1.

熊吃蜂蜜是20(批量的情況)。


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