一些基本流的使用和線程講解

一 . 一些基本的操作流

1.
LineNumberReader:
* public int getLineNumber():獲取行號
* public void setLineNumber(int lineNumber):設置起始行號
String readLine():讀取一行
案例:讀取文件,每次讀取一行打印並且加上行號

/**
 * 1.
   LineNumberReader:
 * public int getLineNumber():獲取行號
 * public void setLineNumber(int lineNumber):設置起始行號
   String readLine():讀取一行
        案例:讀取文件,每次讀取一行打印並且加上行號
 *
 */
public class LineNumberReaderDemo {
    public static void main(String[] args) throws IOException {
        //創建LineNumberReader對象
        //public LineNumberReader(Reader in)
        LineNumberReader lnr = new LineNumberReader(new FileReader("a.txt"));

        //默認起始行號從0開始
        //設置其實行號爲從10開始
        lnr.setLineNumber(10);

        //一次讀取一行
        String line;
        while ((line = lnr.readLine())!=null) {
            //打印每一行的行號和內容
            System.out.println(lnr.getLineNumber()+":"+line);
        }

        //關流
        lnr.close();
    }

}

//心得:當要用到標記行號時,可以用LineNumberReader讀取


2.操作基本數據類型的流

  • 可以操作基本類型的流對象。
  • DataInputStream:讀數據
  • DataOutputStream:寫數據

  • 案例:給流中寫基本類型的數據,並且讀取
    注意:
  • 讀寫順序必須一致,否則數據有問題。
/**
 * 2.操作基本數據類型的流
 * 可以操作基本類型的流對象。
 * DataInputStream:讀數據
 * DataOutputStream:寫數據
 案例:給流中寫基本類型的數據,並且讀取
 注意:
 * 讀寫順序必須一致,否則數據有問題。
 *
 */
public class DataOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        //寫數據
        //write();
        read();
    }

    private static void read() throws IOException {
        //DataInputStream:讀數據
        //創建對象:public DataInputStream(InputStream in)
        DataInputStream dis = new DataInputStream(new FileInputStream("dos.txt"));

        //讀數據了,按什麼順序寫入就必須按照什麼順序讀出來
        System.out.println(dis.readByte());
        System.out.println(dis.readShort());
        System.out.println(dis.readInt());
        System.out.println(dis.readLong());
        System.out.println(dis.readChar());
        System.out.println(dis.readFloat());
        System.out.println(dis.readDouble());
        System.out.println(dis.readBoolean());

        //關流
        dis.close();
    }

    private static void write() throws IOException {
        //public DataOutputStream(OutputStream out)
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("dos.txt"));

        //給流關聯的文件中寫入基本類型的數據
        dos.writeByte(20);
        dos.writeShort(200);
        dos.writeInt(2000);
        dos.writeLong(20000L);

        dos.writeChar(97);
        dos.writeFloat(12.34F);
        dos.writeDouble(23.34);
        dos.writeBoolean(true);

        //關流
        dos.close();
    }

}

//心得:當需要有次序的讀寫數據時,可用DataOutputStream和DataInputStream方法來讀取


3.
內存操作流:解決臨時數據存儲的問題。
操作字節數組(演示着一個案例即可)
ByteArrayInputStream
ByteArrayOutputStream
byte[] toByteArray() 將之前寫入內存的流轉換成字節數組

操作字符數組
CharArrayReader
CharArrayWrite

操作字符串
StringReader
StringWriter

案例:演示
操作字節數組
ByteArrayInputStream
ByteArrayOutputStream

將數據寫到流中保存在內存,並且讀取
//在這裏示例ByteArrayOutputStream
//剩下的讀者有興趣可以去試試,用法和這個一致

/**
 *  操作字節數組(演示着一個案例即可)
     ByteArrayInputStream
     ByteArrayOutputStream
     byte[] toByteArray() 將之前寫入內存的流轉換成字節數組
 *
 */
