1. 程式人生 > >Java學習筆記(13)

Java學習筆記(13)

interrupt 至少 hour 數量 異常 ext 字符數 nts 試圖

StringBuffer

增加

    append(boolean b) 可以添加任意類型的數據到容器中

    insert(int offset,boolean b) 指定插入的索引值,插入對應的內容 (offset可以理解為想插入的東西在插入後的索引值為多少,也就是想插入地方右區間的索引值)

刪除

    delete(int start ,int end) 根據指定的開始與結束的索引值刪除對應的內容

    deleteCharAt(int index) 根據指定的索引值刪除一個字符

修改

    replace(int start,int end,String str) 根據指定的開始與結束索引值替代成指定的內容

    reverse() 翻轉字符串緩沖類的內容。 abc————>cba

    setCharAt(int index,char ch) 把指定索引值的字符替換指定的字符。

    subString(int start,int end) 根據指定的索引值截取子串

    ensureCapacity(int minimumCapacity) 指定StringBuffer內部的字符數組長度的。

查看

    indexOf(String str,int fromIndex) 查找指定字符串第一次出現的索引值,並且指定開始查找的位置

    capacity() 查看當前字符數組的長度

    charAt(int index) 根據指定的索引值查找字符

    lastIndexOf(String str) 查找指定字符最後一次出現的索引值

    length() 存儲的字符個數

    toString() 把字符串緩沖類的內容轉換成字符串返回

判斷

public static void main(String[] args) {
        // TODO Auto-generated method stub
        //先使用StringBuffer無參的構造函數創建一個字符串緩沖類。
        StringBuffer sb=new StringBuffer();
        sb.append(
"abc"); System.out.println("字符串緩沖類的內容:"+sb); /* 添加 sb.append(true); sb.append(3.14f); System.out.println("字符串緩沖類的內容:"+sb); */ //插入 sb.insert(2, "小明"); System.out.println("字符串緩沖類的內容:"+sb); //刪除 sb.delete(2, 4); //刪除的時候也是包頭不包尾的 System.out.println("字符串緩沖類的內容:"+sb); sb.insert(2, "小明"); sb.deleteCharAt(3); System.out.println("字符串緩沖類的內容:"+sb); } 結果: 字符串緩沖類的內容:abc 字符串緩沖類的內容:ab小明c 字符串緩沖類的內容:abc 字符串緩沖類的內容:ab小c
public static void main(String[] args) {
        // TODO Auto-generated method stub
        //先使用StringBuffer無參的構造函數創建一個字符串緩沖類。
        StringBuffer sb=new StringBuffer();
        sb.append("abc");
        System.out.println("字符串緩沖類的內容:"+sb);
        /* 添加
        sb.append(true);
        sb.append(3.14f);
        System.out.println("字符串緩沖類的內容:"+sb);
        */
        //插入
        sb.insert(2, "小明");
        System.out.println("字符串緩沖類的內容:"+sb);
        //刪除
        /*sb.delete(2, 4);   //刪除的時候也是包頭不包尾的
        System.out.println("字符串緩沖類的內容:"+sb);
        sb.insert(2, "小明");
        sb.deleteCharAt(3);
        System.out.println("字符串緩沖類的內容:"+sb);
        */
        //修改
        sb.replace(2, 4, "陳小狗");
        System.out.println("字符串緩沖類的內容:"+sb);
        sb.reverse();
        System.out.println("字符串緩沖類的內容:"+sb);
        sb.reverse();
        sb.setCharAt(3, ‘紅‘);
        System.out.println("字符串緩沖類的內容:"+sb);
        System.out.println("字符串的內容:"+sb.substring(2, 4));
        sb.ensureCapacity(20);
    }

結果:
字符串緩沖類的內容:abc
字符串緩沖類的內容:ab小明c
字符串緩沖類的內容:ab陳小狗c
字符串緩沖類的內容:c狗小陳ba
字符串緩沖類的內容:ab陳紅狗c
字符串的內容:陳紅
public static void main(String[] args) {
        // TODO Auto-generated method stub
        StringBuffer sb=new StringBuffer();
        sb.append("abcjavaabc");
        //查找
        System.out.println("索引值為:"+sb.indexOf("abc", 0));
        System.out.println("索引值為:"+sb.indexOf("abc", 3));
        //sb.ensureCapacity(20);
        sb.append("javajava");
        System.out.println("查看字符數組的長度:"+sb.capacity());
        System.out.println("存儲字符的個數:"+sb.length());
        System.out.println("根據指定的索引值查找字符:"+sb.charAt(2));
        System.out.println("轉換成字符串輸出:"+sb.toString());
    }

結果:
索引值為:0
索引值為:7
查看字符數組的長度:34
存儲字符的個數:18
根據指定的索引值查找字符:c
轉換成字符串輸出:abcjavaabcjavajava

StringBuffer與StringBuilder的相同處與不同處:

    相同點:

      1.兩個類都是字符串緩沖類

      2.兩個類的方法與實現都是一致的

    不同點:

      1.StringBuffer是線程安全的,操作效率低,StringBuilder是線程非安全的,操作效率高

      2.StringBuffer是jdk1.0出現的,StringBuilder是jdk1.5的時候出現的

推薦使用:StringBuilder,因為操作效率高。

System類 系統類    主要用於獲取系統的屬性數據

System類常用的方法:

    arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

import java.util.*;
public class Demo1 {
/*
        arraycopy(Object src, int srcPos, Object dest, int destPos, int length)  
         src - 源數組。
        srcPos - 源數組中的起始位置。
        dest - 目標數組。
        destPos - 目標數據中的起始位置。
        length - 要復制的數組元素的數量。
 * */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int[] srcArr= {10,12,14,16,19};
        //把srcArr數組的元素拷貝到destArr數組中
        int[] destArr=new int[4];
        System.arraycopy(srcArr, 1, destArr, 0, 4);
        System.out.println("目標數組的元素:"+Arrays.toString(destArr));
    }

}

