Day 30 單例模式和Java NIO

Day 30

Author : ScorpioDong

1. 單例模式

1.1 要求

	當前類只有一個對象,一旦當前類存在一個對象之後,無法在重新創建當前類的對象,就算是你要創建,代碼返回的對象依然是上一次創建的對象。
	懶漢模式,餓漢模式

1.2 單例模式推導

package com.qfedu.a_single;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * SingleDog要求是一個單例類,整個代碼運行週期內有且只有一個類對象
 *
 * 不可取方式:
 * @全體成員 SingleDog類使用一個單例類,我創建過對象了,你們不要搞事情
 *          紅包
 * 【期望】
 *      從語法角度約束一些行爲。
 *      目前創建對象過於簡單,new + 構成方法組合非常easy
 * 【解決方案】
 *      private修飾構造方法
 *
 * 【問題】
 *      構造方法私有化之後,類外沒有操作構造方法的權限,沒辦法創建對象
 * 【解決方案】
 *      1. 暴力反射
 *          暴力反射,給予構造方法操作權限,這裏和沒有使用private有什麼區別
 *          【不合適】
 *      2. 期望
 *          類外最起碼有一個對象。
 *          a. 類外沒有SingleDog類對象
 *          b. 期望獲取到一個SingleDog對象
 *
 *          需要方法來完成
 *              1. 該方法要求靜態成員方法,沒有對象,需要通過類名調用
 *              2. 類外要求使用public修飾
 *              3. 該方法需要得到一個SingleDog對象,返回值類型是SingleDog
 *              4. 方法參數爲無參數,構造方法也是無參
 *              5. 方法名 getInstance
 *              public static SingleDog getInstance()
 *
 * 【問題】
 *      依然是不同對象
 *
 * 【期望】
 *      1. 調用該方法每一次調用都是new新的對象。
 *      2. 這個new得有限制,如果之前創建過對象,你就不能再new了,沒有對象可以new
 *      3. 存在一個變量可以保存之前創建對象的空間首地址,並且可以持久性保存。
 *      分析:
 *          a. 需要保存SingleDog類對象空間首地址
 *                 SingleDog類型
 *          b. 該數據不能類外輕鬆獲取
 *                 private
 *          c.
 *              (1) 靜態方法可以使用
 *              (2) 有一定的持久性
 *                  static修飾成員變量
 *          private static SingleDog sd = null;
 * 【getInstance方法】
 *      需要判斷
 *          判斷SingleDog類型的靜態成員變量 sd是否保存有之前創建的空間首地址
 *
 * 【問題】
 *      多線程情況下,存在線程搶佔問題,極有可能導致當前方法被兩個線程同時執行,同時
 *      創建對象
 *      com.qfedu.a_single.SingleDog@22673956
 *      com.qfedu.a_single.SingleDog@6223c513
 *
 * 【期望】
 *      在多線程情況下,也是安全的
 * 【解決方案】
 *      加鎖
 *          【牆裂推薦】synchronized同步代碼塊
 *              使用同步代碼塊,用什麼做鎖對象?
 *              最合適的鎖對象依然是Single.class
 *
 *          【牆裂推薦】synchronized同步方法
 *              static修飾靜態方法的情況下,什麼是鎖對象?
 *                  當前SingleDog類.class字節碼文件
 *                  
 *          【不推薦】Lock鎖對象
 *              和以上兩個方式對比?
 *              new ==> lock ==> 多出一個Lock鎖空間
 *              (1) 在getInstance方法new lock是不合適,多個線程情況下
 *              可能會出現不同鎖情況
 *              (2) Lock做成靜態成員變量
 *                  浪費空間了!!!
 *
 *
 * @author Anonymous 2020/3/13 10:53
 */
public class SingleDog {
    private static SingleDog sd = null;

    private SingleDog() {}
	
    // 同步方法
    public static synchronized SingleDog getInstance() {
        if (null == sd) {
            sd = new SingleDog();
        }

        return sd;
    }
}

public class SingleDog1 {
    private static SingleDog1 sd = null;

    private SingleDog1() {}

    // 同步代碼塊
    public static  SingleDog1 getInstance() {
        synchronized (SingleDog1.class) {
        	if (null == sd) {
        	    sd = new SingleDog1();
        	}
    	}
    
        return sd;
    }
}
package com.qfedu.a_single;

/**
 * 另一個單例模式
 *
 * @author Anonymous 2020/3/13 11:44
 */
public class SingleCat {
    /*
    static修飾,在代碼的加載階段創建完成
    並且使用final修飾,保存當前指向不可以改變
    private修飾類外無法直接獲取,不能修改
     */
    private static final SingleCat sc = new SingleCat();

