為學不間斷
當如行云流水
不可以已
耐得住寂寞,經得起傭訓
自律是一個人最大的雄心
死鎖排查
什么是死鎖?
執行緒之間的相互等待對方鎖的釋放而卡住的情況稱之為死鎖,
比如:
有2把鎖,lockA,lockB
執行緒A:
lockA{
//業務邏輯
lockB{
//業務邏輯
}
}
執行緒B:
lockB{
//業務邏輯
lockA{
//業務邏輯
}
}
執行緒A等待鎖B的釋放,執行緒B等待鎖A的釋放,雙方都執行不下去就出現了死鎖的情況,
如何排查死鎖?
在控制臺執行 jps -l,找到我們的java行程,然后執行jstack 行程號,就可以找到死鎖,

如何避免死鎖
鎖的順序相同或者避免相同的鎖相互持有
阻塞
阻塞IO是常見的記憶體模型,在讀寫資料時客戶端會發生阻塞,阻塞IO模型的作業流程為:在用戶執行緒發出IO請求之后,內核會檢查資料是否就緒,此時用戶執行緒會一直阻塞等待內核執行緒的回應,在內核資料準備就緒之后,內核將資料復制到用戶執行緒中,并回傳I/O執行結果到用戶執行緒,此時用戶執行緒將解除阻塞狀態斌開始處理資料,典型的阻塞IO的例子為:data=socket.read().如果內核資料沒有準備就緒,socket就會一直在read()方法中阻塞等待,等待內核的就緒,

問題
用戶執行緒的阻塞
非阻塞
非阻塞IO指用戶執行緒發起一個IO操作后,無須阻塞便可以馬上得到內核的回傳的一個結果,如果內核回傳的false,則代表內核還沒有準備好,需要稍后再次發送IO請求,一旦內核準備好了,并且再次收到用戶執行緒的親戚,內核就會立刻將資料復制到用戶執行緒中并復制的結果通知用戶執行緒,
用戶執行緒需要不間斷的詢問內核是否準備就緒,如果沒有準備就緒內核資料可以執行其他的操作,準備就緒后就可以立即獲取資料并進行相應的操作,

問題:
需要多次訪問內核狀態,浪費IO資源
多路復用IO
非阻塞情況下無可用資料時,應用程式每次輪詢內核看資料是否準備好了也耗費CPU,能否不讓它輪詢,當內核緩沖區資料準備好了,以事件通知當機制告知應用行程資料準備好了呢?應用行程在沒有收到資料準備好的事件通知信號時可以忙些其他的作業,此時 IO多路復用就派上用場了,
在多路復用IO模型當中,有個一個被稱之為selector的執行緒不斷的輪詢socket的狀態,只有socket有讀寫事件的時候,才會通知用戶執行緒進行IO的讀寫,
因為在多路復用IO模型中只需要一個selector管理多個socket執行緒(不必每個socket都進行執行緒監聽)因此大大的節約了系統的資源,

問題:
當單個socket執行緒處理資訊大時,容易引起socket的執行緒的資料堆積,導致資源的堆積
信號驅動IO
在信號驅動IO模型中,在用戶發起一個IO請求時,系統會為該請求對應的socket注冊一個信號函式,在內核資料準備就緒時,系統會發送一個信號到用戶執行緒,用戶執行緒在接收到該信號,會在信號函式中呼叫對應的I/O讀寫操作完成實際的I/O操作.

問題:
用戶執行緒接收到內核信號后,需要用戶呼叫IO函式進行實際的IO讀寫操作,將資料讀取到用戶執行緒,并不是異步的,是需要一步一步同步執行,
異步I/O模型
異步執行緒模型就是解決信號驅動I/O模型中的同步問題,流程為以下:
用戶執行緒會發起一個asynchronous read操作到內核,內核在接收到asynchronous read請求后會立刻回傳一個狀態,來說明請求是否成功發起,在此程序中用戶執行緒不會發起任何阻塞,接著,內核會等待資料準備完成并將資料復制到用戶執行緒中,在資料復制完成后內核會發一個訊息信號到用戶執行緒,通知用戶執行緒asynchronous讀操作已經完成,

在整個異步IO中,用戶執行緒不需要取關心資料讀取和如何作業的,只要判斷信號是否完成就行,因此不會阻塞執行緒,
同步
同步跟異步的區別在于資料從內核空間拷貝到用戶空間是否由用戶執行緒完成,這里又分為同步阻塞跟同步非阻塞兩種,
- 同步阻塞:此時一個執行緒維護一個連接,該執行緒完成資料到讀寫跟處理到全部程序,資料讀寫時時執行緒是被阻塞的,
- 同步非阻塞:非阻塞的意思是用戶執行緒發出讀請求后,讀請求不會阻塞當前用戶執行緒,不過用戶執行緒還是要不斷的去主動判斷資料是否準備OK了,此時還是會阻塞等待內核復制資料到用戶行程,他與同步BIO區別是使用一個連接全程等待
同步IO流程:

