----------------------
ASP.Net+Android+IOS開發、.Net培訓、期待與您交流! ----------------------
一.線程概述
* 學習筆記:
* 1、當多個程序例如QQ、暴風、音樂同時運行時,其實CPU是一個一個的執行的,只不過進行的快速切換
* 2、例如,搬東西,五個人一起把A區的一批貨物搬到B區,這就是一個進程,而每個人搬運就是線程
* 3、java中進程就是正在執行的程序,而線程就是進程中的一個獨立單元,控制着進程的執行
* 4、JVM執行的時候會有一個進程java.exe
* 5、創建線程有兩種方法
二.創建線程第一種方法(繼承Thread類)
* 1、定義類並繼承Thread類
* 2、複寫run方法
* 目的:爲了將自定義代碼存儲在run方法中,讓線程運行
* 3、創建此類對象,也就是創建了一個線程
* 4、調用start方法,也就是啓動了這個線程
* start方法的兩個作用:啓動線程和調用run方法
* 5、發現每次運行結果都不一樣,因爲多個線程都在獲取CPU的執行權,CPU執行誰,誰就運行
* 注意:在某一個時刻,只有一個線程在運行(多核除外),只不過CPU在做着快速的切換
* 6、多線程運行可以形象的理解爲程序在搶奪CPU的執行權
* 這是多線程的一個特性:隨機性
代碼示例:
public class ThreadDemo {
public static void main(String[] args) {
Test t = new Test(); //創建了一個線程
t.start(); //啓動了這個線程,調用run方法
t.run(); //不啓動線程,僅僅調用run方法
for(int i =0;i<100;i++){
System.out.println("主函數在執行"+i);
}
}
}
class Test extends Thread{
public void run(){
for(int i = 0;i<100;i++){
System.out.println("複寫了run方法"+i);
}
}
}
三.創建線程第二種方法(實現Runnable接口)
* 線程第二種方式:實現Runable接口
* 示例:多窗口賣票
* 步驟;
* 1、定義類實現Runnable接口
* 2、覆蓋Runnable接口中的run方法
* 作用:將線程要運行的代碼存儲在run方法中
* 3、通過Thread類建立線程對象
* 4、把Runnable接口的子類對象作爲參數傳遞給Thread類的構造函數
* 作用:調用run方法的對象必須是Runnable接口的子類對象,所以要把此對象傳遞給Thread類的構造函數
* 5、調用Thread類的start方法,並調用Runnable接口的子類的run方法
代碼示例:
public class RunnableDemo {
public static void main(String[] args) {
Ticket t = new Ticket(); //這並沒有創建線程,因爲該類沒有繼承Thread類
Thread t1 = new Thread(t,"一"); //這纔是創建了一個線程
Thread t2 = new Thread(t,"二");
Thread t3 = new Thread(t,"三");
Thread t4 = new Thread(t,"四");
Thread t5 = new Thread(t,"五");
t1.start();//啓動線程並調用run方法
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class Ticket implements Runnable{
private int i=100; //共有100張票,因爲只創建了一個對象,所以i只有一個,是共享的
public void run(){
for(int a=1;a<=1000;a++){
if(i>0){
System.out.println(Thread.currentThread().getName()+"號窗口賣"+i--+"號票");
}
}
}
}
四.繼承和實現的區別
* 繼承和實現的區別:
* 繼承只能夠繼承一個類,有侷限性
* 而如果一個學生類已經繼承了人類,那麼就不能給繼承Thread類了,導致不能夠創建線程
* 而實現恰好打破了這種侷限性,當一個類已經有了自己的父類,這時仍然可以實現Runnable創建線程
*
* 繼承中,線程代碼存放在Thread子類的run方法中
* 實現中,線程代碼存放在Runnable子類的run方法中
*
* 實現的好處:避免了單繼承的侷限性
五.多線程練習
* 創建兩個線程,並和主線程同時運行
* 1、自定義線程名稱
* 線程默認名稱爲:Thread-0,編號從零開始
* 通過調用父類構造函數對name進行初始化,
* 並定義this.getName()或者(Thread.currentThread().getName)獲取名稱
* 2、 static Thread currentThread() 獲取當前線程對象
* getName() 獲取線程名稱
* setName() 設置線程名稱
代碼示例:
public class Thread_Lianxi {
public static void main(String[] args) {
Demo d1 = new Demo("one"); //創建一個線程
Demo d2 = new Demo("two"); //創建一個新線程
d1.start();
d2.start();
for(int i=0;i<30;i++){
System.out.println("bbbbbbbbbbbb");
}
}
}
class Demo extends Thread{
Demo(String name){
super(name);
}
public void run(){
for(int i=0;i<30;i++){ //局部變量i在每個線程區域中都有一個獨立的塊
System.out.println("aaaaaaaaaaaaaaa"+Thread.currentThread().getName());
}
}
}
六.多線程安全問題
* 多線程安全問題:
* 問題原因:當多個線程在執行同一個線程共享數據時,第一個線程剛剛執行了一部分,
* 另一個線程就參與了進來執行,導致共享數據的錯誤
* 解決辦法:在多個線程操作共享數據時,控制在一個線程執行過程中,其他線程不能幹參與執行
*
* java對多線程安全問題提供了專業的解決辦法,就是同步代碼塊
* 格式:
* synchronize(對象){
* 需要被同步的代碼塊 //只要操作了共享數據的代碼就是需要被同步的代碼
* }
*
* 相當於火車上的廁所,例如一個人去上廁所,走到門前首先判斷下,沒人,然後進去,關上門,上鎖,在他還沒出來之前
* 外面的人看到門上鎖顯示有人,那麼就進不去,只能等裏面的人出來以後才能進去
*
* 同步的前提:
* 1、必須有兩個或兩個以上的線程。例如如果火車上只有一個人,那麼就沒必要鎖門了
* 2、必須是多個線程使用同一個鎖。例如如果1號車廂的廁所是打開來,而2號車廂的廁所是鎖着來,2號車廂的人還是進不去廁所
*
* 同步的好處:解決了多線程的安全問題
* 同步的弊端:線程執行時需要判斷鎖,較爲耗費資源。 例如回家直接推開門進去和需要開鎖,較爲麻煩
*
* 補充知識點:實例其實就是對象,但是它是有所屬的。比如說,我們可以說他是“人”,
* 但是我們不能單獨說他是“兒子”,我們必須說他是某某的“兒子”。
* 所以,我們定義了類 CA,並通過類 CA 創建了對象 objA。
* 我們就可以說 objA 是類 CA 的實例。
代碼示例:
public class SynchronizedDemo {
public static void main(String[] args) {
Ticket1 t = new Ticket1(); //這並沒有創建線程,因爲該類沒有繼承Thread類
Thread t1 = new Thread(t,"一"); //這纔是創建了一個線程
Thread t2 = new Thread(t,"二");
t1.start();//啓動線程並調用run方法
t2.start();
}
}
class Ticket1 implements Runnable{
private int i=100; //共有100張票,因爲只創建了一個對象,所以i只有一個,是共享的
Object obj = new Object();
public void run(){
for(int a=1;a<=1000;a++){
synchronized(obj){
if(i>0){
try {
Thread.sleep(10); //這個方法本身拋出了異常
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"號窗口賣"+i--+"號票");
}
}
}
}
}
七.多線程同步函數
* 目的:該程序是否有問題,如果由,該怎麼找出來
*
* 找問題方法:
* 1、明確哪些代碼是多線程運行代碼 ( run方法中的代碼和所調用的方法)
* 2、明確共享數據 (Bank類的對象、num屬性)
* 3、明確多線程運行中哪些代碼可以操作共享數據
*
* 然後分析問題:
* 1、如果一個線程在運行到num=num+a後執行權就被其他線程搶走了,這時候就執行不到打印語句了
*
* 解決問題:
* 1、這時候就需要把多線程執行到得共享數據Synchronized了
*
* 同步函數:public Synchronized void add(){
* 同步函數的鎖是this
* 同步代碼塊的鎖是對象
* 靜態同步函數的鎖是 類名.class 字節碼文件
代碼示例:
public class Synchronized_Cunqian {
public static void main(String[] args){
Client c = new Client();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
//銀行
class Bank{
private int num=0;
Object obj = new Object();
public void add(int a){ //同步函數 public Synchronized void add(int a){
synchronized(obj){
num=num+a;
try{Thread.sleep(10);}catch(Exception e){}
System.out.println("現在銀行有"+num+"元錢");
}
}
}
//客戶存錢
class Client implements Runnable{
Bank b = new Bank();
public void run(){
for(int i=0;i<3;i++){
b.add(100);
}
}
}
八.線程間通信
* 多線程通信:其實就是多個線程在操作同一個資源,只不過操作的動作不同
* 示例:一個線程向共享區存入人的姓名和年齡,而另一個線程向共享區提取數據
*
* 記住:多線程出現了問題;檢查兩個條件是否滿足
* 1、是否同步了兩個或兩個以上的線程
* 2、必須是多個線程使用同一個鎖
代碼示例:
public class Thread_tongxin {
public static void main(String[] args) {
Person p = new Person();
Input i = new Input(p);
Output o = new Output(p);
Thread t = new Thread(i);
Thread t1 = new Thread(o);
t.start();
t1.start();
}
}
// 人的屬性
class Person {
String name;
String sex;
}
// 存入
class Input implements Runnable {
private Person p;
Input(Person p) {
this.p = p;
}
public void run() {
int i=0;
while (true) {
synchronized (p) {
if (i==0) {
p.name = "小花";
p.sex = "女";
}
else {
p.name = "李四";
p.sex = "男";
}
i = (i+1)%2;
}
}
}
}
// 提取
class Output implements Runnable {
private Person p;
Output(Person p) {
this.p = p;
}
public void run() {
while (true) {
synchronized (p) {
System.out.println(p.name + "........" + p.sex);
}
}
}
}
九.等待喚醒機制
* 多線程中等待喚醒機制
*
* 問題:目前的代碼執行後存入數據代碼可能會被執行很多次以後,提取數據的代碼才能被執行
*
* 目的:想要每存入一組數據,就提取改組數據,然後再存入
* 方法:
* 1、首先定義一個控制共享區的變量,如果爲true,就代表裏面有數據;如果爲false,就代表裏面沒數據
* 當一個線程開始執行存入代碼時,判斷下共享區裏面是否有數據(是否爲false),如果沒有就存入,有的話就wait
* 存入以後改變這個變量改爲true,並且喚醒提取代碼的線程notify,
* 2、現在提取代碼的線程已經被喚醒,首先if判斷共享區裏面是否有數據(是否爲true),如果由就提取,沒有就wait
* 提取出以後改變這個變量爲false,並且喚醒存入代碼的線程notify,
*
*
* 注意:
* 1、不管是存入還是提取,鎖都要一樣,唯一性
* 2、因爲wait()方法、notify()方法都是Object中的方法,所以需要對象進行調用,因爲是Object中的方法,所以任何對象都可以
* 3、wait()方法在Object類中拋出了異常,所以需要在內部進行捕獲處理
代碼示例:
public class Deng_dai_huan_xing_ji_zhi {
public static void main(String[] args) {
Person p = new Person();
// Input i = new Input(p);
// Output o = new Output(p);
// Thread t = new Thread(i);
// Thread t1 = new Thread(o);
new Thread(new Input(p)).start();
new Thread(new Output(p)).start();
}
}
// 人的屬性
class Person {
private String name;
private String sex;
private boolean flag = false;
public void setShow(String name, String sex) {
synchronized (this) {
if (this.flag) {
try {
this.wait();
} catch (Exception e) {
}
}
this.name = name;
this.sex = sex;
this.flag = true;
this.notify();
}
}
public void print() {
synchronized (this) {
if (!this.flag) {
try {
this.wait();
} catch (Exception e) {
}
}
System.out.println(this.name + "........" + this.sex);
this.flag = false;
this.notify();
}
}
}
// 存入
class Input implements Runnable {
private Person p;
Input(Person p) {
this.p = p;
}
public void run() {
int i = 0;
while (true) {
if (i == 0) {
p.setShow("小花", "女");
} else {
p.setShow("小黑", "男");
}
i = (i + 1) % 2;
}
}
}
// 提取
class Output implements Runnable {
private Person p;
Output(Person p) {
this.p = p;
}
public void run() {
while (true) {
p.print();
}
}
}
十.等待喚醒機制實例(生產者與消費者)
* 多線程喚醒機制例子:生產者與消費者
*
* 1、爲什麼要使用while
* :因爲如果當生產者多線程被喚醒以後,共享區裏明明有數據,這時候沒有經過判斷就又創建了一個商品
* 共享區內就會發生錯誤
* 2、爲什麼使用notifyAll
* :因爲當生產者線程wait以後,可能會喚醒其他生產者線程,因爲需要先經過判斷,這樣也生產不了,
* 導致所有線程全部等待
代碼實例:
public class Produce_Consume {
public static void main(String[] args) {
Resource r = new Resource();
Produce p = new Produce(r); //爲了保證r對象是同一個
Consume c = new Consume(r); //爲了保證r對象是同一個
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(c);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//共享資源
class Resource{
private String name; //商品名稱
private int count=1; //計數
private boolean flag= false;
//生產
public synchronized void intPut(String name){
while(flag){
try{
wait();
}catch(Exception e){
}
}
this.name = name;
count++;
System.out.println(Thread.currentThread().getName()+this.name+this.count);
flag=true;
this.notifyAll();
}
//消費
public synchronized void OutPut(){
while(!flag){
try{
wait();
}catch(Exception e){
}
}
System.out.println(Thread.currentThread().getName()+this.name+this.count+"...............");
flag=false;
this.notifyAll();
}
}
//生產者
class Produce implements Runnable{
private Resource r;
Produce(Resource r){
this.r = r;
}
public void run(){
while(true){
//
r.intPut("麪包");
}
}
}
//消費者
class Consume implements Runnable{
private Resource r;
Consume(Resource r){
this.r = r;
}
public void run(){
while(true){
r.OutPut();
}
}
}
十一.lock鎖(消費者與生產者代碼更嚴謹)
* 思路:
* 1、因爲在JDK1.5以後,lock就替換了synchronized,而condition就替換了wait()、notify();
* 2、現在通過lock.lock()的方式就可以手動上鎖,通過lock.unlock()的方式可以手動開鎖
* 3、通過con.await()的方式讓線程等待,注意,這時候會拋出一個異常,而這個異常不能夠在這裏處理,所以要聲明
* 4、通過con.signalAll()的方式喚醒全部線程
*
*
* 可是這樣又出現了問題:
* 因爲con.signalAll()是喚醒全部線程,那麼本方線程也可能去讀取while,這樣就佔用了資源
*
* 解決辦法:
* 1、在這裏可以通過創建多個Condition實例,
* 2、此例創建兩個,一個控制喚醒消費者線程,另一個控制喚醒生產者線程
代碼示例:
public class Produce_Consume {
public static void main(String[] args) {
Resource_ r = new Resource_();
Produce_ p = new Produce_(r);
Consume_ c = new Consume_(r);
/*
* Thread有一個構造函數 Thread(Runnable r),作用是分配新的Thread對象
* 因爲只有Thread對象纔可以啓動線程並調用run方法
*/
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(c);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
// 資源
class Resource_ {
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock(); // (多態)通過Lock接口的實現類ReentrantLock創建了lock對象
private Condition con_pro = lock.newCondition(); // lock對象通過調用newCondition方法創建了綁定此Lock實例的Condition實例con
private Condition con_con = lock.newCondition();
// 生產
public void produce_(String name) throws Exception {
lock.lock();
try {
while (flag) {
con_pro.await(); // 這裏不能處理,因爲只有當爲true時纔會等待,那麼此時線程處於等待狀態,怎麼可能繼續運行處理代碼呢?
}
this.name = name + "...." + count++;
System.out.println(Thread.currentThread().getName()
+ this.name );
flag = true;
con_con.signal();
} finally {
lock.unlock(); // 因爲不管線程是否等待,都必須打開鎖,所以把開鎖功能放在finally中
}
}
// 消費
public void consume_() throws Exception {
lock.lock();
try {
while (!flag) {
con_con.await();
}
System.out.println(Thread.currentThread().getName()
+ this.name + "...............");
flag = false;
con_pro.signal();
} finally {
lock.unlock();
}
}
}
// 生產者
class Produce_ implements Runnable {
private Resource_ r;
Produce_(Resource_ r) {
this.r = r;
}
public void run() {
while (true) {
try {
r.produce_("+商品+");
} catch (Exception e) {
}
}
}
}
// 消費者
class Consume_ implements Runnable {
private Resource_ r;
Consume_(Resource_ r) {
this.r = r;
}
public void run() {
while (true) {
try {
r.consume_();
} catch (Exception e) {
}
}
}
}
十二.停止線程
如何停止線程?
只有一種,run方法結束。
開啓多線程運行,運行代碼通常是循環結構。
只要控制住循環,就可以讓run方法結束,也就是線程結束。
特殊情況:
當線程處於了凍結狀態。
就不會讀取到標記。那麼線程就不會結束。
當沒有指定的方式讓凍結的線程恢復到運行狀態是,這時需要對凍結進行清除。
強制讓線程恢復到運行狀態中來。這樣就可以操作標記讓線程結束。
Thread類提供該方法 interrupt();
代碼示例:
class StopThread implements Runnable
{
private boolean flag =true;
public synchronized void run()
{
while(flag)
{
try{
wait();
}catch(Exception e){
System.out.println(Thread.currentThread().getName()+"....Exception");
flag = false;
}
System.out.println(Thread.currentThread().getName()+"....run");
}
}
}
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
//st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"...."+num);
}
System.out.println("over");
}
}
十三.守護線程
* 守護線程(又叫用戶線程、後臺線程)
* 注意:
* 1、該方法必須在啓動線程之前調用
* 2、如果只有守護線程在運行,那麼JVM退出
*
* 這時候程序中線程t1和t2因爲被定義爲守護線程,當它們都wait時,主線程執行完畢,JVM退出,程序執行完畢
代碼示例:
public class SetDaemon {
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
//定義守護線程
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
//st.changeFlag();
//t1.interrupt();
//t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"...."+num);
}
System.out.println("over");
}
}
class StopThread implements Runnable
{
private boolean flag =true;
public synchronized void run()
{
while(flag)
{
try{
wait();
}catch(Exception e){
System.out.println(Thread.currentThread().getName()+"....Exception");
flag = false;
}
System.out.println(Thread.currentThread().getName()+"....run");
}
}
}
十四.join方法
join:
當A線程執行到了B線程的join()方法時,A就會等待。等B線程都執行完,A纔會執行。
join可以用來臨時加入線程執行。
代碼示例:
class Demo1 implements Runnable
{
public void run()
{
for(int x=0; x<70; x++)
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
}
}
class JoinDemo
{
public static void main(String[] args) throws Exception
{
Demo1 d = new Demo1();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
//t1.join(); //這句話的意思是:當主線程執行到這裏時,會釋放執行權,因爲只有當t1執行完以後主線程才能重新獲得執行權
t2.start();
t1.join(); /*這句話的意思是:當主線程執行到這裏時,會釋放執行權,
*而因爲之前已經有t1和t2兩個線程都被啓動,所以這時候t1和t2兩個線程爭奪執行權,
*當t1執行完以後主線程纔可以擁有執行權,這時候就是主線程和t2爭奪執行權了
*/
for(int x=0; x<80; x++)
{
System.out.println("main....."+x);
}
System.out.println("over");
}
}
十五.優先級&yield方法
* toString()方法:返回該線程的字符串表示形式,包括 【線程名稱】【優先級】【線程組】
*
* 對於優先級,更改線程的優先級格式:
* setPriority(MAX_PRIORITY 或 MIN_PRIORITY 或 NORM_PRIORITY);
代碼示例:
public class ToString_Demo {
public static void main(String[] args) throws Exception
{
Demo2 d = new Demo2();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t1.setPriority(Thread.MAX_PRIORITY);
//MAX_PRIORITY是10 ; MIN_PRIORITY是1 ; ONRM_PRIORITY是5
t2.start();
for(int x=0; x<80; x++)
{
System.out.println("main....."+x);
}
System.out.println("over");
}
}
class Demo2 implements Runnable
{
public void run()
{
for(int x=0; x<70; x++)
{
System.out.println(Thread.currentThread().toString()+"....."+x);
}
}
}
* static void yield()
* 作用:暫停執行當前線程,改爲執行其他線程
代碼示例:
public class YieldDemo {
public static void main(String[] args) throws Exception //異常拋給了JVM
{
Demo2 d = new Demo2();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
class Demo3 implements Runnable
{
public void run()
{
for(int x=0; x<70; x++)
{
System.out.println(Thread.currentThread().toString()+"....."+x);
Thread.yield();
}
}
}
----------------------
ASP.Net+Android+IOS開發、.Net培訓、期待與您交流! ----------------------詳細請查看:http://edu.csdn.net