一、介紹
Java學習之A4:多線程、網絡編程、反射、注解、代理。
二、多線程
(一)、線程的創(chuàng)建
多線程指:多個線程同時執(zhí)行,但每個線程都有自己的執(zhí)行路徑,互不影響(由CPU負責調度執(zhí)行)。
- 多線程的創(chuàng)建方式一:繼承Thread類,重寫run()方法,然后分配和啟動子類的實例(
p.start();)。 - 多線程的創(chuàng)建方式二:聲明一個實現Runnable接口,實現run()方法,然后創(chuàng)建Thread實例并作為參數傳入實現Runnable接口的實例(
new Thread(new RunnableImpl()).start();)。 - 多線程的創(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í)行測試。各自獨立,會自動生成測試報告。
步驟:
- 導入Junit的jar包到項目中(IDEA集成了)
- 為需要測試的業(yè)務寫對應的測試類,為每個業(yè)務方法寫測試方法(公共,無參,無返回值)
- 測試方法上必須聲明
@Test注解,然后在測試方法中編寫代碼調用被測試的業(yè)務方法測試。 - 開始測試:選中測試方法,測試運行,測試通過為綠色,失敗為紅色。
- 測試的正確性取決于測試用例的完整性。
四、反射
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();
}
(二)、元注解
元注解:注解注解的注解,即放在注解上面的注解。
常用元注解:
-
@Retention(RetentionPolicy.RUNTIME):保留注解到編譯器運行時,即運行時也能獲取注解信息。
SOURCE:只保留在源文件中,編譯時丟棄;CLASS:保留在class文件中,運行時也保留;RUNTIME:保留在class文件中,運行時也保留,可以通過反射獲取注解信息。 -
@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);
}
}
七、學習鏈接
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());
}
}