結果:
目標數組的元素:[12, 14, 16, 19]

    currentTimeMillis()    獲取當前系統時間 重點

System.out.println("當前的系統時間是:"+System.currentTimeMillis());

結果:
當前的系統時間是:1549623586306

    exit(int status)      退出jvm    如果參數是0表示正常退出jvm,非0表示異常退出jvm  0或者非0的數據都可以退出jvm,對於用戶而言沒有任何區別    一般

public static void main(String[] args) {
        // TODO Auto-generated method stub
        int[] srcArr= {10,12,14,16,19};
        //把srcArr數組的元素拷貝到destArr數組中
        int[] destArr=new int[4];
        System.arraycopy(srcArr, 1, destArr, 0, 4);
        System.exit(0);   //jvm退出...        0或者非0的數據都可以退出jvm。對於用戶而言沒有任何區別
        System.out.println("目標數組的元素:"+Arrays.toString(destArr));
        System.out.println("當前的系統時間是:"+System.currentTimeMillis());
        
    }

結果:

(編程習慣:try塊中一般傳0,catch中一般傳非0,其實傳什麽也可以達到效果)??

    gc()       建議jvm趕快啟動垃圾回收器回收垃圾

finalize() 如果一個對象被回收器回收的時候,會先調用對象的finalize()方法 我們如果想要看到System.gc()的結果,可以重寫finalize方法

import java.util.*;
public class Demo1 {
/*
gc()            建議jvm趕快啟動垃圾回收器回收垃圾
finalize()         如果一個對象被回收器回收的時候,會先調用對象的finalize()方法
 * */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int[] srcArr= {10,12,14,16,19};
        //把srcArr數組的元素拷貝到destArr數組中
        int[] destArr=new int[4];
        System.arraycopy(srcArr, 1, destArr, 0, 4);
        //System.exit(0);   //jvm退出...        0或者非0的數據都可以退出jvm。對於用戶而言沒有任何區別
        System.out.println("目標數組的元素:"+Arrays.toString(destArr));
        System.out.println("當前的系統時間是:"+System.currentTimeMillis());
        for (int i=0;i<4;i++) {
            new Person("狗娃"+i);
            System.gc();  //建議趕快啟動垃圾回收器回收
        }
    }

}
class Person{
    String name;
    public Person(String name) {
        this.name=name;
    }
    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();
        System.out.println(this.name+"被回收了");
    }
}