public class ByteArrayOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        //給內存中寫數據public ByteArrayOutputStream()
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        //給內存中調用方法寫數據
        baos.write("hello".getBytes());
        //將寫入內存中的數據讀取出來
        byte[] buf = baos.toByteArray();//調用這個方法,將之前寫入內存中的數據存儲到字節數組中
        ByteArrayInputStream bais = new ByteArrayInputStream(buf);//將剛纔存儲到字節數組中的內容關聯上bais

        //只有這樣之後,我們纔可以直接從bais中讀取我們想要的內容
        //一次讀取一個字節
        int by;
        while ((by=bais.read())!=-1) {
            System.out.print((char)by);
        }
        //關流
        bais.close();
        baos.close();
    }

}

//要使用臨時存取數據時,可使用操作字節字符數組等來實現臨時存取數據


4.
* 打印流:
* 字節打印流 PrintStream
* 字符打印流 PrintWriter
*
* 特點:
* A:只能操作目的地,不能操作數據源
* B:可以操作任意類型的數據
* C:如果啓動了自動刷新,能夠自動刷新
* D:可以操作文件的流
* 注意:什麼流可以直接操作文件?
看流對象的API,如果其構造方法同時有File和String類型的參數,就可以直接操作文件。

案例1:利用字符打印流給文件中書寫數據(String類型),需要手動刷新

public class PrintWriterDemo {
    public static void main(String[] args) throws IOException {
        //使用打印流給文件中寫入hello,java,world
        //public PrintWriter(String fileName)
        PrintWriter pw = new PrintWriter("pw.txt");

        //給流關聯的文件中寫數據
        pw.write("hello");
        pw.write("java");
        pw.write("world");

        //刷新
        pw.flush();     
        //3.關流
        pw.close();
    }

}
  • 操作任意類型的數據呢?
  • print()
  • println():如果啓動了自動刷新,能夠實現刷新,而且還實現了自動換行。
  • 如何啓動自動刷新:利用構造
  • PrintWriter(OutputStream out, boolean autoFlush)
  • PrintWriter(Writer out, boolean autoFlush)

  • 如果啓用了自動刷新,則只有在調用 println、printf 或 format 的其中一個方法時纔可能完成此操作
    案例2:利用字符流給文件中寫數據(int類型,boolean類型),啓動自動刷新
public class PrintWriterDemo2 {
    public static void main(String[] args) throws IOException {
        //創建字符打印流對象,並開啓自動刷新
        //public PrintWriter(Writer out,boolean autoFlush)
PrintWriter pw = new PrintWriter(new FileWriter("pw2.txt"), true);

        //給流中寫數據
        //pw.write("hello");
        //pw.write("java");

        //注意:如果已經開啓了自動刷新功能,必須調用則 println、printf 或 format的時候,纔可以實現自動刷新
        pw.println("hello");
        pw.println("java");
        pw.println("world");//調用println這個方法給文件中寫數據,1.寫數據  2.換行  3.刷新

        //可以操作任意類型的數據
        pw.println(true);
        pw.println(12.34);

        //關流
        pw.close();

    }

}

案例3:利用字符打印流複製java文件(BufferedReader+PrintWriter)

public class PrintWriterDemo3 {
    public static void main(String[] args) throws IOException {
        /**
         * 案例3:利用字符打印流複製java文件(BufferedReader+PrintWriter)
         */
        //數據源
        BufferedReader br = new BufferedReader(new FileReader("PrintWriterDemo.java"));
        //目的地
        //PrintWriter(Writer out, boolean autoFlush) 
        PrintWriter pw = new PrintWriter(new FileWriter("copy.java"),true);

        //讀一行寫一行
        String line;
        while ((line=br.readLine())!=null) {
            pw.println(line);//1.寫數據  2.換行  3.刷新
        }

        //關流
        pw.close();
        br.close();

    }

}

