應用程序
-------------
進程
-------------
1.運行時(runtime)應用程序。
2.進程之間的內存不是共享(獨佔)
3.進程間通信使用的socket(套接字).
多線程
-------------
1.同一個進程內併發執行的代碼段。
2.線程之間共享內存。
3.創建靈活響應的桌面程序。(帶操作界面的程序)
4.每個運行着的線程對應一個stack(棧)。
5.應用程序至少有一個線程(主線程)
java.lang.Thread ( Thread 線程類)
----------------
學習多線程時,要保持一個不要較真的心態,因爲在程序調用程序的線程時,什麼時候調用是不好說的
1.Thread.yield()方法
讓當前線程讓出cpu搶佔權,具有謙讓之意,瞬時的動作,讓出後,立即重新開始搶佔。
------------------------------
1 示例
//使用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();
....
---------------------------------------------------
2,3示例
/*使用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(批量的情況)。