結果:
目標數組的元素:[12, 14, 16, 19]
當前的系統時間是:1549627330079
狗娃0被回收了
狗娃2被回收了
狗娃3被回收了
狗娃1被回收了

    getenv(String name)   根據環境變量的名字獲取環境變量

System.out.println("環境變量:"+System.getenv("JAVA_HOME"));

結果:
環境變量:D:\JAVA\jdk\

    getProperties()      獲取當前的系統所有的屬性

    getProperty(String key)   根據系統的屬性名獲取對應的屬性值(獲取一個屬性)

//Properties properties=System.getProperties();
        //properties.list(System.out);
        String value=System.getProperty("os.name");
        System.out.println("當前系統:"+value);

結果:
當前系統:Windows 10

Runtime類    該類主要代表了應用程序的運行環境   

    getRuntime()         返回當前應用程序的運行環境對象
    exec(String command)      根據指定的路徑執行對應的可執行文件

import java.io.IOException;

/*
  getRuntime()   返回當前應用程序的運行環境對象
  exec(String command)     根據指定的路徑執行對應的可執行文件
 */
public class Demo2 {

    public static void main(String[] args) throws IOException, InterruptedException {
        // TODO Auto-generated method stub
        Runtime runtime=Runtime.getRuntime();
        Process process=runtime.exec("C:\\Windows\\notepad.exe");
        Thread.sleep(3000); //讓當前程序停止3秒
        process.destroy();
    }

}

結果:
打開記事本,三秒之後關閉

      freeMemory()       返回jvm空閑的內存,以字節為單位。
      maxMemory()        返回 Java 虛擬機試圖使用的最大內存量
      totalMemory()        返回 Java 虛擬機中的內存總量

public static void main(String[] args) throws IOException, InterruptedException {
        // TODO Auto-generated method stub
        Runtime runtime=Runtime.getRuntime();
        System.out.println("jvm空閑的內存量:"+runtime.freeMemory());
        System.out.println("Java 虛擬機試圖使用的最大內存量"+runtime.maxMemory());
        System.out.println("Java 虛擬機的內存總量"+runtime.totalMemory());
    }

結果:
jvm空閑的內存量:124484880
Java 虛擬機試圖使用的最大內存量2000683008
Java 虛擬機的內存總量125829120

日期類Date

使用Calendar.getInstance()獲取Calendar對象

import java.util.Calendar;
import java.util.Date;
public class Demo3 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //Date date=new Date();   //獲取當前的系統時間
        //System.out.println("年份:"+date.getYear());//已經過時,不推薦使用
        Calendar calendar=Calendar.getInstance();        //獲取當前的系統時間
        System.out.println("年:"+calendar.get(Calendar.YEAR));
        System.out.println("月:"+(calendar.get(Calendar.MONTH)+1));
        System.out.println("日:"+calendar.get(Calendar.DATE));
        
        System.out.println("時:"+calendar.get(Calendar.HOUR_OF_DAY));
        System.out.println("分:"+calendar.get(Calendar.MINUTE));
        System.out.println("秒:"+calendar.get(Calendar.SECOND));
    }

}

結果:
年:2019
月:2
日:12
時:9
分:11
秒:20

日期格式化類SimpleDateFormat