心得:當使用printwriter時,他只能寫不能讀
也就是說只能操作目的地,不能操作數據源
可以操作任意類型的數據
如果啓動了自動刷新還可以自動刷新,自動換行
可以操作文件的流


5.標準輸入輸出流
* System類下有這樣的兩個成員變量:

  • 標準輸入流:
  • public static final InputStream in

    案例1:利用標註輸入流進行鍵盤錄入,錄入後讀取流並打印在控制檯

public static void main(String[] args) throws IOException {
        /**
         * public static final InputStream in
         */
        //將鍵盤錄入的數據封裝在了輸入流中
        //Scanner sc = new Scanner(System.in);
        InputStream is = System.in;

        //將鍵盤錄入的數據從輸入流中讀取出來
        int by;
        while ((by=is.read())!=-1) {
            System.out.print((char)by);
        }

        //關流
        is.close();
    }

案例2:用IO流實現鍵盤錄入,一次讀取一行數據
分析:
InputStream is = System.in;
BufferedReader是字符緩衝流,是對字符流進行高效操作的
所以,參數必須是字符類型
而我們現在有的是字節類型的流
請問:怎麼辦呢?轉換流

  • 標準輸出流:
    public static final PrintStream out
public static void main(String[] args) throws IOException {
        /**
         * 案例2:用IO流實現鍵盤錄入,一次讀取一行數據
         * InputStream is = System.in;
         * InputSreamReader isr = new InputStreamReader(is)
         * BufferedReader br = new BufferedReader(isr); 
         */
        //將上面的分析寫爲一部
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        //一次讀取一行數據
        System.out.println("請輸入你的姓名");
        String name = br.readLine();
        System.out.println("請輸入你的年齡");
        String age = br.readLine();

        System.out.println(name+":"+age);
    }

案例:解析輸出語句System.out.println(“helloworld”);