異步
對于異步來說,用戶進行讀或者寫后,將立刻回傳,由內核去完成資料讀取以及拷貝作業,完成后通知用戶,并執行回呼函式(用戶提供的callback),此時資料已從內核拷貝到用戶空間,用戶執行緒只需要對資料進行處理即可,不需要關注讀寫,用戶不需要等待內核對資料的復制操作,用戶在得到通知時資料已經被復制到用戶空間,我們以如下的真實異步非阻塞為例,

JAVA NIO
JAVA NIO基于多路復用,主要實作有三大核心內容:Selector(選擇器),Channel(通道),Buffer(緩沖區),
Selector:用于監聽多個Channel事件,打開或資料到達,一個執行緒可以管理多個資料Channel的事件,
Channel:雙向流,可以用于讀也可以用于寫,NIO實作主要有:FileChannel,DatagramChannel,SocketChannel,ServerSocketChannel,分別對應的檔案是:IO,UDP,TCP I/O,Socket Client和Socker Server操作,
Buffer: Buffer實際上是一個容器,其內部通過一個連續的位元組陣列存盤I/O上的資料,在NIO中,Channel在檔案、網路上對資料的讀取或寫入都必須經過Buffer,

JAVA NIO和傳統IO的最大區別如下:
1)IO是面向流的,NIO是面向快取區的:在面向流的操作中,資料只能在一個流中連續進行讀寫,資料沒有快取區,因此位元組無法前后移動,而在NIO中每次都是將資料從一個Channel讀取到一個Buffer中,再從Buffer中寫入Channel中,因此可以進行前后移動,該功能在應用層主要用于資料的粘包,拆包等操作,在網路不可靠的環境尤為重要,
2) 傳統IO是流操作是阻塞模式的,NIO是非阻塞的,在傳統IO,read()或write()進行讀寫操作時,該執行緒將一直被阻塞,知道資料完全讀取或完全寫入,NIO是通過selector監聽Channel事件的變化,在資料發送變化時通知該執行緒進行讀寫操作,
對于讀來說:在buffer沒有資料的時候,監聽狀態沒有變化,執行緒可以執行其他業務操作,對于操作而言,在使用一個執行緒執行操作時,只需要將Channel的值寫入Buffer即可,用戶執行緒不需要等待整個資料完全寫入channel就可以執行其他業務操作了,
NIO樣例:
public class NIOBase {
?
// 執行緒中的通道管理器
public Selector selector;
public String from,to;//server or client
?
public NIOBase(String from,String to){
this.from = from;
this.to = to;
}
?
/**
* 初始化 該執行緒中的通道管理器Selector
*/
public void initSelector() throws IOException {
this.selector = Selector.open();
}
?
?
/**
* 采用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則回圈處理
* 這里主要監聽連接事件以及讀事件
*/
public void listen() throws IOException {
//輪詢訪問select
while(true){
//當注冊的事件到達時,方法回傳;否則將一直阻塞
selector.select();
//獲得selector中選中的項的迭代器,選中的項為注冊的事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
//回圈處理注冊事件
/**
* 一共有四種事件:
* 1. 服務端接收客戶端連接事件: SelectionKey.OP_ACCEPT
* 2. 客戶端連接服務端事件: SelectionKey.OP_CONNECT
* 3. 讀事件: SelectionKey.OP_READ
* 4. 寫事件: SelectionKey.OP_WRITE
*/
while(iterator.hasNext()){
SelectionKey key = iterator.next();
//手動洗掉已選的key,以防重復處理
iterator.remove();
//判斷事件性質
if (key.isAcceptable()){//服務端接收客戶端連接事件
accept(key);
}else if (key.isReadable()){//讀事件
read(key);
}else if (key.isConnectable()) {//客戶端連接事件
connect(key);
}
}
}
}
?
/**
* 當監聽到讀事件后的處理函式
* @param key 事件key,可以從key中獲取channel,完成事件的處理
*/
public void read(SelectionKey key) throws IOException {
//step1. 得到事件發生的通道
SocketChannel socketChannel = (SocketChannel) key.channel();
?
//step2. 創建讀取的緩沖區.將資料讀取到緩沖區中
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
int len = socketChannel.read(byteBuffer);
String msg = "";
byte[] arr = null;
while (len > 0){
byteBuffer.flip();
arr = new byte[len];
byteBuffer.get(arr,0,len);
msg += new String(arr);
byteBuffer.clear();
len = socketChannel.read(byteBuffer);
}
System.out.println(from + " received data from " + to + ":" + msg);
//step3. 再將訊息回發給客戶端
if (from.equals("server"))socketChannel.write(ByteBuffer.wrap(new String(" server send some data back to client").getBytes()));
}
?
/**
* 當監聽到服務端接收客戶端連接事件后的處理函式
* @param key 事件key,可以從key中獲取channel,完成事件的處理
*/
public void accept(SelectionKey key) throws IOException{}
?
/**
* 當監聽到客戶端連接事件后的處理函式
* @param key 事件key,可以從key中獲取channel,完成事件的處理
*/
public void connect(SelectionKey key) throws IOException{}
}
服務端執行緒類:
/**
* 采用NIO的方式,開啟服務執行緒
* 該執行緒存在一個Selector,通道管理器,管理所有的channel
* step1.服務初始化時,會初始化一個ServerSocektChannel,并注冊到Selector中,注冊"服務端接收客戶端連接"事件
*
* step2.之后開啟監聽,輪詢判斷Selector上是否有需要處理的事件,如果有則回圈處理;
*
* step2.1 客戶端連接事件:在處理的程序中,獲取與客戶端連接的通道 socketChannel,并注冊到Selector中,通過該通道,與客戶端進行讀寫操作
*
* step2.2 讀事件,利用讀取緩沖區與通道結合進行
*/
public class NIOServerThread extends NIOBase implements Runnable{
?
public NIOServerThread(String from, String to) {
super(from, to);
}
?
//服務端執行緒中的通道管理器,使用它,可以在同一個執行緒中管理多個通道
?
?
?
@Override
public void run() {
try {
initSelector();//初始化通道管理器Selector
initServer(Constant.IP,Constant.PORT);//初始化ServerSocketChannel,開啟監聽
listen();//輪詢處理Selector選中的事件
} catch (IOException e) {
e.printStackTrace();
}
?
}
?
?
/**
* 獲得一個ServerSocket通道,并通過port對其進行初始化
* @param port 監聽的埠號
*/
private void initServer(String ip,int port) throws IOException {
//step1. 獲得一個ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
?
//step2. 初始化作業
serverSocketChannel.configureBlocking(false);//設定通道為非阻塞
serverSocketChannel.socket().bind(new InetSocketAddress(ip,port));
?
//step3. 將該channel注冊到Selector上,并為該通道注冊SelectionKey.OP_ACCEPT事件
//這樣一來,當有"服務端接收客戶端連接"事件到達時,selector.select()方法會回傳,否則將一直阻塞
serverSocketChannel.register(this.selector,SelectionKey.OP_ACCEPT);
}
?
?
/**
* 當監聽到服務端接收客戶端連接事件后的處理函式
* @param key 事件key,可以從key中獲取channel,完成事件的處理
*/
@Override
public void accept(SelectionKey key) throws IOException {
?
//step1. 獲取serverSocketChannel
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
?
//step2. 獲得和客戶端連接的socketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);//設定為非阻塞
?
//step3. 通過socketChannel給客戶端發送資訊
socketChannel.write(ByteBuffer.wrap(new String("server has a connection with client").getBytes()));
?
//step4. 注冊該socketChannel
socketChannel.register(selector,SelectionKey.OP_READ);//為了接收客戶端的訊息,注冊讀事件
}
}
客戶端執行緒類:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
?
/**
* NIO 客戶端執行緒
*/
public class NIOClientThread extends NIOBase implements Runnable{
?
?
public NIOClientThread(String from, String to) {
super(from, to);
}
?
@Override
public void run() {
try {
initSelector();//初始化通道管理器
initClient(Constant.IP,Constant.PORT);//初始化客戶端連接scoketChannel
listen();//開始輪詢處理事件
} catch (IOException e) {
e.printStackTrace();
}
}
?
/**
* 獲得一個SocketChannel,并對該channel做一些初始化作業,并注冊到
* @param ip
* @param port
*/
private void initClient(String ip,int port) throws IOException {
//step1. 獲得一個SocketChannel
SocketChannel socketChannel = SocketChannel.open();
?
//step2. 初始化該channel
socketChannel.configureBlocking(false);//設定通道為非阻塞
?
?
//step3. 客戶端連接服務器,其實方法執行并沒有實作連接,需要再listen()方法中呼叫channel.finishConnect()方法才能完成連接
socketChannel.connect(new InetSocketAddress(ip,port));
?
//step4. 注冊該channel到selector中,并為該通道注冊SelectionKey.OP_CONNECT事件和SelectionKey.OP_READ事件
socketChannel.register(this.selector,SelectionKey.OP_CONNECT|SelectionKey.OP_READ);
}
?
/**
* 當監聽到客戶端連接事件后的處理函式
* @param key 事件key,可以從key中獲取channel,完成事件的處理
*/
@Override
public void connect(SelectionKey key) throws IOException {
super.connect(key);
//step1. 獲取事件中的channel
SocketChannel socketChannel = (SocketChannel) key.channel();
?
//step2. 如果正在連接,則完成連接
if (socketChannel.isConnectionPending()){
socketChannel.finishConnect();
}
socketChannel.configureBlocking(false);//將連接設定為非阻塞
//step3. 連接后,可以給服務端發送訊息
socketChannel.write(ByteBuffer.wrap(new String("client send some data to server").getBytes()));
?
}
}
常量類:
public class Constant {
public static final int PORT = 8080;
public static final String IP = "127.0.0.1";
}
運行主類:
public class NIOMain {
public static void main(String[] args) {
?
new Thread(new NIOServerThread("server","client")).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new NIOClientThread("client","server")).start();
?
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/293598.html
標籤:java
