JAVA (多線程、網絡編程、反射、注解、代理)

一、介紹

Java學習之A4:多線程、網絡編程、反射、注解、代理。

二、多線程

(一)、線程的創(chuàng)建

多線程指:多個線程同時執(zhí)行,但每個線程都有自己的執(zhí)行路徑,互不影響(由CPU負責調度執(zhí)行)。

  1. 多線程的創(chuàng)建方式一:繼承Thread類,重寫run()方法,然后分配和啟動子類的實例(p.start();)。
  2. 多線程的創(chuàng)建方式二:聲明一個實現Runnable接口,實現run()方法,然后創(chuàng)建Thread實例并作為參數傳入實現Runnable接口的實例(new Thread(new RunnableImpl()).start();)。
  3. 多線程的創(chuàng)建方式三:使用Callable接口,實現call()方法,然后創(chuàng)建FutureTask實例并作為參數傳入實現Callable接口的實例(new FutureTask(new CallableImpl()).start();)。
1、繼承Thread類

定義一個子類繼承Thread類,重寫run()方法,然后分配和啟動子類的實例(p.start();)。

public class threadstest {
    //main方法本身是由一條主線程負責執(zhí)行的
    public static void main(String[] args) {
        MyThread t=new MyThread();//4、創(chuàng)建一個線程對象,才能代表線程
        t.start();//5、啟動線程,向CPU注冊請求開啟一個線程
        //t線程執(zhí)行,在主線程外執(zhí)行,必須在t.start()后面才能開啟t線程
        for (int i=0;i<5;i++) System.out.println("main is running: "+i);
        /**輸出:(每次情況都不一樣,主線程和MyThread線程并行)
         * main is running: 0
         * main is running: 1
         * MyThread is running: 0
         * main is running: 2
         * main is running: 3
         * main is running: 4
         * MyThread is running: 1
         * MyThread is running: 2
         * MyThread is running: 3
         * MyThread is running: 4
         */
    }
}
class MyThread extends Thread{//1、定義定義一個子類繼承Thread類,成為線程類
    //2、重寫Threads類的run方法
    @Override
    public void run(){
        for (int i=0;i<5;i++)System.out.println("MyThread is running: "+i);
        //3、在run方法中編寫線程的任務代碼(線程的任務)
    }
}

優(yōu)點:編碼簡單

缺點:無法控制線程的優(yōu)先級、調度順序、運行狀態(tài)、等待和喚醒,無法繼承其他類,只能繼承Threads類。

注意:啟動線程必須是調用start()方法,若調用run()方法,會當成普通的方法執(zhí)行,此時相當于還是單線程執(zhí)行。

2、實現Runnable接口

實現Runnable接口,實現run()方法,然后創(chuàng)建Thread實例并作為參數傳入實現Runnable接口的實例(new Thread(new RunnableImpl()).start();)。

public class runnable {
    public static void main(String[] args) {
        //3、創(chuàng)建線程任務對象代表一個線程任務
        Runnable r=new MyRunnable();
        //4、把線程任務對象交給一個線程對象來處理
        Thread t=new Thread(r);
        //5、啟動線程
        t.start();
        for (int i=0;i<5;i++) System.out.println("main is running: "+i);
        
        //匿名內部類實現:
        new Thread(new Runnable() {
            @Override
            public void run() {for (int i=0;i<5;i++) System.out.println("MyThread is running: "+i);}
        }).start();//直接調用start()啟動
        
        //函數式接口匿名內部類
        new Thread(()->{for (int i=0;i<5;i++) System.out.println("MyThread is running: "+i);}).start();
    }
}
//1、定義一個線程任務類實現Runnable接口
class MyRunnable implements Runnable {
    //2、重寫run方法,設置線程任務
    @Override
    public void run(){
        for (int i=0;i<5;i++)System.out.println("MyThread is running: "+i);
    }
}

優(yōu)點:任務類只是實現接口,可以繼承其他類,擴展性強。

缺點:需要多一個Runnable對象,并且線程執(zhí)行結果不能直接返回。

3、使用Callable接口(需要返回值)

前兩種線程執(zhí)行完后重寫的run()方法不能直接返回結果,因為run的返回值為void。

使用Callable接口,實現call()方法,然后創(chuàng)建FutureTask(線程任務對象)實例并作為參數傳入實現Callable接口的實例(new FutureTask(new CallableImpl()).start();)。