public static void main(String[] args) {
//      PrintStream ps = System.out;
//      ps.println(true);

        //上面兩行合併爲一行,底層調用的字節打印流中的方法
        System.out.println(true);
    }```

在調用system.in時,要獲取鍵盤錄入的數據
得用標準輸入流讀取鍵盤錄入的數據
在打印時,調用system.out時控制檯打印寫入的數據

----------

6.
 * 合併流:SequenceInputStream類可以將多個輸入流串流在一起,合併爲一個輸入流,因此,該流也被稱爲合併流。

   構造:
   SequenceInputStream(InputStream s1, InputStream s2) :將s1和s2合併成一個輸入流,先讀取s1後讀取s2

 * 
 * 案例1:
 *      我要把DataStreamDemo.java和ByteArrayStreamDemo.java寫到一個文件Copy.java
 * 
 * 數據源:
 *      DataStreamDemo.java
 *      ByteArrayStreamDemo.java
 * 目的地:
 *      Copy.java

/**
* 6.
* 合併流:SequenceInputStream類可以將多個輸入流串流在一起,合併爲一個輸入流,因此,該流也被稱爲合併流。

構造:
SequenceInputStream(InputStream s1, InputStream s2) :將s1和s2合併成一個輸入流,先讀取s1後讀取s2


    • 案例1:
  • 我要把DataStreamDemo.java和ByteArrayStreamDemo.java寫到一個文件Copy.java

    • 數據源:
  • DataStreamDemo.java
  • ByteArrayStreamDemo.java
  • 目的地:
  • Copy.java
    *
    */
    public class SequenceInputStreamDemo {
    public static void main(String[] args) throws IOException {
    //創建合併流對象
    //SequenceInputStream(InputStream s1, InputStream s2) :將s1和s2合併成一個輸入流,先讀取s1後讀取s2
    //將兩個數據源合而爲一
    SequenceInputStream sis = new SequenceInputStream(new FileInputStream(“PrintWriterDemo.java”), new FileInputStream(“SystemIn2.java”));
    //封裝目的地
    FileOutputStream fos = new FileOutputStream(“copy2.java”);

    //一下讀寫一個字節數組
    byte[] buf = new byte[1024];
    int len;
    while ((len=sis.read(buf))!=-1) {
        //讀多少寫多少
        fos.write(buf, 0, len);
    }
    
    //關流
    fos.close();
    sis.close();
    

    }

}

在使用兩個數據源時,可以用sequence拼接,然後將其寫入另一個目的地中

----------

8. 對象的序列化和反序列化
 * 序列化流:把對象按照流一樣的方式寫到文件或者在網絡中傳輸。    
 * 反序列化流:把文件或者網絡中的流對象數據還原對象。
 * 
 * ObjectOutputStream:序列化流
   writeObject(Object obj) 將指定的對象寫入 ObjectOutputStream。

 * ObjectInputStream:反序列化流
   Object readObject() 從 ObjectInputStream 讀取對象。 
 * 
   注意:如果一個類不是實現Serializable接口無法把實例化,會報異常java.io.NotSerializableException
 * 類通過實現 java.io.Serializable 接口以啓用其序列化功能。未實現此接口的類將無法使其任何狀態序列化或反序列化。
 * 
 * 如何實現序列化?
 *      讓對象所屬類的實現序列化接口。   
 * 對象化示例

學生類
public class Studnet implements Serializable{
//實現這個接口不需要實現任何方法,這個接口說白了就是僅僅給Student類,打上了一個可以被序列化的標示
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Studnet(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Studnet() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return “Studnet [name=” + name + “, age=” + age + “]”;
}

}

測試類
public static void main(String[] args) throws IOException {
//創建序列化流對象
//public ObjectOutputStream(OutputStream out)
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“oos.txt”));

    //創建一個學生對象,將學生對象寫入文件中
    Studnet s = new Studnet("劉德華", 50);
    oos.writeObject(s);
    // java.io.NotSerializableException
    //類通過實現 java.io.Serializable 接口以啓用其序列化功能

    //關流
    oos.close();

}

public static void main(String[] args) throws Exception {
//創建反序列化流對象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(“oos.txt”));

    //讀取文件中存儲的對象,以實現反序列化
    //readObject()
    Object object = ois.readObject();
    System.out.println(object);

    //關流
    ois.close();
    /**

* 9. Properties(查看api實現map接口本質是一個map集合)
* Properties:Properties 類表示了一個持久的屬性集。屬性列表中每個鍵及其對應值都是一個字符串。
* 特點:Properties 可保存在流中或從流中加載。
案例:使用map集合的put方法給集合中存儲數據並且遍歷
*/
public class ProppertiesDemo {
public static void main(String[] args) {
//創建Properties對象,本質上上是一個map幾個,但是鍵值都是String類型
Properties prop = new Properties();

    //給對象中存儲元素
    prop.put("zhangjie", "xiena");
    prop.put("huangxiaoming", "baby");
    prop.put("wanglaoshi", "zhangziyi");

    //map集合遍歷
    //獲取鍵的集合
    Set<Object> keys = prop.keySet();
    for (Object key : keys) {
        System.out.println(key+":"+prop.get(key));
    }

}
}
序列化就是將對象寫入目的地中
反序列化就是存入的對象從數據源中取出來
優點就是使用的時候可以將對象存入文件中


----------

9. Properties(查看api實現map接口本質是一個map集合)
 9.1
 * Properties:Properties 類表示了一個持久的屬性集。屬性列表中每個鍵及其對應值都是一個字符串。     
 * 特點:Properties 可保存在流中或從流中加載。
   案例:使用map集合的put方法給集合中存儲數據並且遍歷


/**
* 9. Properties(查看api實現map接口本質是一個map集合)
* Properties:Properties 類表示了一個持久的屬性集。屬性列表中每個鍵及其對應值都是一個字符串。
* 特點:Properties 可保存在流中或從流中加載。
案例:使用map集合的put方法給集合中存儲數據並且遍歷
*/
public class ProppertiesDemo {
public static void main(String[] args) {
//創建Properties對象,本質上上是一個map幾個,但是鍵值都是String類型
Properties prop = new Properties();

    //給對象中存儲元素
    prop.put("zhangjie", "xiena");
    prop.put("huangxiaoming", "baby");
    prop.put("wanglaoshi", "zhangziyi");

    //map集合遍歷
    //獲取鍵的集合
    Set<Object> keys = prop.keySet();
    for (Object key : keys) {
        System.out.println(key+":"+prop.get(key));
    }

}

 9.2
 * Properties的特有功能:
 *      A:添加元素
 *          public Object setProperty(String key,String value)
 *      B:獲取元素
 *          public String getProperty(String key)
 *          public Set<String> stringPropertyNames()
 案例:使用它的特有功能添加元素並遍歷


package com.edu_08;

import java.util.Properties;
import java.util.Set;

/**
* 9.2
* Properties的特有功能:
* A:添加元素
* public Object setProperty(String key,String value)
* B:獲取元素
* public String getProperty(String key)
* public Set stringPropertyNames()
案例:使用它的特有功能添加元素並遍歷
*
*/
public class PropertiesDemo2 {
public static void main(String[] args) {
//創建Properties這個集合
Properties prop = new Properties();

    //調用public Object setProperty(String key,String value)
    prop.setProperty("huangxiaoming", "baby");
    prop.setProperty("dengchao", "sunli");
    prop.setProperty("xidada", "pengliyuan");

    //1.獲取所有鍵的集合
    //public Set<String> stringPropertyNames()
    Set<String> keys = prop.stringPropertyNames();
    //遍歷鍵,根據鍵找值
    for (String key : keys) {
        System.out.println(key+":"+prop.getProperty(key));
    }


}

}


 9.3
 * 可以和IO流進行結合使用:
 *      把文件中的數據加載到集合中。注意:文件中的數據必須是鍵值對象形式的(例如:張傑=謝娜)。
 *      public void load(InputStream inStream)
 *      public void load(Reader reader)
        案例:創建一個鍵值對文件,將文件中的鍵值對加載到集合中,輸出集合

 * 
 *      把集合中的數據存儲到文本文件中,並且是按照鍵值對形式存儲的。

package com.edu_08;

import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

/**
* 把集合中的數據存儲到文本文件中,並且是按照鍵值對形式存儲的。
* public void store(OutputStream out,String comments)
* public void store(Writer writer,String comments)
案例:將創建的鍵值對集合加載到文件中
*
*/
public class PropertiesDemo4 {
public static void main(String[] args) throws IOException {
//創建集合
Properties prop = new Properties();

    //給集合中存儲數據
    prop.setProperty("liudehua", "50");
    prop.setProperty("liming", "60");
    prop.setProperty("zhangxueyou", "40");

    //將集合中的元素,存儲到文本文件中
    prop.store(new FileWriter("prop2.txt"), "name=age");

}

}

心得:在使用properties時,可以利用其中的store方法進行存儲鍵值對象
可利用load方法進行讀取文件中的鍵值對象
存儲方式爲
成員=年齡 形式

----------
1:多線程(理解)
    (1)線程是依賴於進程而存在的。
        A:進程    正在運行的應用程序
        B:線程    進程的執行路徑,執行單元
        案例:畫圖理解單線程和多線程的代碼執行流程
    ![線程和進程](https://img-blog.csdn.net/20170515193512190?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc21pdGhsaWFuZzE5OTY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)   
    ![多線程](https://img-blog.csdn.net/20170515193634722?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc21pdGhsaWFuZzE5OTY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
    (2)多線程的兩種方案:(掌握)
        請參照:多線程兩種方式
        繼承Thread類(查看api簡單介紹Thread類):
        實現Runable接口:

一:繼承thread類
public class MyThread extends Thread{
//1.繼承Thread類
//2.重寫run方法,重寫run方法中的代碼之後,當我們啓動了這個線程之後,我們的這個線程就會執行run方法中的代碼
@Override
public void run() {
//需求:開啓該線程之後,執行一個for循環
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}

}
public static void main(String[] args) {
//只要我們創建了一個線程對象,並且啓動該線程的實例,我們就相當於開啓了一個線程
MyThread mt = new MyThread();
mt.start();//1.開啓了一個線程 2.讓開啓的這個線程執行他對應的類中的run方法

    //在次創建一個子線程,並開啓這個子線程執行他的run方法
    MyThread mt2 = new MyThread();
    mt2.start();

}

二:實現runable接口
public class MyThread implements Runnable{
@Override
public void run() {
//啓動該線程對象之後,需要執行的代碼
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}

}
public static void main(String[] args) {
//創建Mythread對象
MyThread mt = new MyThread();
//開啓這個線程
//mt.start();//這裏的這個類僅僅是實現了Runnalble接口的一個類,但是start方法在Thread類中
//但是我們想要開啓一個線程,就必須調用start方法,請問怎麼辦?

    //public Thread(Runnable target)
    Thread t1 = new Thread(mt);
    t1.start();


}

    (3)多線程的幾個問題:
        A:啓動線程用的是哪個方法
            start()
        B:start()和run()的區別
            start():1.開啓線程  2.執行run()方法裏面的代碼
            run():執行的是線程裏面執行的代碼,並不會開啓線程
        C:爲什麼要重寫run()
            因爲每個線程需要執行的代碼都是都是不一樣的,
            我們需要將每個線程自己獨立執行的代碼寫到run()方法中執行
        D:線程可以多次啓動嗎
        答:如果是不同的線程對象,可以同時啓動
        不能多次啓動          
    (4)線程的調度和控制
        線程休眠(Thread.sleep(毫秒值))
        線程名稱(setName(),getName();)
        線程的調度及優先級setPriority(10)(注意默認值是5,區間在1-10之間)
        什麼叫線程優先級:說白了就是設置你搶佔cpu執行權搶佔到的概率

    (5)多線程案例(兩種方式實現,睡一會出現線程安全問題):
        5.1繼承Thread賣票
        5.2實現Runnable賣票(睡一會出現線程安全問題)    

public class MyThread extends Thread{
//共有100張票,將ticket改爲靜態之後,被類的所有對象所共享
static int ticket = 100;

@Override
public void run() {
    //用一個while true循環模擬三個窗口一直處於打開的狀態
    while (true) {
        //只有當ticket>0的時候,纔可以出售票
        if (ticket>0) {
            System.out.println(getName()+"正在出售第:"+ticket--+"張票");
        }
    }
}

}

public class Test {
public static void main(String[] args) {
//創建三個線程模擬三個售票窗口
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();

    //給線程設置名稱
    mt1.setName("窗口一");
    mt2.setName("窗口二");
    mt3.setName("窗口三");

    //啓動線程,開啓售票
    mt1.start();
    mt2.start();
    mt3.start();

}

}

        問題:
        按照真實的情景加入了延遲,確發現出現了這樣的兩個問題:
        A:相同的票賣了多次
            CPU的一次操作必須是原子性的(操作是CPU執行一次就可以直接完成的)
        B:出現了負數的票
            隨機性和延遲導致的
        出現上面的問題稱爲線程安全問題。

    (6)多線程安全問題
        A:是否是多線程環境
        B:是否有共享數據
        C:是否有多條語句操作共享數據


解決辦法

package com.edu_09;
/**
* 加入延遲之後出現的問題:
* 1.出現了重複的票,出現了兩張100的票
* 因爲cpu的執行具有原子性(cpu一次就可以執行完畢的動作)。。
* 2.出現了第0張票和第-1張票
* 就是因爲線程搶佔的隨機性,和延遲性導致的
*
* 以上的兩個問題,統稱爲線程安全問題。。
*
* 總結:什麼時候會出現線程安全問題?
* 1.存在多線程的情況
* 2.多個線程之間存在共享數據
* 3.存在多條語句操作共享數據
*
* 在我們自己的程序中存在線程安全問題嗎?存在。。
* 1.存在多線程的情況
* 存在
* 2.多個線程之間存在共享數據
* 存在
* 3.存在多條語句操作共享數據
* 存在
*
*
* 如何解決線程安全問題??
* 1.存在多線程的情況
* 改不了
* 2.多個線程之間存在共享數據
* 改不了
* 3.存在多條語句操作共享數據
* 可以改變
*
*
*(7)如何解決多線程安全問題(掌握)
注意:線程安全執行效率就低,線程不安全,執行效率高
A:同步代碼塊(測試不是同一個鎖的情況,測試是同一個鎖的情況)
synchronized(對象) {
需要被同步的代碼。
}
* 1.對象?
* 答:任意對象,這個對象就被成爲鎖
* 2.需要被同步的代碼?
* 答:會出現線程安全問題的代碼。
* 3.注意:同步代碼塊對這個鎖是有要求的
* 答:需要多個線程共享同一把鎖
*
*
*
*/
public class MyThread implements Runnable{
//定義100張票
int ticket = 100;
Object obj = new Object();

@Override
public void run() {
    while (true) {
        //同步代碼塊
        //synchronized (new Object()) {//t1,t2,t3三個線程不共享同一把鎖每個線程都有自己的議案鎖
        synchronized (obj) {//這樣3個線程纔可以共享同一把鎖   
        if (ticket>0) {
                //考慮到實際的生活中,我們需要給每一個線程加入一定的延遲,模擬一下這種效果
                try {
                    Thread.sleep(100);
                    /**
                     * 分析:爲什麼會出現兩張100張票
                     * t1搶佔到cpu的執行權,此時ticket=100,但是此刻休眠了
                     * 此時被t2搶佔到了cpu的執行權,此時ticket=100,
                     * t1,t2分別睡了100毫秒之後,分別醒來了。。
                     * t1此時出售第100張票
                     * t2此時出售第100張票
                     */

                    /**
                     * 分析:爲什麼會出現0張票和-1張票
                     * 假設此時票池中僅剩1張票了,
                     * t1進來休眠了
                     * t2進來休眠了
                     * t3進來休眠了
                     */
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"張票");
                /**
                 * t1醒來,出售的是第1張票,此時tickt=0
                 * t2醒來,出售第0張票,此時ticket=-1
                 * t3醒來,出售第-1張票,此時ticket=-2
                 */

                /**
                 * ticket--這個動作一共包含幾步:
                 * 1.打印出ticket此刻本身的值
                 * 2.ticket自減1
                 * 3.將自減之後的ticket的最新的值賦值給變量ticket
                 */
            }
        }
        //當被同步的代碼執行完畢之後,t1手裏拿着的obj這個鎖纔會被釋放,
        //t1,t2,t3重新搶佔cpu的執行權,誰搶到了繼續拿着obj這個鎖,執行同步代碼塊中的內容
    }

}

}

    (7)如何解決多線程安全問題(掌握)
        * 線程安全執行效率就低
        A:同步代碼塊(測試不是同一個鎖的情況,測試是同一個鎖的情況)
            synchronized(對象) {
                需要被同步的代碼。
            }
            需求:1.測試不是同一把鎖的時候線程安全嗎? 2.如果是同一把鎖線程安全嗎?
        兩個問題:1.對象是什麼 ?
                  答:任意對象 ,相當於是一把鎖,只要線程進去就把鎖鎖上
                  2.需要同步的代碼?
                  答:被線程執行的代碼

        C:鎖對象問題
            a:同步代碼塊(定義一個抽象類,裏面專門定義一個鎖)
                任意對象

            b:同步方法(僅適用於實現runable接口)
            public synchronized void sellTicket(){同步代碼}
                this

            c:靜態同步方法
                類的字節碼對象
                public static synchronized void sellTicket() {
                        需要同步的代碼
                }


    (8)匿名內部類的方式使用多線程(掌握)
        new Thread() {
            public void run() {
                ...
            }
        }.start();

        new Thread(new Runnable(){
            public void run() {
                ...
            }
        }).start();

    案例:利用匿名內部類,啓動多個線程,驗證單例設計模式之懶漢式所存在的缺陷,
          當使用多線程來搞的時候就不單例了。。

package com.edu_11;
/**
* (8)匿名內部類的方式使用多線程(掌握)
new Thread() {
public void run() {

}
}.start();

    new Thread(new Runnable(){
        public void run() {
            ...
        }
    }).start();

*
*/
public class NiMingThread {
public static void main(String[] args) {
//方式1:
new Thread(){
//重寫的方法
@Override
public void run() {
for (int i = 0; i <10; i++) {
System.out.println(i);
}
}
}.start();

    //方式2
    new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i <10; i++) {
                System.out.println(i);
            }
        }
    }).start();


}

}

    (9) JDK5的Lock鎖,我們之前造的所有的鎖都沒有手動釋放鎖
        static Lock lock = new ReentrantLock();
        枷鎖:lock.lock();
        釋放鎖:lock.unlock();
        可以讓我們明確的知道在哪裏加鎖和釋放鎖。
        依然寫一個賣票的demo,用lock枷鎖釋放鎖,
        爲了保證我們創建的鎖一定會被釋放,用一下代碼進行改進
        try{....}finally{.....}

package com.edu_15;

public class NotifyThread extends Thread{
@Override
public void run() {
synchronized (MyLock.obj) {
//幻想等待線程
MyLock.obj.notify();//喚醒正在等待的線程,喚醒的等待線程的鎖對象,必須和等待線程的鎖對象一致
}
}

}

package com.edu_15;

public class WaitThread extends Thread{

@Override
public void run() {
    synchronized (MyLock.obj) {
        //讓等待線程處於等待狀態
        try {
            MyLock.obj.wait();//當線程處於等待狀態的時候,線程就不會繼續往下執行了
                              //線程在處於等待的時候,會釋放掉自己手中的鎖
                             //sleep()這個方法,在線程休息的時候會釋放鎖碼?
                            //答:不會
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    System.out.println("我被喚醒了");
}

}

package com.edu_15;

public abstract class MyLock {
public static final Object obj = new Object();

}

package com.edu_15;
/**
* (11)線程等待和喚醒機制(案例演示:waitThread,NotifyThread,MyLock,Test)
鎖對象調用wait():線程的等待
鎖對象調用notify():線程的喚醒
* 注意:
wait和sleep的區別
線程等待,在等待的同時釋放鎖,而sleep()方法在執行的過程中是不會釋放鎖的
*/
public class Test {
public static void main(String[] args) {
//創建等待線程,讓等待線程處於一個等待狀態
WaitThread wt = new WaitThread();
wt.start();

    //睡上5秒鐘之後喚醒等待線程
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }


    //創建喚醒線程對象
    NotifyThread nt = new NotifyThread();
    nt.start();

}

}

“`
(10)死鎖問題
同步嵌套,鎖裏面套了一個鎖,出現同步嵌套
(簡單介紹,要求大家以後寫的時候需要注意)

這裏需要注意,當使用同步嵌套鎖時,可能會導致電腦卡死,他會出現兩個線程搶佔資源的問題,所以最好點一下就停止運行

(11)線程等待和喚醒機制(案例演示:waitThread,NotifyThread,MyLock,Test)
    鎖對象調用wait()鎖對象調用notify()

    注意:
    wait和sleep的區別
    線程等待,在等待的同時釋放鎖,而sleep()方法在執行的過程中是不會釋放鎖的

主要掌握線程創建的兩種方法1.繼承Thread類重新其中的run()方法 2.創建線程名稱和獲取名稱 線程睡眠 3.給線程上鎖 4.線程的喚醒和等待

最主要的是線程安全問題,必須小心操作

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