作用:

  1. 可以把日期轉換成指定格式的字符串  format()
  2. 可以把一個字符串轉換成對應的日期 parse()
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class Demo3 {

    public static void main(String[] args) throws ParseException {
        // TODO Auto-generated method stub
        //Date date=new Date();   //獲取當前的系統時間
        //System.out.println("年份:"+date.getYear());
        
        //顯示當前的系統時間:2019年2月12日 xx時xx分xx秒
        /*日期格式化類SimpleDateFormat
         *     作用1:可以把日期轉換成指定格式的字符串        format()
         *     作用2:可以把一個字符串轉換成對應的日期        parse()
         * */
        Date date=new Date();
        SimpleDateFormat dateFormat=new SimpleDateFormat(); //使用了默認的格式創建了一個日期格式化對象
        String time=dateFormat.format(date);
        System.out.println("當前的系統時間:"+time);
        SimpleDateFormat dateFormat2=new SimpleDateFormat("yyyy年MM月dd日  HH:mm:ss");
        time=dateFormat2.format(date);
        System.out.println("當前的系統時間:"+time);
        
        String birthday="1999年11月1日  19:27:35";
        Date date2=dateFormat2.parse(birthday); //註意:指定的字符串格式必須要與SimpleDateFormat的模式要一致,空格個數也要考慮
        System.out.println(date2);
    }

}


結果:
當前的系統時間:2019/2/12 上午10:55
當前的系統時間:2019年02月12日  10:55:31
Mon Nov 01 19:27:35 CST 1999

Math類(數學類)    主要是提供了很多的數學公式

abs(double a)       獲取絕對值
ceil(double a)        向上取整
floor(double a)       向下取整
round(float a)        四舍五入
random()          產生一個隨機數,返回帶正號的 double 值,該值大於等於 0.0 且小於 1.0

public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println("絕對值:"+Math.abs(-3));
        System.out.println("向上取整:"+Math.ceil(3.14));
        System.out.println("向上取整:"+Math.ceil(-3.14));
        System.out.println("向下取整:"+Math.floor(3.14));
        System.out.println("向下取整:"+Math.floor(-3.14));
        System.out.println("四舍五入:"+Math.round(3.54));
        System.out.println("四舍五入:"+Math.round(3.49));
        System.out.println("隨機數:"+Math.random());
    }


結果:
絕對值:3
向上取整:4.0
向上取整:-3.0
向下取整:3.0
向下取整:-4.0
四舍五入:4
四舍五入:3
隨機數:0.6458472762144221

隨機數類:(Random類)

import java.util.Random;

/*
  Random類        隨機數類
  
  
  */
public class Demo5 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Random random=new Random();
        int randomNum=random.nextInt(10)+1;//產生的隨機數是1-10之間的
        System.out.println("隨機數:"+randomNum);
    }

}

結果:
隨機數:2

註意:隨機數產生的區間是根據所給區間 左閉右開 的,也就是包含左邊的臨界值,不包含右邊的

需求:編寫一個函數隨機產生四位的驗證碼。

import java.util.Random;

/*
  Random類        隨機數類
  
  
  */
public class Demo5 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        /*Random random=new Random();
        int randomNum=random.nextInt(10)+1;//產生的隨機數是1-10之間的
        System.out.println("隨機數:"+randomNum);
        */
        
        char[] arr= {‘中‘,‘國‘,‘傳‘,‘a‘,‘Q‘,‘f‘,‘B‘};
        StringBuilder sb=new StringBuilder();
        Random random=new Random();
        //需要四個隨機數,通過隨機數獲取字符數組中的字符
        for (int i=0;i<4;i++) {
            int index=random.nextInt(arr.length);//產生的隨機數必須是數組的索引值範圍之內的
            sb.append(arr[index]);
        }
        //String str=sb.toString();
        //System.out.println("驗證碼為:"+str);
        System.out.println("驗證碼為:"+sb);
    }

}

結果:
驗證碼為:QB傳B

進程:正在執行的程序稱作為一個進程,進程負責了內存空間的劃分

Windows號稱是多任務的操作系統,那麽Windows是同時運行多個應用程序嗎?

    從宏觀的角度:Windows確實是在同時運行多個應用程序

    從微觀的角度:cpu是做了一個快速切換執行的動作,由於速度太快,所以我們感覺不到在切換而已。