    private SingleCat() {}

    /*
    使用方法做的統一
     */
    public static SingleCat getInstance() {
        return sc;
    }
}

2. NIO

2.1 BIO概述

BIO
	BIO ==> Basic IO (基本IO), Block IO(阻塞IO)
	Scanner操作,文件讀寫操作,Socket數據傳輸操作... 都是BIO
	
	比如TPC羣聊,私聊聊天室
		Socket涉及到的IO,也是BIO
		資源浪費:
			1. 多線程,每一個Socket會對應一個線程,如果用戶量巨大,會導致線程過
			多,資源處理過多
			2. 採用阻塞狀態,一旦進入阻塞,代碼無法執行其他操作。
			3. 承載量一般,吞吐量比較小,同時可靠性不佳

2.2 NIO概述

NIO
	NIO ==> New IO(新IO), Non-Block IO(非阻塞IO)
	NIO非阻塞IO,運行當前程序在處理IO事務時,不會影響其他程序的運行,可以在不使用多線程的情況下,滿足IO操作要求。
	三大核心部分:
		通道
			Channel
			文件操作,網絡數據傳遞操作使用的通道
		緩衝
			Buffer
			緩衝使用可以提供操作效率,減少不必要的讀寫次數
		選擇器
			Selector
			真·核心 老大 boss

2.3 Buffer Channel完成文件操作

2.3.1 常用API

java.nio.Buffer
Buffer緩衝區
	ByteBuffer 字節緩衝 常用
	ShortBuffer
	IntBuffer
	LongBuffer 
	CharBuffer 字節緩衝 常用
	FloatBuffer
	DoubleBuffer

常用方法:
	public static ByteBuffer allocate(int capacity);
		按照指定的字節數分配對應的緩衝區空間,保存字節數據
	public byte get(); 
		從字節緩衝區對象中讀取一個byte類型數組
	public final Buffer flip();
		翻轉緩衝區,回到緩衝區的開始位置。
	public static ByteBuffer wrap(byte[] arr);
		存入一個byte類型數組到緩衝區,會得到一個新的ByteBuffer
	public static ByteBuffer put(byte[] b);
		將字節數組存入緩衝去
		

Channel接口,通道接口
	FileChannel 文件操作通道
	DatagramChannel UDP協議數據包操作的Channel
	ServerSocketChannel TCP服務端ServerSocket對應Channel
	SocketChannel TCP客戶端Socket對應Channel
首先操作文件,以FileChannel爲例
	public long read(ByteBuffer buffer);
		從通道中讀取數據到ByteBuffer中
	public long write(ByteBuffer buffer);
		從Buffer中寫數據到通道中
	public long transferFrom(ReadableByteChannel src, long position, long count) 
	從指定srcChannel中,指定位置position開始,讀取count個元素,到當前通道中
	文件複製操作。
	
	public long	transferTo(long position, long count, WritableByteChannel target) 
	將當前通道中的數據寫入到target中,從當前通道的position位置開始,計數count

2.3.2 操作文件數據

package com.qfedu.b_niofile;

import org.junit.Test;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
* @author Anonymous 2020/3/13 15:17
*/
public class FileNioTest {

   /*
   通過NIO寫入數據到文件中的操作
    */
   @Test
   public void testNioFileWrite() throws IOException {
       // 1. 文件操作字節輸出流
       FileOutputStream fos = new FileOutputStream("D:/aaa/1.txt");

       // 2. 利用文件操作輸出字節流對象獲取對應的Channel通道
       FileChannel foc = fos.getChannel();

       // 3. 準備一個緩衝區 4KB緩衝區
       ByteBuffer buffer = ByteBuffer.allocate(1024 * 4);

       // 4. 準備數據,放入緩衝區
       String str = "測試NIO";
       buffer.put(str.getBytes());

       // 5. 存在緩衝區數據放入之後,緩衝區指針發生改變,到達存入數據的末尾
       // 如果此時調用寫入操作,會從存入緩衝區之後開始保存
       // 讓緩衝區指針回到最初的起點,並且操作寫入程序,只會保存緩衝區內的有效數據
       buffer.flip();

       // 6. 緩衝區數據寫入到通道中
       foc.write(buffer);

       // 7. 關閉資源
       fos.close();
   }

   @Test
   public void testNioFileRead() throws IOException {
       // 1. 文件字節操作輸入流
       FileInputStream fis = new FileInputStream("D:/aaa/1.txt");

       // 2. FileChannel
       FileChannel fic = fis.getChannel();

       // 3. 準備緩衝
       ByteBuffer buffer = ByteBuffer.allocate(1024);

       // 4. 從Channel讀取數據保存到緩衝區中
       int read = fic.read(buffer);
       System.out.println(read);

       // 5. 展示數據
       // String(byte[] arr, int off, int count)
       System.out.println(new String(buffer.array(), 0, read));

       // 6. 關閉資源
       fis.close();
   }