public class callable {
    public static void main(String[] args) {
        //3、創(chuàng)建一個callable接口的實現類對象
        MyCallable c=new MyCallable(100);
        
        //4、把callable對象封裝成一個線程任務對象FutureTask對象
        /**
         * FutureTask類實現了Runnable接口,所以可以作為線程任務對象
         * 線程任務對象可以作為線程對象來啟動,可以獲取線程執(zhí)行完畢后的結果
         */
        Runnable t=new FutureTask<Integer>(c);
        
        //5、把FutureTask對象交給一個線程對象來處理
        Thread thread=new Thread(t);
        thread.start();
        
        //6、調用FutureTask對象的get方法,獲取線程執(zhí)行完畢后的結果
        try {
            System.out.println(thread.getName()+" is running: "+((FutureTask<Integer>) t).get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
//1、定義實現類,實現callable接口
class MyCallable implements Callable<Integer> {
    private int sum;
    public MyCallable(int sum) {this.sum=sum;}
    //2、重寫call方法,返回計算結果
    public Integer call() throws Exception {
        int n = 0;
        for (int i = 1; i <= sum; i++) {
            n += i;
        }
        return n;
    }
}

優(yōu)點:線程任務類只是實現接口,可以繼續(xù)繼承類和實現接口,可以在線程執(zhí)行完畢后去獲取線程執(zhí)行的結果

缺點:編碼復雜。

(二)、線程的常用方法

Thread提供的常用方法 說明
void run() 線程任務,線程執(zhí)行體,線程被創(chuàng)建后,必須重寫run方法
void start() 啟動線程,向CPU注冊請求開啟一個線程
void String getName() 獲取線程名稱,默認是Thread-索引,注意要放在啟動線程之前
void setName(String name) 設置線程名稱,默認是Thread-索引
static Thread currentThread() 獲取當前線程對象,當前線程對象是靜態(tài)方法,所以可以直接通過類名調用
void sleep(long millis) 線程休眠,單位為毫秒,線程暫停millis毫秒,在millis毫秒內,線程處于阻塞狀態(tài),無法執(zhí)行其他任務
void join() 讓調用當前的這個方法的線程先執(zhí)行完
Thread提供的常見構造器 說明
Thread(Runnable target) 創(chuàng)建一個線程對象,并指定線程任務對象,線程名稱默認是Thread-索引
Thread(Runnable target, String name) 創(chuàng)建一個線程對象,并指定線程任務對象和線程名稱
Thread(String name) 創(chuàng)建一個線程對象,并指定線程名稱,線程任務對象默認為null

(三)、線程安全問題與線程同步

線程安全問題:多個線程,同時操作同一個共享資源時,可能會出現業(yè)務安全問題。

線程同步:讓多個線程先后依次訪問共享資源,避免多線程操作共享資源時出現數據安全問題。

[!線程同步常見方案——加鎖]

說明:每次只允許一個線程加鎖,加鎖后才能訪問,訪問完畢后自動解鎖,然后其他線程才能再加鎖進來。

1、同步代碼塊:把訪問共享資源的核心代碼給上鎖。synchronized(同步鎖){訪問共享資源的核心代碼}

2、同步方法:把訪問共享資源的核心方法給上鎖。修飾符 synchronized 返回值類型 方法名(形參列表){訪問共享資源的核心代碼}

3、lock鎖對象

public class threadsafe {
    public static void main(String[] args) {
        //創(chuàng)建一個賬戶類,用于創(chuàng)建A,B共同賬戶對象,存入100。
        Account A=new Account(1,100);

        //模擬兩個線程,A和B,A和B共同操作同一個賬戶,A和B同時取錢,每次取10。
        new ABThread("A",A).start();
        new ABThread("B",A).start();
    }
}
class Account {
    private Integer id;
    private double balance;
    public void getMoney(double balance){
        String name=Thread.currentThread().getName();
        if(this.balance>=balance){
            System.out.println(name+"取錢成功,余額為:"+this.balance);
            this.balance-=balance;
            System.out.println(name+"取錢成功,余額應該為:"+this.balance);
        }else{
            System.out.println(name+"取錢失敗,余額不足,余額為:"+this.balance);
        }
    }
//省略getter和setter方法,有參無參構造器,toString方法
}
class ABThread extends Thread {
    private Account account;
    public ABThread(String id,Account account){
        super(id);
        this.account=account;
    }
    @Override
    public void run() {
        account.getMoney(100);
    }
}
1、同步代碼塊(性能好)

作用:把訪問共享資源的核心代碼給上鎖。synchronized(this\類名.class/*同步鎖*/){訪問共享資源的核心代碼}

原理:每次只允許一個線程加鎖后進入,執(zhí)行完畢后自動解鎖,其他線程才能再加鎖進來。

注意:對當前同時執(zhí)行的線程來說,同步鎖必須是同一把(同一個對象),否則會出bug。

鎖對象的使用:使用共享資源作為鎖對象,對于實例方法用this作為鎖,對于靜態(tài)方法用類名.class作為鎖。

public class Synchronized {
    public static void main(String[] args) {
        Account B = new Accounts(1, 100);
        new ABThread("A", B).start();
        new ABThread("B", B).start();

        Account C = new Accounts(2, 100);
        new ABThread("C", C).start();
        new ABThread("D", C).start();
    }
}
class Accounts extends Account{
    public Accounts(int i, int i1) {
        super(i, i1);
    }
    private double p=super.getBalance();
    @Override
    public void getMoney(double balance){
        String name=Thread.currentThread().getName();
//        synchronized ("hei"){
// 同步鎖只能為一個唯一對象,這里使用字符串,字符串是內存內只能加載一份,所以可以作為鎖。
//同步鎖只能為一個對象,這里使用this,this為當前對象,不用像字符串一樣,每次鎖的時候影響到其他對象
        synchronized (this){
            if(p >=balance){
                System.out.println(name+"取錢成功,余額為:"+p);
                p-=balance;
                System.out.println(name+"取錢成功,余額應該為:"+p);
            }else{
                System.out.println(name+"取錢失敗,余額不足,余額為:"+p);
            }
        }//鎖對象:對于實例方法用this作為鎖,對于靜態(tài)方法用類名.class作為鎖。
    }
}
2、同步方法(性能稍欠)

作用:把訪問共享資源的核心方法給上鎖。修飾符 synchronized 返回值類型 方法名(形參列表){訪問共享資源的核心代碼}

給方法上鎖,不如給代碼塊上鎖好,有因為相較于方法,代碼塊的范圍更小更加靈活,同步方法可讀性好。

class Accounts2 extends Account{
    public Accounts(int i, int i1) {
        super(i, i1);
    }
    private double p=super.getBalance();
    @Override
    public synchronized void getMoney(double balance){//把核心方法給上鎖
        String name=Thread.currentThread().getName();
        if(p >=balance){
            System.out.println(name+"取錢成功,余額為:"+p);
            p-=balance;
            System.out.println(name+"取錢成功,余額應該為:"+p);
        }else{
            System.out.println(name+"取錢失敗,余額不足,余額為:"+p);
        }
    }
}
3、lock鎖(接口)

Lock是接口,不能直接實例化,可以采用它的實現類ReentrantLock來實現Lock鎖對象。

對一個工具類創(chuàng)建一個final非靜態(tài)變量,再在需要加鎖的代碼前加上變量名.lock(),再給需要加鎖的代碼拋出異常,最后在finally中解鎖,以防出現死鎖。

//省略main類
class Account {
    private Integer id;
    private double balance;
    private final Lock lock=new ReentrantLock();//一個賬戶一把鎖,加final表示不能修改
//創(chuàng)建一個鎖對象,不能為靜態(tài),靜態(tài)的話所有對象都用一把鎖。
    public void getMoney(double balance){
        String name=Thread.currentThread().getName();
        lock.lock();//加鎖
        try {
            if(this.balance>=balance){
                System.out.println(name+"取錢成功,余額為:"+this.balance);
                this.balance-=balance;
                System.out.println(name+"取錢成功,余額應該為:"+this.balance);
            }else{
                System.out.println(name+"取錢失敗,余額不足,余額為:"+this.balance);
            }
        }finally {
            lock.unlock();//解鎖需要放在finally中,否則可能造成死鎖。
        }
    }
}

(四)、線程池(線程復用)

若啟用線程過多,創(chuàng)建的對象也會有很多,會導致內存消耗過大,CPU的負載過大,導致性能下降。

線程復用:工作線程(WorkThread)+任務隊列(WorkQueue)+任務接口(Runnable/callable)

創(chuàng)建線程池:線程池接口——ExecutorService(執(zhí)行服務)

[!ExecutorService接口]

方法一、使用實現類ThreadPoolExecutor創(chuàng)建線程池對象。

方法二、使用工具類Executors調用靜態(tài)方法返回不同特點的線程池對象。

1、ThreadPoolExecutor創(chuàng)建線程池對象(處理Runnable任務)

ThreadPoolExecutor類提供的構造方法:

public ThreadPoolExecutor(int corePoolSize, 
                            int maximumPoolSize, 
                            long keepAliveTime,
                            TimeUnit unit, 
                            BlockingQueue<Runnable> workQueue, 
                            ThreadFactory threadFactory,
                            RejectionExecutionHandler handler)
使用指定的參數創(chuàng)建線程池對象。

[!ThreadPoolExecutor構造方法]

參一. corePoolSize:線程池中常駐的線程數量

參二. maximumPoolSize:線程池中允許的最大線程數量

CPU密集型任務(計算密集型任務),線程池的常駐線程數量為:
int maxnumPoolSize = Runtime.getRuntime().availableProcessors() + 1;
IO密集型任務(網絡IO密集型任務),線程池的常駐線程數量為:
int maxnumPoolSize = Runtime.getRuntime().availableProcessors() * 2;
線程池的最大線程數量:最大線程數=核心線程數*2

參三. keepAliveTime:線程池中線程空閑的時間,超過這個時間,線程池會自動回收線程

參四. unit:keepAliveTime的單位,默認為毫秒,指定線程存活時間的單位

參五. workQueue:任務隊列,用于存放等待被執(zhí)行的任務,默認為無界隊列ArrayBlockingQueue,可以指定容量,可以基于數組或鏈表的任務隊列。

參六. threadFactory:線程工廠,用于創(chuàng)建線程,默認為DefaultThreadFactory,可以指定線程工廠,如自定義線程名,自定義線程優(yōu)先級等。

參七. handler:拒絕策略,當任務隊列已滿,且線程池中線程數量達到最大值,則執(zhí)行拒絕策略,默認為AbortPolicy,直接拋出異常,可以指定拒絕策略,如忽略任務,執(zhí)行自定義任務等。

[!線程池的注意事項]

開始創(chuàng)建臨時線程:提交任務時核心線程在忙任務隊列滿員,并且還可以創(chuàng)建臨時線程時,才會創(chuàng)建臨時線程。

開始拒絕新任務(handler):核心線程和臨時線程都在忙,任務隊列滿,新任務來時才開始拒絕。

任務拒絕策略 說明
ThreadPoolExecutor.AbortPolicy() 直接拋出異常(默認)RejectedExecutionException
ThreadPoolExecutor.DiscardPolicy() 不拋出異常,直接丟棄任務
ThreadPoolExecutor.DiscardOldestPolicy() 丟棄隊列中最老的任務,再嘗試放入新的任務
ThreadPoolExecutor.CallerRunsPolicy() 由主線程負責調用任務的run()方法從而繞過線程池直接執(zhí)行
2、ThreadPoolExecutor創(chuàng)建線程池對象處理Callable任務

使用Future<T> submit(Callable<T> task)執(zhí)行Callable任務,返回未來任務對象,用于獲取線程返回的結果。

public class executthread {
    public static void main(String[] args) {
        //創(chuàng)建線程池對象,使用線程池的實現類
        ExecutorService executorService=new ThreadPoolExecutor(2, 3,
                10, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        //使用線程池執(zhí)行Runnable任務
        Runnable task1=new RunnableTask1();
        executorService.execute(task1);//任務可以復用,提交的第一個任務
        executorService.execute(task1);
        executorService.execute(task1);//復用線程
        executorService.execute(task1);
        executorService.execute(task1);

        //使用線程池執(zhí)行Callable任務
        Future<String> f1=executorService.submit(new CallableTask(100));
        Future<String> f2=executorService.submit(new CallableTask(200));
        Future<String> f3=executorService.submit(new CallableTask(300));
        Future<String> f4=executorService.submit(new CallableTask(400));
        try {
            System.out.println(f1.get());
            System.out.println(f2.get());
            System.out.println(f3.get());
            System.out.println(f4.get());
        } catch (Exception e) {
            e.printStackTrace();
        }

        //一般不關閉線程池,可以一直使用,但是需要調用shutdown方法
//        executorService.shutdown();//等所有任務執(zhí)行完畢后關閉線程池
//        executorService.shutdownNow();//立即關閉線程池,并打斷正在執(zhí)行的任務

        //當核心線程都在忙時,臨時創(chuàng)建新線程
        executorService.execute(task1);//臨時線程,第六個為臨時線程

        //任務拒絕策略
        executorService.execute(task1);//拒絕策略,直接拋出異常

    }
}
class RunnableTask1 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<5;i++) {
            System.out.println(Thread.currentThread().getName()+" is running:"+i);
            try {//模擬耗時操作
                Thread.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class CallableTask implements Callable<String>{
    private int num;

    public CallableTask(int num) {this.num = num;}
    @Override
    public String call() throws Exception {
        int sum=0;
        for (int i=0;i<num;i++) {
//            System.out.println(Thread.currentThread().getName()+" is running:"+i);
            sum+=i;
        }
        return Thread.currentThread().getId()+":"+sum;
    }
}
3、通過Executors工具類創(chuàng)建線程池對象

Executors底層也是基于ThreadPoolExecutor實現類實現的,我們最好少用Executors工具類,因其沒有控制允許的申請隊列長度,會出現堆積大量的請求,沒有控制允許的創(chuàng)建線程數量,導致OOM(內存溢出)

Executors工具類方法 說明
ExecutorService newFixedThreadPool(int nThreads) 創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數,超出的線程會在隊列中等待。
ExecutorService newCachedThreadPool() 創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
ExecutorService newSingleThreadExecutor() 創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務,保證所有任務按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行。
ExecutorService newScheduledThreadPool(int corePoolSize) 創(chuàng)建一個定長線程池,支持定時及周期性任務執(zhí)行。

(五)、并發(fā)/并行

進程:正在運行的程序就是一個獨立的進程,線程是屬于進程的,一個進程中可以同時運行多個線程,進程中的多個線程是并發(fā)和并行執(zhí)行的。

并發(fā):進程中線程是由CPU負責調度執(zhí)行的,但CPU能同時及處理線程的數量有限,CPU會分時輪流為系統(tǒng)每個線程服務,由于其速度快,這些線程彼此之間是并發(fā)的,即同時執(zhí)行。

并行:同一時間刻度上,同時有多個線程被CPU調度執(zhí)行

三、網絡編程

(一)、通訊三要素

1、IP:設備在網絡中的唯一標識,IPV4和IPV6, DNS域名解析器:把域名(如:www.bilibili.com)解析為IP地址。 物理IP:Mac地址

InetAddress類常用方法 說明
InetAddress getLocalHost() 獲取本機IP,返回一個InetAddress對象
InetAddress getByName(String host) 根據域名獲取IP,返回一個InetAddress對象
boolean isReachable(int timeout) 判斷IP是否可達,參數為超時時間,單位為毫秒,返回boolean值
String getHostAddress() 獲取IP地址,返回String類型
String getHostName() 獲取域名,返回String類型
public class InetAddresstest {
    public static void main(String[] args){
        try{
            //獲取本地IP
            System.out.println(InetAddress.getLocalHost().getHostAddress());//本地IP
            System.out.println("主機名:"+InetAddress.getLocalHost().getHostName());//本地主機名

            //獲取對方IP
            System.out.println(InetAddress.getByName("www.baidu.com").getHostAddress());
            System.out.println("主機名:"+InetAddress.getByName("www.baidu.com").getHostName());

            //判斷本機與對方主機是否互通,判斷是否聯網
            System.out.println(InetAddress.getByName("www.baidu.com").isReachable(5000));
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

2、端口:標記應用程序的唯一標識,

周知端口:0~1023被預先占用(HTTP占用80,FTP占用21)。
注冊端口:1024~65535,分配給用戶進程或者某些應用程序。
動態(tài)端口:49152~65535,由系統(tǒng)分配,不固定分配某進程。

3、協(xié)議:事先規(guī)定的連接規(guī)則,開放式網絡互聯標準:OSI網絡參考模型(全球互聯網標準),事實上國際標準:TCP/UDP網絡模型。

TCP:三次握手——確保通信雙方發(fā)消息沒有問題(C-發(fā)出請求->S-返回一個響應->C-再次發(fā)出確認信息,建立聯系->S)
    四次揮手——確保通信雙方斷開連接沒有問題(C-發(fā)出斷開請求->S-返回一個響應->C,S-處理完畢確認斷開->C-斷開信息->S)

(二)、UDP通道(java.net.DatagramSocket)/CS

特點:無連接、不可靠通信。不事先建立連接,發(fā)送端每次把要發(fā)送的數據(<64KB)、接收端IP等信息封裝成一個數據包,發(fā)出去就不管了。

UDP通信的實現 說明
DatagramSocket構造器 public DatagramSocket() 創(chuàng)建客戶端的Socket對象,系統(tǒng)隨機分配一個端口號
public DatagramSocket(int port) 創(chuàng)建服務器的Socket對象,指定端口號
方法 public void send(DatagramPacket p) throws IOException 發(fā)送數據包,把數據包發(fā)送到指定的服務器
public DatagramPacket receive(DatagramPacket p) throws IOException 接收數據包,把數據包封裝到指定的DatagramPacket對象中,并返回接收到的數據包長度
DatagramPacket構造器 public DatagramPacket(byte[] buf, int length) 創(chuàng)建一個DatagramPacket對象,指定數據包長度,用于接收數據包
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) 創(chuàng)建一個DatagramPacket對象,指定數據包長度、目標IP和端口號,用于發(fā)送數據包
//____________單發(fā)單收____________
public class UDPC_solo {
    //創(chuàng)建客戶端
    public static void main(String[] args) throws Exception{
        System.out.println("客戶端已啟動");
        //創(chuàng)建發(fā)送端對象,實現單發(fā)單收
        DatagramSocket dgs=new DatagramSocket();
        //創(chuàng)建數據包對象封裝要發(fā)送的數據
        byte[] b="Hello,here is a UDPCline massage!".getBytes();
        /**
         * 參數1:要發(fā)送的數據,字節(jié)數組
         * 參數2:要發(fā)送的數據長度
         * 參數3:要發(fā)送的目標主機的IP地址
         * 參數4:要發(fā)送的目標主機的端口號
         */
        DatagramPacket packet=new DatagramPacket(b, b.length, InetAddress.getByName("localhost"), 8888 );
        //讓發(fā)送端對象發(fā)送數據包
        dgs.send(packet);
    }
}
public class UDPS_solo {
    //這里是服務端
    public static void main(String[] args) throws Exception{
        System.out.println("服務端已啟動");
        //創(chuàng)建接收端對象,注冊端口
        DatagramSocket ds=new DatagramSocket(8888);
        //創(chuàng)建數據包對象負責接收數據
        byte[] b=new byte[1024*64];
        DatagramPacket dp=new DatagramPacket(b,b.length);
        //接收數據將數據封裝到數據包對象中
        ds.receive(dp);
        //輸出數據
        System.out.println("服務端收到:"+new String(dp.getData(),0,dp.getLength()));
        //獲取對方IP對象和端口號
        System.out.println("對方IP:"+dp.getAddress().getHostAddress()+" 對象端口:"+dp.getPort());
    }
}
//____________多發(fā)多收_______________
public class UDPCM {//客戶端
    public static void main(String[] args) throws Exception {
        System.out.println("客戶端啟動");
        DatagramSocket ds = new DatagramSocket();
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.print("請輸入要發(fā)送的內容:");
            String str = sc.nextLine();
            if("exit".equals(str)){
                System.out.println("客戶端退出");
                ds.close();//管道關閉,釋放資源
                break;
            }
            byte[] b = str.getBytes();
            ds.send(new DatagramPacket(b, b.length, InetAddress.getLocalHost(), 8888));
        }
    }
}
public class UDPSM {//服務端
    public static void main(String[] args) throws Exception {
        System.out.println("服務器啟動");
        DatagramSocket ds = new DatagramSocket(8888);
        byte[] b = new byte[1024*64];
        DatagramPacket dp = new DatagramPacket(b, b.length);
        while (true) {
            System.out.println("等待客戶端發(fā)送數據");
            ds.receive(dp);
            //查看是否收到
            System.out.println("服務端收到:" + new String(b, 0, dp.getLength()));
            //獲取IP與端口
            System.out.println("客戶端IP:" + dp.getAddress().getHostAddress()+"端口:"+dp.getPort());
        }
    }
}

(三)、TCP通道(java.net.Socket)/CS

特點:面向連接、可靠通信、端到端通信。通信雙方會實現次采取“三次握手”建立連接。

不同點:不同于UDP,TCP需要建立連接,想要同時處理多個客戶端消息需要添加多線程代碼,而UDP可以直接接收多個客戶端消息,不需要用多線程處理。

TCP通信 說明
客戶端 public Socket(InetAddress address, int port) throws IOException 創(chuàng)建客戶端的Socket對象,指定服務器的IP和端口號,并建立連接
public OutputStream getOutputStream() throws IOException 獲取輸出流,發(fā)送數據
public InputStream getInputStream() throws IOException 獲取輸入流,接收數據
服務端 public ServerSocket(int port) throws IOException 創(chuàng)建服務器的ServerSocket對象,指定端口號,并等待客戶端的連接
public Socket accept() throws IOException 等待客戶端的連接,并返回一個Socket對象,表示客戶端的連接對象
public void close() throws IOException 關閉連接,釋放資源
//____________單發(fā)單收____________
public class TCPC {
    public static void main(String[] args) throws Exception {
        //實現TCP通信一發(fā)一收
        Socket s = new Socket("localhost", 8888);
        System.out.println("客戶端啟動");
        //從Socket通信管道中得到一個字節(jié)輸出流,那么對面服務端也要是一個對應的字節(jié)輸入流
        OutputStream os = s.getOutputStream();
        //特殊數據流
        DataOutputStream dos = new DataOutputStream(os);
        dos.writeInt(100);
        dos.writeUTF("你好,我是客戶端");
        //關閉管道
        dos.close();
    }
}
public class TCPS {
    public static void main(String[] args) throws Exception {
        System.out.println("服務端啟動");
        ServerSocket ss = new ServerSocket(8888);//注冊端口號,建立連接
        //調用accept方法,等待客戶端連接,一有客戶端連接會返回一個Socket對象
        Socket s = ss.accept();
        //獲取客戶端發(fā)送的數據
        InputStream is = s.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        System.out.println(dis.readInt());
        System.out.println(dis.readUTF());
        //獲取客戶端端口與IP
        System.out.println("客戶端IP:"+s.getInetAddress().getHostAddress()+" 客戶端端口:"+s.getPort());
    }
}
//____________多發(fā)多收_______________
public class TCPCM {
    public static void main(String[] args) throws Exception {
        //實現TCP通信多發(fā)多收
        Socket s = new Socket("localhost", 8888);
        System.out.println("客戶端啟動");
        OutputStream os = s.getOutputStream();
        DataOutputStream dos = new DataOutputStream(os);
        Scanner sc = new Scanner(System.in);
        //循環(huán)發(fā)送
        while (true) {
            System.out.print("請輸入要發(fā)送的內容:");
            String msg = sc.nextLine();
            if ("exit".equals(msg)) {
                System.out.println("客戶端退出");
                dos.close();//管道關閉,釋放資源
                s.close();
                break;
            }
            dos.writeUTF(msg);
            dos.flush();//刷新數據
        }
    }
}
public class TCPSM {
    public static void main(String[] args) throws Exception {
        System.out.println("服務端啟動");
        ServerSocket ss = new ServerSocket(8888);
        Socket s = ss.accept();
        //獲取客戶端發(fā)送的數據
        InputStream is = s.getInputStream();
        DataInputStream dis = new DataInputStream(is);
        //收消息
        while (true) {
            try {//使用try-catch,當客戶端斷開連接時,程序不會因為 EOFException 而崩潰
                String msg = dis.readUTF();//等待讀取客戶端發(fā)送的數據
                if ("exit".equals(msg)) {
                    System.out.println("服務端退出");
                    dis.close();//管道關閉,釋放資源
                    s.close();
                    break;
                }
                System.out.println("客戶端:"+msg);
                System.out.println("客戶端IP:"+s.getInetAddress().getHostAddress()+" 客戶端端口:"+s.getPort());
            }catch (Exception e)
            {
                System.out.println("客戶端已經斷開連接");
                break;
            }
        }
    }
}
//____________同時接收多個客戶端消息_______________
//客戶端不變、服務端多線程處理
public class TCPSM {
    public static void main(String[] args) throws Exception {
        System.out.println("服務端啟動");
        ServerSocket ss = new ServerSocket(8888);
        //收消息
        while (true) {
            Socket s = ss.accept();
            System.out.println("有客戶端連接"+s.getInetAddress().getHostAddress());
            //把客戶端管道交給一個獨立子線程處理
            new ServerReader(s).start();
        }
    }
}
public class ServerReader extends Thread{
    private Socket s;
    public ServerReader(Socket s){
        this.s = s;
    }
    @Override
    public void run(){
        try {
            //獲取客戶端發(fā)送的數據
            InputStream is = s.getInputStream();
            DataInputStream dis = new DataInputStream(is);
            //使用try-catch,當客戶端斷開連接時,程序不會因為 EOFException 而崩潰
            while (true) {
                try {
                    String msg = dis.readUTF();//等待讀取客戶端發(fā)送的數據
                    if ("exit".equals(msg)) {
                        System.out.println("服務端退出");
                        dis.close();//管道關閉,釋放資源
                        s.close();
                        return;
                    }
                    System.out.println("客戶端:"+msg);
                    System.out.println("客戶端IP:"+s.getInetAddress().getHostAddress()+" 客戶端端口:"+s.getPort());
                }catch (Exception e)
                {
                    System.out.println("客戶端"+s.getInetAddress().getHostAddress()+"已經斷開連接");
                    return;
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

(三)、B/S架構原理

HTTP協(xié)議規(guī)定:響應給瀏覽器的數據格式必須滿足一定的格式

協(xié)議版本 空格 狀態(tài)碼 空格 狀態(tài)符 回車換行         HTTP/1.1 200 OK\t
頭部字段名:值;                回車換行        Content-Type:text/html;charset=UTF-8\t
.......                        回車換行       \t
頭部字段名:值;                回車換行        
必須單獨換一行
響應正文(真正給瀏覽器展示的網頁數據)             <html>...</html>

瀏覽器不能每次請求都創(chuàng)建一個線程,可以使用線程池優(yōu)化,把所有Socket管道包裝成任務隊列。

//使用線程池優(yōu)化,ServerReader還是使用Thread線程(自包含Runnable線程)
public class TCPSM {
    public static void main(String[] args) throws Exception {
        System.out.println("服務端啟動");
        ServerSocket ss = new ServerSocket(8888);
        //創(chuàng)建線程池
        ExecutorService pool= new ThreadPoolExecutor(3, 5, 1000,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(100),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        //收消息
        while (true) {
            Socket s = ss.accept();
            System.out.println("有客戶端連接"+s.getInetAddress().getHostAddress());
            //把客戶端管道包裝成一個任務對象交給線程池處理
            pool.execute(new ServerReader(s));
        }
    }
}

Junit單元測試框架

單元測試:針對最小功能單元:方法,編寫測試代碼對其進行正確性測試。

優(yōu)點:可以針對某個方法執(zhí)行測試,可以針對整個方法執(zhí)行測試。各自獨立,會自動生成測試報告。

步驟:

  1. 導入Junit的jar包到項目中(IDEA集成了)
  2. 為需要測試的業(yè)務寫對應的測試類,為每個業(yè)務方法寫測試方法(公共,無參,無返回值)
  3. 測試方法上必須聲明@Test注解,然后在測試方法中編寫代碼調用被測試的業(yè)務方法測試。
  4. 開始測試:選中測試方法,測試運行,測試通過為綠色,失敗為紅色。
  5. 測試的正確性取決于測試用例的完整性

四、反射

1、反射:加載類,并允許以編程的方法解剖類中的各種成分(成員變量、方法、構造器等)

2、反射獲取類的信息:加載類(Class對象),獲取構造器(Constructor對象)、成員變量(Field對象)、方法(Method對象)。

反射獲取類的信息:

法一:Class c=類名.class
法二:Class c=Class.forName("包名.ClassName")
法三:Class c=對象.getClass()
獲取構造器對象:ClassName.getConstructors( )等方法

獲取成員變量對象:ClassName.getFields( )等方法

獲取方法對象:ClassName.getMethods( )等方法

繞過私有:對象名.setAccessible(true)

獲取構造器對象作用:反射可以動態(tài)創(chuàng)建對象,并且可以調用對象的方法。類名 對象名=(類名) 構造器名.newInstance()

獲取成員變量對象作用:反射可以修改對象的屬性。變量對象名.setField(屬性名,屬性值)

獲取方法對象作用:反射可以調用對象的方法。Object 對象名=方法對象名.setMethod(方法名,參數類型,參數值)

3、反射作用:可以得到一個類的全部成分然后操作,反射可以破壞封裝性,可以繞過泛型的約束,適合做java的高級框架(如SpringBoot)

import java.lang.reflect.Method;
import java.util.ArrayList;

public class getClass {
    public static void main(String[] args) {
// 獲取Class本身
        Class c1 = User.class;
        System.out.println(c1);
        //獲取類本身:Class.forName("類的全類名")
        try {
            Class c2 = Class.forName("com.rasion.reflect.User");
            System.out.println(c2);
            System.out.println(c1 == c2);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //獲取類本身:對象.getClass()
        User user = new User();
        Class c3 = user.getClass();
        System.out.println(c3);
        System.out.println(c1 == c3);

//獲取類的構造器對象:Class.getConstructors()
        Constructor[] classes = c1.getDeclaredConstructors();
        for (Constructor constructor : classes) {
            System.out.println(constructor.getName() + ": " + constructor);
        }

        //無參構造器
        try {
            System.out.println(c1.getConstructor().getName() + ": " + c1.getConstructor());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //獲取一個參數的有參構造器
        try {
            Constructor constructor = c1.getConstructor(String.class);
            System.out.println(constructor.getName() + ": " + constructor);
        } catch (Exception e) {
            e.printStackTrace();
        }

//強轉User的private無參構造器為公共的構造器
        Constructor con = c1.getDeclaredConstructor();
        con.setAccessible(true);//私有構造器使用權限
        User user = (User) con.newInstance();
        System.out.println(user);

//利用反射繞過泛型的約束
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        Class clazz = list.getClass();
        Method add = clazz.getMethod("add", Object.class);//改變泛型方法add的參數類型為Object類型
        add.add(list,1234);
        System.out.println(list);
    }
}

public class reflectKuangJia {
    //反射做簡易框架
    public static void main(String[] args) throws Exception{
        P p = new P("rasion", 18);//自定義了一個P類
        save(p);
        User u = new User("TUP");
        save(u);
    }
    public static void save(Object obj) throws Exception{
        PrintStream out = new PrintStream(new FileOutputStream("resource/hello.txt", true));
        //只有反射知道對象有多少字段
        Class c = obj.getClass();
        Field[] fields= c.getDeclaredFields();//成員變量
        for(Field f:fields){
            f.setAccessible(true);
            out.println(f.getName()+"="+f.get(obj));
        }
        out.println("-------------------");
        out.close();
    }
}

五、注解

注解:java代碼內的特殊標記,如:@Override,@Test,讓其他程序根據注解信息來決定怎么執(zhí)行程序。

應用場景:Junit框架的測試方法注解@Test。

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {//模擬@Test注解
    int count() default 1;// 注解運行次數默認一次
}
public class Myte {
    public static void main(String[] args) throws Exception{
        //獲取MyTest注解對象
        Myte mt = new Myte();
        //獲取類對象
        Class clazz = Myte.class;
        //遍歷所有方法,判斷方法上是否有MyTest注解,有則執(zhí)行
        for(Method m : clazz.getDeclaredMethods()){
            //獲取方法上的注解
            Annotation[] ans = m.getAnnotations();
            //遍歷注解,判斷注解類型是否為MyTest
            for(Annotation an : ans){
                if(an instanceof MyTest){
                    //獲取注解對象,并調用方法
                    MyTest mt1 = (MyTest)an;
                    for(int i = 0; i < mt1.count(); i++){
                        m.invoke(mt);
                    }
                }
            }
        }
    }
    @MyTest
    public static void a1(){
        System.out.println("方法一");
    }

    public static void a2(){//不加注解,不運行
        System.out.println("方法二");
    }
    @MyTest(count = 3)//運行三次
    public static void a3(){
        System.out.println("方法三");
    }
}

(一)、自定義注解

格式:public @interface 注解名{public 屬性類型 屬性名() default 默認值;}

特殊屬性:value,如果注解中只有一個value屬性,或其他屬性都是默認值時,使用注解,value名可以不寫。

@MyC(name="rasion",age=18)//不可以省略名
@My("rasion")
public class main {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
    }
}
public @interface MyC {
    String name;
    int age();
}
public @interface My {
    String value();
    int age() default 18;// 默認值為18
}
//原理
public interface my extends Annotation{
    public abstract String value();
    public abstract int age();
}

(二)、元注解

元注解:注解注解的注解,即放在注解上面的注解。

常用元注解:

  1. @Retention(RetentionPolicy.RUNTIME):保留注解到編譯器運行時,即運行時也能獲取注解信息。
    SOURCE:只保留在源文件中,編譯時丟棄;

    CLASS:保留在class文件中,運行時也保留;

    RUNTIME:保留在class文件中,運行時也保留,可以通過反射獲取注解信息。

  2. @Target(ElementType.TYPE):聲明被修飾的注解只能修飾在哪些位置使用

    TYPE:類,接口;

    FIELD:成員變量;

    METHOD:方法;

    PARAMETER:方法參數;

    LOCAL_VARIABLE:局部變量;

    CONSTRUCTOR:構造器;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})//注解限定范圍為方法與類
//以上兩個為元注解
public @interface MyAnnotation {}

(三)、注解的解析

即:判斷類、方法、成員變量上是否存在注解,并把注解內部解析出來

public class mainTest {
    @Test
    public void parseClass(){//解析類注解
        //獲取類對象
        Class clazz = main.class;
        //判斷類上是否有注解
        if(clazz.isAnnotationPresent(My.class)){
            //獲取注解對象
            Annotation my = (My) clazz.getDeclaredAnnotation(My.class);
//            My my = (My) clazz.getAnnotation(My.class);//與上面的語句一樣
            //獲取注解的值
            System.out.println(((My) my).value());//強轉
            System.out.println(((My) my).age());
        }
    }
}

六、動態(tài)代理

動態(tài)代理:在運行時,根據接口創(chuàng)建代理對象,代理對象調用接口方法,實際調用的是代理對象的方法。

AOP切面:在代理代碼中把目標代碼類包圍,在目標代碼前后添加一些代碼,從而實現代理,這就是切面編程思想。

代理 接口 目標對象類
A方法(..){前置準備} 接口(..){A方法;B方法;} A方法(..){真正的方法)}
B方法(..){前置準備} B方法(..){真正的方法)}

代理創(chuàng)建過程:

目標對象類創(chuàng)建:工具類繼承接口,重寫方法

接口創(chuàng)建:創(chuàng)建目標對象方法的接口方法

代理創(chuàng)建:

修飾 接口名 getInstance(目標對象類 目標對象){//可以改成泛型方法
接口名 對象名=(接口名) Proxy.newProxyInstance(目標對象.getClass().getClassLoader(),
目標對象.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy,Method method, Object[] args)
{代理需要做的事情 return method.invoke(目標對象,args);}
});
return 對象名;}

對象的創(chuàng)建:接口名 對象名=代理名.getInstance(目標對象);

public class User implements UserMapper {//目標對象類
    private String name;
    private String password;
    private String email;
    //.......省略
   @Override
   public User add(User user) {//目標對象實現方法
       System.out.println("添加用戶成功");
      return user;
   }
   @Override
   public void delete(String email) {System.out.println("刪除用戶成功");}
   @Override
   public void update(User user) {System.out.println("修改用戶成功");}
}
public interface UserMapper {//接口
   User add(User user);
   void delete(String email);
   void update(User user);
}
public class UserProxy {//代理類
    public static <T> T getInstance(T t) {
        /**
         * 參數一、用于執(zhí)行用哪個類加載器加載生成的代理類
         * 參數二、要代理的接口——>new Class[]{UserMapper.class}或者s.getClass().getInterfaces()
         * 參數三、代理類要做的事
         */
        T usm=(T) Proxy.newProxyInstance(t.getClass().getClassLoader(),
                t.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 參數一、proxy接收到代理對象本身
                     * 參數二、正在被調用的方法
                     * 參數三、被代理的方法參數
                     */
                    @Override
                    public Object invoke(Object proxy, 
                                         Method method, Object[] args) throws Throwable {
                        //聲明代理要做的事情
                        if("add".equals(method.getName()))
                            System.out.println("add");
                        else if("delete".equals(method.getName()))
                            System.out.println("delete");
                        else if("update".equals(method.getName()))
                            System.out.println("update");
                        //調用被代理的方法
                        return method.invoke(s,args);
                    }
                });
        return usm;
    }
}
public class main {//main方法調用代理對象
   public static void main(String[] args) {
      User user = new User("rasion", "123456", "rasion@qq.com");
      //創(chuàng)建代理對象
      UserMapper proxy = UserProxy.getInstance(user);
      System.out.println(proxy.add(user));
      proxy.delete("rasion@qq.com");
      proxy.update(user);
   }
}

七、學習鏈接

  1. 黑馬程序員Java課程
  2. A4部分代碼倉庫

StringBuilder

拼接字符串的時候,使用StringBuilder,性能更好。

public class StringBuilde {
    public static void main(String[] args) {
        //拼接字符串用"+",如果是大量拼接,效率極差
        //String 的對象是不可變量,共享數據時性能可以,但是當修改數據時性能差
//        String s="";
//        for(int i=0;i<100000;i++){
//            s=s+"a";
//        }
//        System.out.println(s);

        //定義字符串可以使用String 類型,但是操作字符串使用StringBuilder(性能好)
        StringBuilder sb=new StringBuilder();//默認長度為16
        for(int i=0;i<100000;i++){
            sb.append("a");
        }
        System.out.println(sb);
        //StringBuilder只是提供拼接字符串的手段,結果還是要恢復成字符串的
        //將對象內容反轉:sb.reverse();
        String s=sb.toString();
        System.out.println(s);
        //支持鏈式編程
        StringBuilder sb2=new StringBuilder("hello").append("world").append("java");
        System.out.println(sb2);
    }
}

BigDecimal

用于解決浮點型運算,出現結果失真問題,把小數包裝成BigDecimal對象解決

public class bigdecimal {
    public static void main(String[] args) {
        double a = 0.1;
        double b = 0.3;
        System.out.println(a + b);

        //把小數包裝成BigDecimal對象
        //必須使用BigDecimal(String val)字符串構造器
//        BigDecimal bd1 = new BigDecimal(Double.toString(a));
        BigDecimal bd1 = BigDecimal.valueOf(a);
        BigDecimal bd2 = BigDecimal.valueOf(b);
        //目的是要把BigDecimal對象轉成double
        System.out.println(bd1.add(bd2).doubleValue());
        //除法,要做精度運算(四舍五入),精確到小數后4位
        System.out.println(bd1.divide(bd2, 4, BigDecimal.ROUND_HALF_UP).doubleValue());
    }
}
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容