線程:線程在一個進程中負責了代碼的執行,就是進程中一個執行路徑

多線程:在一個進程中有多個線程同時在執行不同的任務

疑問:我們以前沒有學過線程,而線程負責代碼的執行,為什麽代碼可以執行呢?

    任何一個Java程序,jvm在運行的時候都會創建一個main線程執行main方法中的所有代碼

一個Java應用程序至少有兩個線程,一個是主線程負責了main方法的執行,一個是垃圾回收器線程,負責了回收垃圾

多線程的好處:

  1. 解決了一個進程能同時執行多個任務的問題
  2. 提高了資源的利用率

多線程的弊端:

  1. 增加了cpu的負擔
  2. 降低了一個進程中線程的執行概率
  3. 引發了線程安全問題
  4. 出現了死鎖現象

如何創建多線程:

    創建線程的方式:

      方式一:

      1. 自定義一個類繼承Thread類
      2. 重寫Thread類的run方法,把自定義線程的任務寫在run方法中
        • 重寫run方法的目的是什麽呢?
          • 每個線程都有自己的任務代碼,jvm創建的主線程的任務代碼就是main方法中的所有代碼,自定義線程的任務代碼就寫在run方法中,自定義線程負責了run方法中的代碼
      3. 調用Thread的子類對象,並且調用start方法開啟線程
        • 註意:一個線程一旦開啟,那麽線程就會執行run方法中的代碼,run方法千萬不能直接調用,直接調用run方法就相當於調用了一個普通的方法而已
public class Demo6 extends Thread{
    
    @Override        //把自定義線程的任務寫在run方法中
    public void run() {
        // TODO Auto-generated method stub
        for (int i=0;i<10;i++) {
            System.out.println("自定義線程:"+i);
        }
        super.run();
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //創建了自定義的線程對象
        Demo6 d=new Demo6();
        //調用start方法啟動線程
        d.start();
        for (int i=0;i<10;i++) {
            System.out.println("main線程:"+i);
        }
    }

}

結果:
main線程:0
自定義線程:0
main線程:1
自定義線程:1
main線程:2
自定義線程:2
自定義線程:3
自定義線程:4
main線程:3
main線程:4
main線程:5
main線程:6
main線程:7
main線程:8
main線程:9
自定義線程:5
自定義線程:6
自定義線程:7
自定義線程:8
自定義線程:9

需求:模擬QQ視頻與聊天同時進行

public class Demo7 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        TalkThread talkThread=new TalkThread();
        talkThread.start();
        VideoThraead videoThread=new VideoThraead();
        videoThread.start();
    }

}
class VideoThraead extends Thread{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        int i=0;
        while (i++<50) {
            System.out.println("視頻");
        }
        super.run();
    }
}
class TalkThread extends Thread{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        int i=0;
        while (i++<50) {
            System.out.println("聊天");
        }
        super.run();
    }
}

結果:
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
視頻
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天
聊天

線程的生命周期圖

技術分享圖片

線程常用的方法:

Thread(String name) 初始化線程的名字

getName() 返回線程的名字

setName(String name) 設置線程對象名

sleep() 線程睡眠指定的毫秒數。  靜態的方法,哪個線程執行了sleep方法代碼那麽就是哪個線程睡眠

getPriority() 返回當前線程對象的優先級 默認線程的優先級是5,優先級的數字越大,優先級越高,優先級的範圍是1~10,註意:線程的優先級高,並不意味著就一定先執行,只是搶到cpu的概率更大了

setPriority(int newPriority) 設置線程的優先級 雖然設置了線程的優先級,但是具體的實現取決於底層的操作系統的實現(最大的優先級是10 ,最小的1 , 默認是5)。

currentThread() 返回CPU正在執行的線程的對象     該方法是一個靜態的方法,哪個線程執行了currentThread()代碼就返回哪個線程的對象