   // 130
   @Test
   public void testCopyFile() throws IOException {
       long start = System.currentTimeMillis();
       // 1. 安排輸出流和輸入流
       FileInputStream fis = new FileInputStream("D:/aaa/1.mp4");
       FileOutputStream fos = new FileOutputStream("D:/aaa/2.mp4");

       // 2. 準備兩個Channel
       FileChannel srcChannel = fis.getChannel();
       FileChannel dstChannel = fos.getChannel();

       // 3. 拷貝方法
       srcChannel.transferTo(0, srcChannel.size(), dstChannel);
       // dstChannel.transferFrom(srcChannel, 0, srcChannel.size());

       // 4. 關閉資源
       fos.close();
       fis.close();

       long end = System.currentTimeMillis();
       System.out.println("Time:" + (end - start));
   }

   // 300
   @Test
   public void testCopyUseBuffer() throws IOException {
       long start = System.currentTimeMillis();
       BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/aaa/1.mp4"));
       BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:/aaa/3.mp4"));

       int length = -1;
       byte[] buf = new byte[4 * 1024];

       while ((length = bis.read(buf)) != -1) {
           bos.write(buf, 0, length);
       }

       bos.close();
       bis.close();
       long end = System.currentTimeMillis();
       System.out.println("Time:" + (end - start));
   }
}

2.4 網絡編程使用NIO【重點】

2.4.1 Selector選擇器老大

Selector
	選擇器,網絡編程使用NIO的大哥!!!
	服務器可以執行一個線程,運行Selector程序,進行監聽操作。
	新連接, 已經連接, 讀取數據,寫入數據

Selector常用方法:
	public static Selector Open();
		得到一個選擇器對象
	public int select(long timeout);
		監聽所有註冊通道,存在IO流操作是,會將對應的信息SelectionKey存入到內部的集
		閤中,參數是一個超時時間
	public Set<SelectionKey> selectionKeys();
		返回當前Selector內部集合中保存的所有SelectionKey

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sqtsK1TL-1584098459593)(day30.assets/Selector大哥.png)]

2.4.2 SelectionKey

SelectionKey
	表示Selector和網絡通道直接的關係
	int OP_ACCEPT; 16 需要連接
	int OP_CONNECT; 8  已經連接
	int OP_READ; 1  讀取操作
	int OP_WRITE; 4 寫入操作
SelectionKey
	public abstract Selector selector();
		得到與之關聯的 Selector 對象
	public abstract SelectableChannel channel();
		得到與之關聯的通道
	public final Object attachment();
		得到與之關聯的共享數據
	public abstract SelectionKey interestOps(int ops);
		設置或改變監聽事件
	public final boolean isAcceptable();
		是否可以 accept
	public final boolean isReadable();
		是否可以讀
	public final boolean isWritable();
		是否可以寫

2.4.3 ServerSocketChannel

ServerSocketChannel
	服務端Socket程序對應的Channel通道
常用方法:
	public static ServerSocketChannel open();
		開啓服務器ServerSocketChannel通道,等於開始服務器程序
	public final ServerSocketChannel bind(SocketAddress local);
		設置服務器端端口號
	public final SelectableChannel configureBlocking(boolean block);
		設置阻塞或非阻塞模式, 取值 false 表示採用非阻塞模式
	public SocketChannel accept();
		[非阻塞]
			獲取一個客戶端連接,並且得到對應的操作通道
	public final SelectionKey register(Selector sel, int ops);
		[重點方法]
			註冊當前選擇器,並且選擇監聽什麼事件

2.4.4 SocketChannel

SocketChannel
	客戶端Socket對應的Channel對象

常用方法:
	public static SocketChannel open();
		打卡一個Socket客戶端Channel對象
	public final SelectableChannel configureBlocking(boolean block)
		這裏可以設置是阻塞狀態,還是非阻塞狀態
		false,表示非阻塞
	public boolean connect(SocketAddress remote);
		連接服務器
	public boolean finishConnect();
		如果connect連接失敗,可以通過finishConnect繼續連接
	public int write(ByteBuffer buf);
		寫入數據到緩衝流中
	public int read(ByteBuffer buf);	、
		從緩衝流中讀取數據
	public final SelectionKey register(Selector sel, int ops, Object attechment);
		註冊當前SocketChannel,選擇對應的監聽操作,並且可以帶有Object attachment參數
	public final void close();
		關閉SocketChannel

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