第25天多线程、网络编程
多線程、網絡編程
JDK5的鎖和等待喚醒
Lock接口介紹
在JDK5之前,同步和鎖綁定在一起,同時鎖和等待喚醒也綁定在一起。在前學習同步代碼塊或同步方法的時候,線程要進入同步首先需要隱式的獲取同步上的鎖對象。只有持有鎖對象的線程才能進入到同步中。線程出同步的時候會隱式釋放鎖。鎖的獲取和釋放我們并無法操作。
?
到JDK5之后,將同步上的鎖的獲取和釋放修改為由編程人員自己控制。升級后將鎖單獨的封裝在Lock接口中。
?
?
JDK5中的Lock接口,它其實是在代替JDK5之前的同步方法或同步代碼塊。
?
將JDK5之前的隱式獲取鎖,修改為手動獲取:
同時將隱式釋放鎖,也修改為手動操作:
?
????如果使用JDK5的Lock接口,要求必須使用下面的模版代碼:
????
手動調用lock方法獲取鎖
try{
????????書寫的被同步的代碼
}finally{
????手動的調用unlock方法釋放鎖
}
Lock接口使用
/*
* 演示使用JDK5的Lock接口完成線程的同步
*/
class Ticket implements Runnable{
????
????private int num = 100;
????
????// 定義Lock接口的對象
????private Lock loc = new ReentrantLock();
????public void run(){
????????while(true){
????????????// 手動獲取鎖
????????????loc.lock(); // 從這里開始的代碼就會被同步
????????????try{
????????????????if( num > 0 ){
????????????????????System.out.println(Thread.currentThread().getName()+"....."+num);
????????????????????num--;
????????????????}
????????????}finally{
????????????????// 手動釋放鎖
????????????????loc.unlock(); // 同步結束
????????????}
????????}
????}
}
public class ThreadTest {
????public static void main(String[] args) {
????????
????????Ticket task= new Ticket();
????????
????????Thread t = new Thread( task );
????????Thread t2 = new Thread( task );
????????
????????t.start();
????????t2.start();
????}
}
Condition接口介紹
?
Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象,以便通過將這些對象與任意 Lock 實現組合使用,為每個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition 替代了 Object 監視器方法的使用。
?
wait方法被下面方法代替
?
notify 被下面方法代替
?
notifyAll:
Condition接口使用
/*
* 單生產單消費 使用JDK5中的Lock接口和Condition接口替換
*/
// 專門定義一個負責描述保存和取出線程的操作的資源類
class Resource {
????
????// 定義成員變量,充當保存和取出的容器
????private Object[] objs = new Object[1];
????
????// 定義JDK5中的Lock接口對象
????private Lock loc = new ReentrantLock();
????
????// 定義監視保存線程的等待和喚醒對象
????private Condition pro_con = loc.newCondition();
????// 定義監視取消線程的等待和喚醒對象
????private Condition con_con = loc.newCondition();
????// 提供一個保存數據的方法
????public void save( Object obj ) {
????????loc.lock();
????????try{
????????????// 判斷能否操作容器
????????????if( objs[0] != null ){
????????????????// 判斷成立 說明保存的線程不能保存,需要等待
????????????????try { pro_con.await(); } catch( InterruptedException e ){ }
????????????}
????????????// 將數據保存到容器中
????????????objs[0] = obj;
????????????System.out.println(Thread.currentThread().getName()+" 正在保存的數據是:"+objs[0]);
????????????con_con.signal();
????????}finally{
????????????loc.unlock();
????????}
????????
????}
????
????// 提供一個取出數據的方法
????public void get(){
????????loc.lock();
????????try{
????????????if( objs[0] == null ){
????????????????// 判斷成立,說明沒有數據,不能取出,需要等待
????????????????try { con_con.await(); } catch( InterruptedException e ){ }
????????????}
????????????// 打印取出的數據
????????????System.out.println(Thread.currentThread().getName() + " 正在取出的數據::::" +objs[0]);
????????????objs[0] = null;
????????????// 消費結束,需要通知生產者線程
????????????pro_con.signal();
????????}finally{
????????????loc.unlock();
????????}
????}
}
?
// 書寫保存數據線程的任務
class Productor implements Runnable {
?
????private Resource r ;
????// 書寫構造方法的目的是在明確線程的任務的時候,
????// 可以通過構造方法告訴線程的任務它們操作的資源對象
????public Productor( Resource r){
????????this.r = r ;
????}
????public void run() {
????????// 給資源中保存數據
????????while(true){
????????????r.save( "蘋果" );
????????}
????}
}
?
// 書寫取出數據的線程任務
class Consumer implements Runnable {
????
????private Resource r ;
????// 書寫構造方法的目的是在明確線程的任務的時候,
????// 可以通過構造方法告訴線程的任務它們操作的資源對象
????public Consumer( Resource r){
????????this.r = r ;
????}
????public void run() {
????????// 從資源中取出數據
????????while(true){
????????????r.get();
????????}
????}
}
?
// 測試類
public class ThreadTest {
????public static void main(String[] args) {
?
????????// 先要創建一個保存和取出操作的共同的資源對象
????????Resource r = new Resource();
????????
????????// 創建線程的任務對象
????????Productor pro = new Productor( r );
????????Consumer con = new Consumer( r );
????????
????????// 創建線程對象 ,這個線程負責保存數據
????????Thread t = new Thread( pro );
????????
????????// 創建線程對象 ,這個線程負責取出數據
????????Thread t2 = new Thread( con );
????????
????????// 開啟線程
????????t.start();
????????t2.start();
????}
}
多線程的細節
wait和sleep區別
wait:需要被喚醒,sleep時間到自然醒
wait:它只能放在同步中,sleep可以在同步中,也可以不在。
wait:可以指定時間,但大部分情況我們是不指定時間,而sleep必須指定時間
wait:它讓線程等待之后,線程會將鎖是否,而sleep如果在同步中,它讓線程休眠,這時線程是不會放鎖。
同步能不能添加在run方法上
按照語法規則,是可以在run方法上書寫同步的。但是實際開發中是不可能在run方法上添加同步的。
同步方法在線程執行的時候,每個線程要進入這個方法都需要獲取同步鎖,如果獲取不到鎖,線程就無法去執行這個方法。而我們知道run方法是線程要執行的任務方法,如果線程都進不去run方法,相當于線程根本就沒有拿到自己的任務。
線程組
線程組:將多個操作行為相同的線程,可以劃分到一個組中,然后我們不要去面對每個線程,而只要通過這個組操作組里面的所有線程。
?
如何將線程添加到線程組中:
在創建Thread對象的時候,可以根據Thread的構造方法將線程添加到對應的線程組中。
線程優先級
Thread類中的API:每個線程都有一個優先級,高優先級線程的執行優先于低優先級線程。
?
我們在創建線程的時候,可以指定每個線程的優先級,如果沒有指定的話,這個線程優先級默認和執行創建線程時所在的線程優先級相同。主線程的優先級默認為5.
?
線程的優先級: 1 ~ 10 ,一般建議如果需要設計線程的優先級設置為 1 或 5 或 10。
修改線程的優先級:
獲取線程的優先級:
????
????
????
????注意:高優先級的線程被CPU執行的概率會被低優先級的高,但是并不意味著低優先級的線程CPU不執行。
守護線程
守護線程:每個線程都可以或不可以標記為一個守護程序。當且僅當創建線程是守護線程時,新線程才是守護程序。
前臺線程:從主程序開始執行的那個線程一定是前臺線程。主線程屬于前臺線程(非守護線程)。
守護線程也被稱為后臺線程,或者被稱為用戶線程,這些線程它們依然可以去運行線程的任務,但是如果程序中的前臺線程全部結束,這時不管后臺(守護)線程的任務是否結束,守護線程會自動停止運行。
?
?
/*
* 演示守護線程
*/
class Demo implements Runnable{
????
????public void run(){
????????for( int i = 0 ; ; i++){
????????????System.out.println(Thread.currentThread().getName()+"...."+i);
????????}
????}
}
?
public class TheradDemo {
????public static void main(String[] args) {
????????// 執行main方法的線程,一定是前臺線程
????????
????????Demo d = new Demo();
????????
????????// 下面創建的線程和主線程都屬于前臺線程
????????Thread t = new Thread(d);
????????Thread t2 = new Thread(d);
????????
????????// 人為的將創建出來的線程修改為后臺(守護線程),一定要在開啟線程之前修改
????????t.setDaemon(true);
????????t2.setDaemon(true);
????????
????????t.start();
????????t2.start();
????????
????????for( int i = 0 ; i < 20 ; i++){
????????????System.out.println(Thread.currentThread().getName()+"-----"+i);
????????}
????}
}
定時器介紹
定時器:讓設備可以定時的去(重復)執行某個功能。
????
定時器類:
????
????
????構造方法:
????
????
????下面的方法是給Timer(定時器)對象綁定任務
????
?
????/*
* 演示定時器
*/
public class TimerDemo {
????public static void main(String[] args) {
????????
????????// 創建定時器對象
????????Timer t = new Timer();
????????
????????/*
???????? * 給定時器指定任務
???????? * schedule(TimerTask task, long delay, long period)
???????? * TimerTask task : 定時器要執行的任務
???????? * long delay : 當前時間往后延時多久,開始執行任務
???????? * long period : 到執行任務的時間點之后,每隔多久時間重復執行一次任務
???????? */
????????final Random r = new Random();
????????t.schedule( new TimerTask(){
????????????@Override
????????????public void run() {
????????????????// 在run方法中書寫具體Java代碼完成定時器需要做事情
????????????????System.out.println("你好");
????????????????File file = new File("e:/abc/"+r.nextInt()+".txt");
????????????????try {
????????????????????file.createNewFile();
????????????????} catch (IOException e) {
????????????????????e.printStackTrace();
????????????????}
????????????}
????????}, 1000, 1000);????????
????}
}
網絡介紹
網絡介紹(了解)
網絡:通過一些中間的設備,可以將終端設備連接在一起,并且這些終端設備之間可以進行數據的交互。
?
中間設備:網線、交換機、路由器、無線設備、衛星等。
?
終端設備:電腦、服務器、手機、智能家電等。
?
網絡分成:局域網、城域網、廣域網。
?
網絡模型介紹(了解)
由于網絡中的設備太多,于是將工作在網絡中的不同設備功能(職責)進行劃分,將網絡化成七層(網絡模型):
OSI(Open System Interconnection開放系統互連)參考模型 。
1.層物理層:主要定義物理設備標準,如網線的接口類型、光纖的接口類型、各種傳輸介質的傳輸速率等。它的主要作用是傳輸比特流(就是由1、0轉化為電流強弱來進行傳輸,到達目的地后在轉化為1、0,也就是我們常說的數模轉換與模數轉換)。這一層的數據叫做比特。 2.層數據鏈路層:主要將從物理層接收的數據進行MAC地址(網卡的地址)的封裝與解封裝。常把這一層的數據叫做幀。在這一層工作的設備是交換機,數據通過交換機來傳輸。 3.層網絡層:主要將從下層接收到的數據進行IP地址(例192.168.0.1)的封裝與解封裝。在這一層工作的設備是路由器,常把這一層的數據叫做數據包。 4.層傳輸層:定義了一些傳輸數據的協議和端口號(WWW端口80等),如:TCP(傳輸控制協議,傳輸效率低,可靠性強,用于傳輸可靠性要求高,數據量大的數據),UDP(用戶數據報協議,與TCP特性恰恰相反,用于傳輸可靠性要求不高,數據量小的數據,如QQ聊天數據就是通過這種方式傳輸的)。 主要是將從下層接收的數據進行分段和傳輸,到達目的地址后再進行重組。常常把這一層數據叫做段。 5.會話層:通過傳輸層(端口號:傳輸端口與接收端口)建立數據傳輸的通路。主要在你的系統之間發起會話或者接受會話請求(設備之間需要互相認識可以是IP也可以是MAC或者是主機名) 6.表示層:主要是進行對接收的數據進行解釋、加密與解密、壓縮與解壓縮等(也就是把計算機能夠識別的東西轉換成人能夠能識別的東西(如圖片、聲音等)。 7.應用層: 主要是一些終端的應用,比如說FTP(各種文件下載),WEB(IE瀏覽),QQ之類的(可以把它理解成我們在電腦屏幕上可以看到的東西.就是終端應用)。
網絡三要素(重點掌握)
IP介紹(重點)
IP:它本質是連接在網絡中那個終端設備的一個數字標識。如果我們需要進行網絡通信,這時我們就必須知道通信對方的IP地址是多少,才能和對方進行通信。
?
IP地址:
????它被分成5類:A、B、C、D、E。
?
平時我們上網的時候,從來不手動設置IP地址,這時IP地址由寬帶提供商提供。
?
如何查詢自己電腦的IP地址:
?
IP地址:它是連接在網絡中終端設備的唯一的數字標識。
MAC地址:每個網卡都有一個網卡地址,這個地址也被稱為物理地址。每個網卡在生產的時候就已經固定死(全球唯一)。
網卡是設備的物理標識,它不容易記憶,也不容器操作。但是我們的確可以通過網卡地址找到需要通信的設備。但是實際應用中,程序不會根據網卡地址通信。
IP地址,邏輯地址:它可以分配給任何一個連接在網絡中的設備。
?
網卡地址和IP地址:
????網卡地址:可以理解成手機的唯一編號(*#06#)。
????IP地址:可以理解成手機號碼。
?
?
了解的內容:其實每個網卡在出廠的時候已經分配了一個IP地址。
這個地址:本地回環地址。127.0.0.1
域名解析(了解)
IP地址是我們唯一訪問網絡中設備的路徑。平時我們上網的時候根本沒有使用IP地址訪問任何網絡中的信息。
平時在瀏覽器的地址欄中輸入域名(www.baidu.com),通過域名訪問網絡中的資源數據。
?
當我們在瀏覽器的地址欄中輸入域名之后,其實背后對應的將域名解析成對應的IP地址,最后還是在使用IP地址訪問。
?
關于域名解析成IP地址:域名解析
????1、先通過操作系統本地的hosts文件中查詢當前的域名有沒有對應的IP地址,如果有就會使用hosts文件中的IP,作為當前域名的對應的IP地址訪問。
????????C:\Windows\System32\drivers\etc\hosts
????????
????2、如果第一步解析IP地址失敗,這時系統會自動到DNS服務器上解析。DNS服務器上會有全球所有的網站對應的IP地址。查詢到域名對應的ip地址,就會使用這個ip地址訪問。如果DNS服務器解析失敗,這時說明域名有問題。
端口介紹(重點)
通過IP地址,可以訪問到連接在網絡中的某個終端設備。設備中肯定會運行很多的應用程序(軟件),最終我們需要和設備中的某個應用軟件進行數據交互。
?
運行在終端設備中的任何應用軟件,它們在這個設備中都有一個唯一的標識,這個標識我們稱為軟件在此設備中的端口號。
?
端口號:從0~65535之間,一般0~1024之間的端口號,是給系統軟件使用的時候,因此我們自己編程中,開發的程序需要綁定端口的時候,建議使用1025~65535之間的端口號。
?
總結:
????IP:它本質是網絡中的電腦的標識。
????端口:電腦中運行的軟件的標識。
協議介紹(重點)
協議:雙方需要共同遵守的規則。在網絡編程中,協議是在規范通信雙方需要遵守的數據交互格式。
我們學習的網絡編程中的協議,主要是OSI模型中傳輸層協議(底層協議):
????
UDP協議:用戶數據報文包協議。
????????通信雙方不同關心對方是否在接收數據。發送方只管發送數據,接收方如果在,就可以接收到數據,如果不在,數據就會被丟棄。
????????UDP協議,傳輸快,效率高,但不安全。不能發送大數據。
????????UDP協議,一般用在實時聊天等程序底層。
?
????TCP協議:傳輸控制協議。
????????通信的雙方,必須經常三次握手,建立數據交互的通道。然后雙方在這個通道中進行數據傳遞。如果有一方斷開通道,這時通道就被破壞,無法在繼續通信。
????TCP協議:它主要用在對數據安全性要求較高的軟件底層。傳遞慢,效率低,但安全。
?
總結:最終我們通過網絡通信,需要上門介紹的三個要素:
協議:雙方通信的規則
ip:對方的設備標識
端口:對方設備中運行的軟件標識
網絡編程
IP對象(理解+編碼)
連接在網絡中的任何設備都有一個IP地址。因此Java也將IP地址封裝成一個對象,如果我們在編程中需要操作IP地址,可以直接使用InetAddress對象完成。
Java中的網絡編程相關的類和接口都在java.net包下:
?
Ipv4:它采用的4個數字作為ip地址。每個數字范圍0~255之間。它也是IP的第四個版本。
Ipv6:它是IP地址的第六個版本。它采用的是八個數字作為ip地址。首選形式為 x:x:x:x:x:x:x:x,其中這些 'x' 是八個 16 位地址段的十六進制值。
?
InetAddress類它沒有對外提供構造方法,只能通過其中的靜態方法獲取對象:
?
/*
* 演示InetAddress對象
*/
public class IPDemo {
????public static void main(String[] args) throws UnknownHostException {
????????
????????method2();
????}
?
????public static void method2() throws UnknownHostException {
????????
????????InetAddress[] ints = InetAddress.getAllByName("www.sina.com.cn");
????????
????????for (InetAddress inet : ints) {
????????????System.out.println(inet.getHostAddress());
????????}
????}
????public static void method1() throws UnknownHostException {
????????/*
???????? * 使用靜態方法獲取IP對象
???????? * getByName(String host)
???????? * String host: 表示是需要封裝的IP地址,
???????? * 字符串可以書寫:IP地址、 主機名、域名
???????? */
????????InetAddress inet = InetAddress.getByName("www.baidu.com");
????????
????????// 獲取IP地址 getHostAddress()
????????String ip = inet.getHostAddress();
????????/*
???????? * 獲取主機名
???????? * getHostName() 有時無法解析出對方的主機名稱時,這個值就會變成IP地址
???????? */
????????String name = inet.getHostName();
????????System.out.println(ip+"...."+name);
????}
}
Socket對象(理解)
Socket : 網絡套接字。
?
UDP協議編程
DatagramSocket對象(理解)
DatagramSocketL:它是用來發送和接收數據報包的通信端點(終端設備)對象。
?
?
DatagramPacket對象(理解)
DatagramPacket:它相當于打包或拆包的對象。
UDP發送端(重點理解+編碼)
?
/*
* 簡單演示基于UDP協議發送方實現
*/
public class UdpSendDemo {
????public static void main(String[] args) throws Exception {
????????
????????// 創建發送方對象
????????DatagramSocket ds = new DatagramSocket(9191);
????????
????????/*
???????? * 創建打包對象
???????? * DatagramPacket(byte[] buf, int length, InetAddress address, int port)
???????? * byte[] buf : 被發送的數據
???????? * int length : 發送的數據的長度
???????? * InetAddress address : 接收方的IP地址對象
???????? * int port : 接收方的端口
???????? */
????????byte[] buf = "有人犯困啦!UDP肯定掛掉!!!".getBytes();
????????int length = buf.length;
????????InetAddress address = InetAddress.getByName("192.168.49.40");
????????int port = 9292;
????????DatagramPacket dp = new DatagramPacket( buf , length , address , port);
????????
????????// 發送數據
????????ds.send(dp);
????????
????????// 關閉發送方
????????ds.close();
????}
}
UDP接收端(重點理解+編碼)
/*
* 簡單實現基于UDP協議的接收方
*/
public class UdpReceDemo {
????public static void main(String[] args) throws Exception {
????????
????????// 創建接收方對象
????????DatagramSocket ds = new DatagramSocket(9292);
????????
????????/*
???????? * 創建拆包對象
???????? * DatagramPacket(byte[] buf, int length)
???????? * byte[] buf : 臨時用于存儲接收到的數據的容器
???????? * int length : 臨時用于存儲數據的場地大小
???????? */
????????DatagramPacket dp = new DatagramPacket( new byte[1024] , 1024 );
????????
????????// 接收數據
????????ds.receive(dp);
????????
????????// 獲取接收到的數據
????????byte[] buf = dp.getData();
????????// 接收到的數據長度
????????int length = dp.getLength();
????????// 獲取發送方的ip
????????String ip = dp.getAddress().getHostAddress();
????????// 獲取發送方的端口號
????????int port = dp.getPort();
????????System.out.println(ip+".."+port + ", 數據是:"+ new String( buf , 0 , length ));
????????
????????// 關閉接收方
????????ds.close();
????}
}
UDP練習
需求:
發送方鍵盤錄入字符串數據,將字符串數據發送給接收方,接收方接收到之后,統計出其中字母字符的個數,然后將結果發送給發送方。
?
發送端(編碼實戰)
?
/*
* 發送方:
* ????1、鍵盤錄入字符串數據,
* ????2、將數據發送給接收方
* ????3、接收 接收方發來的結果數據
*/
public class UdpSendTest {
????public static void main(String[] args) throws IOException {
?
????????// 創建發送方對象
????????DatagramSocket ds = new DatagramSocket(9393);
????????
????????// 創建Scanner對象
????????Scanner sc = new Scanner( System.in );
????????System.out.println("請輸入數據:");
????????String line = sc.nextLine();
????????
????????// 創建打包對象
????????DatagramPacket send_dp = new DatagramPacket(
???????????????? line.getBytes(),
???????????????? line.getBytes().length,
???????????????? InetAddress.getByName("192.168.49.40"),
???????????????? 9494
????????????????);
????????// 發送數據
????????ds.send(send_dp);
????????///數據已經發送給接收方,需要接收數據//
????????// 創建拆包對象
????????DatagramPacket rece_dp = new DatagramPacket( new byte[1024] , 1024 );
????????// 接收數據
????????ds.receive(rece_dp);
????????// 獲取數據
????????String s = new String( rece_dp.getData() , 0 , rece_dp.getLength() );
????????System.out.println(s);
????????
????????// 關閉
????????ds.close();
????}
}
接收端(編碼實戰)
/*
* 接收方:
* ????1、接收數據
* ????2、統計出其中的字母字符個數
* ????3、將結果發送給發送方
*/
public class UdpReceTest {
????public static void main(String[] args) throws Exception {
????????
????????// 創建接收方對象
????????DatagramSocket ds = new DatagramSocket( 9494 );
????????
????????// 創建拆包對象
????????DatagramPacket rece_dp = new DatagramPacket( new byte[1024] , 1024 );
????????// 接收數據
????????ds.receive(rece_dp);
????????// 獲取數據
????????String data = new String( rece_dp.getData() , 0 , rece_dp.getLength() ) ;
????????System.out.println(rece_dp.getAddress().getHostAddress()+"接收方接收到的數據是:"+data);
????????// 處理:統計字母字符個數
????????int count = 0;
????????// 遍歷字符串
????????for( int i = 0 ; i < data.length() ; i++ ){
????????????// 取出字符串中的每個字符
????????????char ch = data.charAt(i);
????????????// 判斷是否是字母字符
????????????if( (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ){
????????????????count++;
????????????}
????????}
????????// 統計結束之后,需要將結果發送給發送方
????????String s = "統計的結果是:"+count;
????????// 創建打包對象
????????DatagramPacket send_dp = new DatagramPacket(
????????????????s.getBytes(),
????????????????s.getBytes().length,
????????????????// 這里的IP地址是發送方的IP地址,可以通過上面的拆包對象獲取
????????????????rece_dp.getAddress(),
????????????????// 接收方的端口號
????????????????rece_dp.getPort()
????????????????);
????????/// 發送數據
????????ds.send(send_dp);
????????
????????// 關閉
????????ds.close();
????}
}
TCP編程
回顧IO流
????
TCP編程模型介紹(理解)
?
?
TCP客戶端(理解)
?
?
?
TCP服務端(理解)
?
TCP客戶端實現(重點+編碼)
?
?
?
TCP服務端實現(重點+編碼)
?
?
?
TCP練習
需求:
需求:客戶端鍵盤錄入數據,發送給服務器,服務端將數據保存到磁盤中,服務端告訴客戶端"微博發送成功"。
?
?
?
?
?
客戶端實現
?
?
?
服務端實現
?
?
?
服務端開啟多線程
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
轉載于:https://www.cnblogs.com/beyondcj/p/6270832.html
總結
以上是生活随笔為你收集整理的第25天多线程、网络编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于js对象引用的小例子
- 下一篇: LeetCode Number Comp