public class Demo8 extends Thread{
    public Demo8() {}
    public Demo8(String name) {
        super(name);        //調用了Thread類的一個參數的構造方法
    }
    @Override
        public void run() {
            // TODO Auto-generated method stub
            for (int i=0;i<10;i++) {
                System.out.println(this.getName()+i);
                try {
                    Thread.sleep(100);//這裏只能捕獲,不能拋出,因為其父類Thread的run方法並沒有拋出異常,子類重寫時拋出的異常類型要小於或等於父類拋出的異常
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            super.run();
        }
    
    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        //創建了一個線程對象
        Demo8 d=new Demo8("狗娃");
        d.sleep(1000);
        d.setName("鐵蛋");//d.setName("鐵蛋");//設置線程的名字
        System.out.println("線程的名字:"+d.getName());
        d.start();
    }

}

結果:
線程的名字:鐵蛋
鐵蛋0
鐵蛋1
鐵蛋2
鐵蛋3
鐵蛋4
鐵蛋5
鐵蛋6
鐵蛋7
鐵蛋8
鐵蛋9    
            先出來第一二行,然後每隔0.1秒出來一行
public class Demo8 extends Thread{
    public Demo8() {}
    public Demo8(String name) {
        super(name);        //調用了Thread類的一個參數的構造方法
    }
    @Override
    public void run() {
            // TODO Auto-generated method stub
        System.out.println("this:"+this);
        System.out.println("當前線程對象:"+Thread.currentThread());
        for (int i=0;i<10;i++) {
            System.out.println(this.getName()+i);
            System.out.println(Thread.currentThread().getName()+i);
            /*try {
                Thread.sleep(100);//這裏只能捕獲,不能拋出,因為其父類Thread的run方法並沒有拋出異常,子類重寫時拋出的異常類型要小於或等於父類拋出的異常
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }*/
        }
        
    }
    
    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        //創建了一個線程對象
        Demo8 d=new Demo8("狗娃");
        System.out.println("自定義線程的優先級:"+d.getPriority());
        System.out.println("主線程的優先級:"+Thread.currentThread().getPriority());
        //d.sleep(1000);
        d.setName("鐵蛋");//d.setName("鐵蛋");//設置線程的名字
        System.out.println("線程的名字:"+d.getName());
        d.setPriority(10);
        d.start();
        for (int i=0;i<10;i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
        Thread mainThread=Thread.currentThread();
        System.out.println("主線程的名字:"+mainThread.getName());
    }

}

結果:
自定義線程的優先級:5
主線程的優先級:5
線程的名字:鐵蛋
main0
main1
main2
main3
main4
this:Thread[鐵蛋,10,main]
當前線程對象:Thread[鐵蛋,10,main]
鐵蛋0
鐵蛋0
鐵蛋1
鐵蛋1
鐵蛋2
鐵蛋2
鐵蛋3
鐵蛋3
鐵蛋4
鐵蛋4
鐵蛋5
鐵蛋5
鐵蛋6
鐵蛋6
鐵蛋7
鐵蛋7
鐵蛋8
鐵蛋8
鐵蛋9
鐵蛋9
main5
main6
main7
main8
main9
主線程的名字:main

需求:模擬三個窗口在售50張票

public class Demo9 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //創建三個線程對象,模擬三個窗口
        SaleTicket thread1=new SaleTicket("窗口1");
        SaleTicket thread2=new SaleTicket("窗口2");
        SaleTicket thread3=new SaleTicket("窗口3");
        //開啟線程售票
        thread1.start();
        thread2.start();
        thread3.start();
    }

}
class SaleTicket extends Thread{
    static int num=50;//票數        不加static的時候是非靜態的成員變量,非靜態的成員變量數據是在每個對象中都會維護一份數據的。所以會出現三個窗口賣了150張票的情況
    public SaleTicket() {}
    public SaleTicket(String name) {
        // TODO Auto-generated constructor stub
        super(name);
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            if (num>0) {
                System.out.println(Thread.currentThread().getName()+"售出了第"+num+"號票");
                num--;
            }
            else {
                System.out.println("售罄了...");
                break;
            }
        }
        //super.run();
    }
}

問題1:50張票售了150次

可以向票數上加static使其成為靜態成員變量來解決

問題2:出現了線程安全問題

出現線程安全問題的根本原因:

  1. 存在兩個或者兩個以上的線程對象,而且線程之間共享著一個資源
  2. 有多個語句操作了共享資源

線程安全問題的解決方案:sun提供了線程同步機制讓我們解決這類問題的

Java線程同步機制的方式:

    方式一:同步代碼塊

      同步代碼塊的格式:

        synchronized(鎖對象){

          需要被同步的代碼...

        }

同步代碼塊要註意的事項:

    1. 任意的一個對象都可以作為鎖對象
    2. 在同步代碼塊中調用了sleep方法並不是釋放鎖對象的,也就是不會改變鎖對象的開關狀態,就像你睡覺鎖著門,別人也進不來
    3. 只有真正存在線程安全問題的時候才使用同步代碼塊,否則會降低效率的
    4. 多線程操作的鎖對象必須是唯一共享的,否則無效。
public class Demo9 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //創建三個線程對象,模擬三個窗口
        SaleTicket thread1=new SaleTicket("窗口1");
        SaleTicket thread2=new SaleTicket("窗口2");
        SaleTicket thread3=new SaleTicket("窗口3");
        //開啟線程售票
        thread1.start();
        thread2.start();
        thread3.start();
    }

}
class SaleTicket extends Thread{
    static int num=50;//票數        不加static的時候是非靜態的成員變量,非靜態的成員變量數據是在每個對象中都會維護一份數據的。所以會出現三個窗口賣了150張票的情況
    static Object o=new Object();//為什麽要加static呢
    public SaleTicket() {}
    public SaleTicket(String name) {
        // TODO Auto-generated constructor stub
        super(name);
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            //同步代碼塊
            synchronized (o) {//可以使用字符串   "鎖"   ,這是最簡單的鎖對象,因為它在字符串常量池中
                if (num>0) {
                    System.out.println(Thread.currentThread().getName()+"售出了第"+num+"號票");
                    num--;
                }
                else {
                    System.out.println("售罄了...");
                    break;
                }
            }
        }
        //super.run();
    }
}

結果:
窗口1售出了第50號票
窗口1售出了第49號票
窗口1售出了第48號票
窗口1售出了第47號票
窗口3售出了第46號票
窗口3售出了第45號票
窗口3售出了第44號票
窗口3售出了第43號票
窗口3售出了第42號票
窗口3售出了第41號票
窗口3售出了第40號票
窗口3售出了第39號票
窗口3售出了第38號票
窗口2售出了第37號票
窗口2售出了第36號票
窗口2售出了第35號票
窗口2售出了第34號票
窗口2售出了第33號票
窗口2售出了第32號票
窗口2售出了第31號票
窗口2售出了第30號票
窗口2售出了第29號票
窗口2售出了第28號票
窗口2售出了第27號票
窗口2售出了第26號票
窗口2售出了第25號票
窗口2售出了第24號票
窗口2售出了第23號票
窗口2售出了第22號票
窗口2售出了第21號票
窗口2售出了第20號票
窗口2售出了第19號票
窗口2售出了第18號票
窗口2售出了第17號票
窗口2售出了第16號票
窗口2售出了第15號票
窗口2售出了第14號票
窗口2售出了第13號票
窗口2售出了第12號票
窗口2售出了第11號票
窗口2售出了第10號票
窗口2售出了第9號票
窗口2售出了第8號票
窗口2售出了第7號票
窗口2售出了第6號票
窗口2售出了第5號票
窗口2售出了第4號票
窗口2售出了第3號票
窗口2售出了第2號票
窗口2售出了第1號票
售罄了...
售罄了...
售罄了...

    方式二:同步函數

Java學習筆記(13)