day16多线程网络编程日志枚举
多线程&网络编程
一、实现多线程
1.1 相关概念
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的一条执行路径、实际运作单位。简单理解:应用软件中互相独立,可以同时运行的功能。我们之前编写的代码属于单线程程序。
进程是程序的基本执行实体、正在运行的程序。进程是系统进行资源分配和调用的独立单位,每个进程都有它自己的内存空间和系统资源。进程具有以下特性
- 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
- 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
- 并发性:任何进程都可以同其他进程一起并发执行。
多线程就是有多个线程的程序,它可以同时执行多个线程以提高效率,可以在一些比较耗时的操作中应用该技术。如:拷贝大文件、复制传输大文件等。聊天软件、服务器、游戏中也有大量应用。
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在多个CPU上同时执行。
1.2 实现方式
1.2.1 Thread类
用来表示/操作线程。在Java中创建好的Thread实例,其实和操作系统中的线程是一一对应的关系,操作系统提供了一组关于线程的API(C语言),Thread类是Java对于这组API进一步封装。
方法介绍
方法名 说明 启动方法 void run() 在线程开启后,此方法将被调用执行 void start() 使此线程开始执行,Java虚拟机会调用此线程的run方法() 名称/对象相关方法 [点这里](#示例代码 1.1) void setName(String name) 将此线程的名称更改为等于参数name String getName() 返回此线程的名称 static Thread currentThread() 返回对当前正在执行的线程对象的引用 控制方法 [点这里](#示例代码 1.4) static void sleep(long milis) 使当前正在执行的线程停留(暂停执行) void join() 等待这个线程死亡 [void setDaemon(boolean on)](#示例代码 1.6) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 优先级方法 [点这里](#示例代码 1.5) final int getPriority() 返回此线程的优先级 final void setPriority(int newPriority) 更改此线程的优先级(线程默认优先级是5,线程优先级范围是:1-10) 给线程设置名称还有带参的构造方法Thread(String name),使用带参构造方法需要先在继承子类中写入无参、String name参数构造方法。
public static Thread currentThread() 返回对当前正在执行的线程对象的引用
实现步骤
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
示例代码 1.1
/*MyThread类*/
public class MyThread extends Thread{MyThread() {System.out.println("Thread.currentThread()---"+Thread.currentThread().getName()); //构造方法的执行线程为main}@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(this.getName()+"线程开始执行了"+i);}System.out.println("Thread.currentThread()---"+Thread.currentThread().getName()); //获取线程对象,返回当前线程对象的名字}
}
/*测试类*/
public class MyTreadDemo {public static void main(String[] args) {MyThread myThread1 = new MyThread();MyThread myThread2 = new MyThread();myThread1.setName("线程1"); //设置线程名字myThread2.setName("线程2");myThread1.start(); //currentThread.getName == 线程1myThread2.start(); //currentThread.getName 此时为“线程2”//myThread2.run(); //直接调用run时,执行线程为main}
}
线程执行有每次执行都可能不同的特点,因为start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统/CPU决定的。
重写run()方法是因为它是用来封装被线程执行的代码,调用start()方法时会自动执行run()方法。
start()方法和run()方法区别:
run()方法封装线程执行的代码。直接调用时,相当于普通方法,使用currentThread方法返回线程对象为main线程;
start()方法启动线程,然后由JVM调用此线程的run方法,使用currentThread方法返回的线程对象名为对应线程名。同一个线程不能多次执行start()方法。
1.2.2 Runnable接口
由于Thread是线程类,需要将方法传入后才能执行。为了将线程和操作方法分离,Java提供了Runnable接口。Runnalbe 只是一个接口,提供了唯一一个方法 run()
,可用于多线程中任务的运行定义。
Thread构造方法
Thread(Runnable target) 分配一个新的Thread对象
实现步骤
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法
- 创建MyRunnable类对象
- 创建Thread类对象,把MyRunnable对象作为构造方法的参数
- 启动线程
示例代码 1.2
/*MyRunnable*/
public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 50; i++) {System.out.println(Thread.currentThread().getName() + "开动了" + i);}}
}
/*测试类*/
public class MyRunnableDemo {public static void main(String[] args) {MyRunnable mr = new MyRunnable();MyRunnable mr1 = new MyRunnable();Thread thread = new Thread(mr,"飞机");Thread t = new Thread(mr1,"导弹");thread.start();t.start();}
}
相比继承Thread类,实现Runnable接口避免了Java单继承的局限性,适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想。
1.2.3 Callable和FutureTask
继承Thread和实现Runnable接口两种方式重写run()方法都不可以直接返回结果,不适合需要返回线程执行结果的业务场景。
Callable是一个函数式接口,Callable接口接受一个泛型作为接口中call方法的返回值类型。
FutureTask可以把Callable对象封装成线程任务对象以交给Thread处理,线程执行后可以通过FutureTask的get方法去获取任务执行的结果。
方法介绍
方法名 说明 V call() 计算结果,如果无法计算结果,则抛出一个异常 FutureTask(Callable callable) 创建一个FutureTask,一旦运行就执行给定的Callable V get() 如有必要,等待计算完成并获取结果 实现步骤
- 定义一个MyCallable类实现Callable接口
- 在MyCallable类中重写call()方法
- 创建MyCallable类对象
- 创建FutureTask对象,把MyCallable对象作为构造方法的参数
- 创建Thread类的对象,把FutureTask对象作为构造方法的参数
- 启动线程
- 再调用get方法,就可以获取线程结束后的结果。
注意事项
get()方法的调用一定要在Thread类的对象调用start()方法之后
示例代码 1.3
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** @author Lenovo*/
public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {for (int i = 0; i < 10; i++) {System.out.println("跟妹子表白第" + i + "次");}//返回值就表示线程运行完毕之后的结果return "答应";}public static void main(String[] args) throws ExecutionException, InterruptedException {//线程开启之后需要执行里面的call方法MyCallable mc = new MyCallable();//Thread没有直接接收Callable接口实现类对象的构造方法//Thread t1 = new Thread(mc);//可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象FutureTask<String> ft1 = new FutureTask<>(mc);FutureTask<String> ft2 = new FutureTask<>(mc);//创建线程对象Thread t1 = new Thread(ft1);Thread t2 = new Thread(ft2)//String s = ft.get();//开启线程t1.start();t2.start();System.out.println(ft1.get());System.out.println(ft2.get());}
}
1.2.4 Thread的[方法](#1.2.1 Thread类)
- 设置和获取线程名称
- 获取当前线程对象
- 线程控制(睡眠和等待)
示例代码 1.4
public class ThreadDemo {public static void main(String[] args) {MYThread myThread1 = new MYThread();MYThread myThread2 = new MYThread();myThread1.setName("烧水");myThread2.setName("泡面");myThread1.start(); //currentThread.getName == 线程1try {myThread1.join();} catch (InterruptedException e) {e.printStackTrace();}myThread2.start(); //currentThread.getName 此时为“线程2”}
}
class MYThread extends Thread {MYThread() { }@Overridepublic void run() {for (int i = 0; i < 30; i++) {System.out.println(this.getName() + "开始执行了"+i);}
// try {// Thread.sleep(1000);
// } catch (InterruptedException e) {// e.printStackTrace();
// }System.out.println("Thread.currentThread()---" + Thread.currentThread().getName());}
}
线程优先级
线程调度模型
分时调度模型:
所有线程轮流使用CPU的使用权,平均分配给每个线程占用CPU的时间片。
抢占式调度模型:
优先让优先极高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级搞得线程获取的CPU时间片相对多一些。
线程默认优先级是5,范围为1~10的int类型整数,最高为10最低为1。线程优先级高仅仅代表线程获取CPU时间片的几率高,不代表他一定优先完成。
示例代码 1.5
public class ThreadDemo {public static void main(String[] args) {MYThread myThread1 = new MYThread();MYThread myThread2 = new MYThread();myThread1.setName("汽车");myThread1.setPriority(1);myThread2.setName("飞机");myThread2.setPriority(10);myThread1.start(); //currentThread.getName == 线程1myThread2.start(); //currentThread.getName 此时为“线程2”}
}class MYThread extends Thread {MYThread() { }@Overridepublic void run() {for (int i = 0; i < 30; i++) {System.out.println(this.getName() + "开始执行了" + i);}System.out.println("Thread.currentThread()---" + Thread.currentThread().getName());}
}
- 守护线程
示例代码 1.6
public class ThreadDemo {public static void main(String[] args) {MYThread myThread1 = new MYThread();MYThread myThread2 = new MYThread();myThread1.setName("公主");myThread2.setName("骑士");//设置守护线程myThread2.setDaemon(true);myThread1.start(); myThread2.start(); }
}
class MYThread extends Thread {MYThread() { }@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(this.getName() + "开始执行了"+i);}System.out.println(Thread.currentThread().getName()+"死了");//标记终点}
}
运行结果
公主开始执行了98
公主开始执行了99
骑士开始执行了87
公主死了
骑士开始执行了88
骑士开始执行了89
骑士开始执行了90 //公主死了之后骑士并未执行到终点
1.2.5 线程生命周期
- 创建线程对象(new Thread)
- 执行(start),转为就绪状态(有执行资格但没执行权)
- 如果抢到执行权,开始运行
- 如果运行时执行权被抢走,返回就绪状态
- 如果运行时遇到阻塞方法,转为阻塞状态(没有执行资格和执行权)
- 阻塞状态结束(方法时间结束/阻塞方式结束),返回就绪状态
- 如果执行完成(run结束),线程死亡变成垃圾
小结
线程相关概念:线程是操作系统调度的最小单位,进程是程序运行的实体,多线程就是有多个线程的程序,并发就是同时有多个程序交替执行,并行就是有多个程序同时执行。
Java中有三种实现线程的方式:继承Thread类、实现Runnable接口、实现Callable接口,他们的最终实现类都是Thread。但Callable可以获取方法返回值,Thread和Runnable则不可以。Thread有设置/获取名字、返回线程对象、开始/等待/睡眠、设置/返回优先级、守护线程等方法。
线程自创建开始进入生命周期,线程对象调用start方法开启第二个周期,线程转为就绪状态,如果此时抢到执行权就开始运行,若没有或运行时执行权被抢走就转为就绪状态,如果运行时遇到阻塞方法则转为阻塞状态,阻塞状态结束返回就绪状态,执行完成run方法体内部代码后线程死亡。
二、 线程同步
2.1 线程的安全问题
2.1.1 卖票案例
需求:有100张票,有三个窗口卖票,请设计一个程序模拟卖票。
/*SellTicket*/
public class SellTicket implements Runnable {private int tickets = 100;@Overridepublic void run() {while (true) {if (tickets > 0) {try {Thread.sleep(100); //模拟出票动作} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "售出第" + tickets);tickets--; //卖出后票数-1System.out.println("余票" + tickets + "张"); //显示余票} else {System.out.println("票没了");try {Thread.sleep(10000); //模拟隔不久就有人来买票} catch (InterruptedException e) {e.printStackTrace();}}}}
}
/*测试类*/
public class SellTicketDemo {public static void main(String[] args) {SellTicket st = new SellTicket();Thread t1 = new Thread(st,"窗口1");Thread t2 = new Thread(st,"窗口2");Thread t3 = new Thread(st,"窗口3");t1.start();t2.start();t3.start();/*窗口2售出第97余票96张窗口2售出第96余票95张窗口1售出第96余票95张*///由于线程具有随机性,多个线程操作一个数据源时会造成线程安全问题}
}
2.1.2 出现线程安全问题原因
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
当以上三个条件同时出现,就会导致线程安全问题,解决方法为:把多条语句操作共享数据的代码锁起来,让任意时刻只能有一个线程执行即可。方式有:同步代码块、同步方法
一个线程只能有锁的时候才能对共享数据进项访问,结束访问后必须释放锁。
持有锁和释放锁之间所执行的代码叫做临界区(Critical Section)
锁具有排他性,即一个锁只能被一个线程持有,这种锁被称为互斥锁
2.1.3 同步代码块
格式
synchronized(任意对象){多条语句操作共享数据的代码 }
优势劣势
- 优势:解决了多线程的数据安全问题
- 劣势:当线程很多时,每个线程都会判断同步上的锁,会降低程序的运行效率。
锁对象需要唯一,如果不同线程锁的不是同一个对象,就解决不了线程安全的问题,所以synchronized后的对象不能为this。
示例代码 2.1
/*SellTicket类*/
public class SellTicket implements Runnable {private int tickets = 100;private Object obj = new Object();@Overridepublic void run() {while (true) {//假设t1先抢到执行权,t1进入run,遇到sychronized将自身锁入synchronized (obj){if (tickets > 0) {try {//t1 休眠,但仍占用锁Thread.sleep(100);//假设此时t2进入,但t1被锁在里面,t2无法执行锁内代码块} catch (InterruptedException e) {e.printStackTrace();}//t1休眠结束,继续执行System.out.println(Thread.currentThread().getName() + "售出第" + tickets);tickets--;} else {System.out.println("票没了");try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}}}}
}
/*测试类*/
public class SellTicketDemo {public static void main(String[] args) {SellTicket st = new SellTicket();Thread t1 = new Thread(st,"窗口1");Thread t2 = new Thread(st,"窗口2");Thread t3 = new Thread(st,"窗口3");t1.start();t2.start();t3.start();}
}
/*
窗口3售出第10
窗口3售出第9
窗口2售出第8
窗口2售出第7
窗口1售出第6
窗口1售出第5
窗口1售出第4
窗口1售出第3
窗口1售出第2
窗口1售出第1
票没了
*/
2.1.4 同步方法
同步方法就是将synchronized加到方法上。
格式
修饰符 synchronized 返回值类型 方法名(参数){方法代码块 }
同步方法的锁对象是this
静态同步方法
就是将synchronized加到静态方法上
静态同步方法的锁对象是类名.class
示例代码 2.2
public class SellTicket implements Runnable {private static int tickets = 100;@Overridepublic void run() {while (true) {sellTicket();}}private synchronized static void sellTicket() {if (tickets > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "售出第" + tickets);tickets--;} else {System.out.println("票没了");try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
/*测试类*/
public class SellTicketDemo {public static void main(String[] args) {SellTicket st = new SellTicket();Thread t1 = new Thread(st,"窗口1");Thread t2 = new Thread(st,"窗口2");Thread t3 = new Thread(st,"窗口3");t1.start();t2.start();t3.start();}
}
2.1.5 Lock锁
Lock是一个接口,控制多个线程对共享资源访问的工具,实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
方法名 | 说明 |
---|---|
void lock(); | 获得锁 |
void unlock(); | 释放锁 |
实现类构造方法 | ReentrantLock |
ReentrantLock(); | 创建一个ReentrantLock实例 |
示例代码 2.3
public class LockDemo {public static void main(String[] args) {SellTicket sl = new SellTicket();Thread t1 = new Thread(sl, "窗口1");Thread t2 = new Thread(sl, "窗口2");Thread t3 = new Thread(sl, "窗口3");t1.start();t2.start();t3.start();}
}class SellTicket implements Runnable {private int tickets = 100;private Lock lock = new ReentrantLock();@Overridepublic void run() {while (true) {lock.lock();try {if (tickets > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "售出第" + tickets);tickets--;} else {System.out.println("票没了");break;}} finally {lock.unlock();}}}
}
2.2 死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
java 死锁产生的四个必要条件:
- 互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
- 不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
- 请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
- 循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
示例代码 2.4
public class DeadLock {public static void main(String[] args) {SubThread t1 =new SubThread();t1.setName("a");t1.start();SubThread t2 = new SubThread();t2.setName("b");t2.start();}static class SubThread extends Thread{private static final Object yitian = new Object();private static final Object tulong = new Object();@Overridepublic void run() {if ("a".equals(Thread.currentThread().getName())){synchronized (yitian){System.out.println("a线程获得了倚天剑,爽翻了,再来个屠龙刀就好了...");
// synchronized (tulong){ //造成死锁
// System.out.println("a线程获得了倚天剑和屠龙刀,直接称霸武林...");
// }}synchronized (tulong){System.out.println("a线程获得了倚天剑和屠龙刀,直接称霸武林...");}}if ("b".equals(Thread.currentThread().getName())){synchronized (tulong){System.out.println("b线程获得了屠龙宝刀,得劲,谁也不给....");
// synchronized (yitian){ //造成死锁
// System.out.println("b线程获得了屠龙后又来把倚天剑....b称霸武林");
// }}synchronized (yitian){System.out.println("b线程获得了屠龙后又来把倚天剑....b称霸武林");}}}}
}
小结
多个线程操作一个共享资源时可能造成数据不安全,解决方案为给操作数据的代码块上个“锁”,有同步代码块、同步方法、Lock锁三种锁解决线程的安全问题。
同步代码块有关键字synchronized,他会将第一个抢到该线程的任意对象锁住,只允许该对象执行锁中代码。执行完后会释放锁。劣势是当线程很多时,每个线程都需要判断一次锁会影响程序运行效率。
同步方法就是将该关键字放到方法声明上,修饰符 synchroized 方法返回值类型 方法名(方法形参){}。需要注意同步方法锁的对象是this,静态同步方法锁锁的是类名.class。
Lock锁是接口,使用前需要先获得Lock对象,有lock和unlock方法去锁代码块。
死锁出现原因是不同线程之间互相持有对方所需的资源,发生死锁将导致程序无法继续下去。
三、线程通信
3.1 等待/通知机制
在单线程编程中,要执行的操作需要满足一定的条件才能执行,可以把这个操作放在if语句快中。
在多线程编程中,可能A线程条件没有满足只是暂时的,稍后其他的线程B可能会更新条件使得A线程的条件得到满足,可以将A线程暂停,直到他的条件得到满足后再将A线程唤醒。
3.2 Object类的等待和唤醒方法
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待同时释放锁,直到另一个线程调用该对象的notify()方法 |
void wait(long) | 如果在参数指定的时间内没有被唤醒,超时后会自动唤醒。 |
void notify() | 唤醒正在等待单个线程,并不立即释放锁 |
void notifyAll() | 唤醒正在等待的所有线程,并不立即释放锁 |
wait()方法只能在同步代码块中由锁对象调用,调用后当前线程会释放锁。
notify()方法也只能在同步代码块中由锁对象调用,没有使用锁对象调用wait()/notify()会抛出异常。如果有多个等待的线程,**notify()方法只能唤醒其中的一个,并不会立即释放锁对象。**一般将notify方法放在同步代码块的最后。
通知过早:线程wait()等待后,可以调用notify()唤醒线程,如果notify()唤醒过早,在等待之前就调用了notify()可能会打乱程序正常的执行逻辑。
3.3 生产者和消费者
生产者消费者问题是多线程同步问题的经典案例。生产者生成一定量的数据放到缓冲区中,然后重复此过程;与此同时,消费者也在缓冲区消耗这些数据。生产者和消费者之间必须保持同步,要保证生产者不会在缓冲区满时放入数据,消费者也不会在缓冲区空时消耗数据。不够完善的解决方法容易出现死锁的情况,此时进程都在等待唤醒。
3.3.1 案例分析
- 缓冲区类(Box)定义表示奶箱牛奶数量的int变量,定义标记箱中是否有牛奶的boolean变量,定义存入方法put和取出方法get。
- 生产者类(Productor)实现Runnable接口,重写run方法,设置线程任务
- 消费者类(Consumer)实现Runnable接口,重写run方法,设置线程任务
- 测试类(BoxDemo)main方法,创建生产者消费者对象,生产成对应线程对象启动线程。
3.3.2 代码实现
Box类
public class Box {/**定义一个成员变量表示第几瓶奶*/private int milk;/**定义一个成员变量表示奶箱状态,true表示有,false表示无*/private boolean state = false;/**提供存储牛奶的操作*/public void put(int milk) {//如果有就等待消费if (state){try {wait();} catch (InterruptedException e) {e.printStackTrace();}}//如果没有就生产this.milk = milk;System.out.println("生产出第" + this.milk + "瓶奶");//修改奶箱状态state = true;//唤醒其他线程notifyAll();}/**提供获取牛奶的操作*/public void get() {//如果没有牛奶就等待if (!state) {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}//如果有就消费System.out.println("用户拿到第" + this.milk + "瓶奶");//消费完后修改奶箱状态state = false ;//唤醒其他线程notifyAll();}
}
Product类
public class Product implements Runnable {/**定义Box对象*/private Box box;/**将其他类中的Box对象传入这里*/Product(Box box) {this.box = box;}/**run方法内进行生产牛奶操作,将牛奶放入奶箱*/@Overridepublic void run() {//最多5瓶奶for (int i = 1; i <= 5; i++) {box.put(i);}}
}
Consumer类
public class Consumer implements Runnable{/**定义Box对象*/private Box box;/**将其他类中的Box对象传入这里*/public Consumer(Box box) {this.box = box;}/**run方法内进行消费牛奶操作,将牛奶从奶箱取出*/@Overridepublic void run() {while (true){box.get();}}
}
BoxDemo类
public class BoxDemo {public static void main(String[] args) {Box box = new Box();//创建生产者对象Product product = new Product(box);//创建消费者对象Consumer consumer = new Consumer(box);//生成对应线程Thread t1 = new Thread(product,"送奶工");Thread t2 = new Thread(consumer,"小明");//启动线程t1.start();t2.start();}
}
小结
线程通信主要有两类方法:wait和notify,wait用于让线程等待,notify用于唤醒线程。生产着消费者案例很好的解释了线程间通信问题。
四、 线程池
4.1 线程状态介绍
线程创建并启动后,不是立刻进入执行状态也不是一直处于执行状态。线程对象在不同时期有不同状态,状态被定义在java.lang.Thread.State枚举类中,State枚举类源码如下:
public class Thread { public enum State { /* 新建 */NEW , /* 可运行状态 */RUNNABLE , /* 阻塞状态 */BLOCKED , /* 无限等待状态 */WAITING , /* 计时等待 */TIMED_WAITING , /* 终止 */TERMINATED; } // 获取当前线程的状态public State getState() {return jdk.internal.misc.VM.toThreadState(threadStatus);}}
通过源码可以看见java的线程有6种状态,每种状态的含义如下
线程状态 | 具体含义 |
---|---|
NEW | 一个尚未启动的线程的状态。也称之为初始状态、开始状态。线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread()只有线程对象,没有线程特征。 |
RUNNABLE | 当我们调用线程对象的start方法,那么此时线程对象进入了RUNNABLE状态。那么此时才是真正的在JVM进程中创建了一个线程,线程一经启动并不是立即得到执行,线程的运行与否要听令与CPU的调度,那么我们把这个中间状态称之为可执行状态(RUNNABLE)也就是说它具备执行的资格,但是并没有真正的执行起来而是在等待CPU的调度。 |
BLOCKED | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
WAITING | 一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有两种,分别是调用Object.wait()、join()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。 |
TIMED_WAITING | 一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有三种,分别是:Thread.sleep(long),Object.wait(long)、join(long)。 |
TERMINATED | 一个完全运行完成的线程的状态。也称之为终止状态、结束状态 |
各个状态的转换如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e2J1BmzA-1675157879226)(C:\Users\Lenovo\Desktop\我\实习培训\培训\SE进阶笔记\img\1591163781941.png)]
4.2 基本原理
线程池就是一个可以复用线程的技术。线程池内部可以预先创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交给线程池, 线程池将这些任务缓存在工作队列中, 线程池中的工作线程不断地从队列中取出任务并执行。工作线程就是我们之前学的线程,任务就是实现了Runnable或Callable接口的实例对象。线程池应用场景有:网购商品秒杀、云盘文件上传下载、12306网上购票系统等等。
4.3 Executors
概述:JDK对线程池也进行了相关的实现,我们可以使用Executors中提供的静态方法来创建线程池
构造方法 | 说明 |
---|---|
static ExecutorService newCachedThreadPool() | 创建一个默认的线程池,线程数量会随着任务增加而增加,如果线程任务执行完毕且空闲一段时间则会被回收掉 |
static ExecutorService newFixedThreadPool(int nThreads) | 创建一个指定最多线程数量的线程池,如果某个线程因为执行异常而结束,线程池会补充一个新线程替代它 |
static ExecutorService newSingleThreadExecutor() | 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程 |
static ScheduleExecutorService newScheduledThreadPool(int corePoolSize) | 创建一个线程池,可以实现在给定的延迟后运行任务或者定期执行任务 |
Executors的底层是基于ThreadPoolExecutor创建线程池对象的。大型并发系统环境中使用Executors如果不注意可能出现系统风险,所以建议使用ThreadPoolExecutor。
newFixedThreadPool(int nThreads)和newSingleThreadExecutor()允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误。
newCachedThreadPool()和newScheduledThreadPool(int corePoolSize)创建的线程最大数量上限是Integer.MAX_VALUE,线程数可能会随着任务1:1增长,也可能出现OOM错误。
常用方法 | 说明 |
---|---|
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行Runnable任务 |
Future submit(Callable task) | 执行任务,返回未来任务对象获取线程结果,一般拿来执行Callable任务 |
void shutdown() | 等任务执行完毕后关闭线程池 |
LisshutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 |
示例代码 4.1
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolDemo {public static void main(String[] args) {//static ExecutorService newCachedThreadPool() 创建一个默认的线程池//static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池//因为是static修饰的,所以用 类名. 形式即可,指定线程数量最多为10ExecutorService executorService = Executors.newFixedThreadPool(10);//Executors --- 可以帮助我们创建线程池对象//ExecutorService --- 可以帮助我们控制线程池//Lambda表达式创建Thread对象executorService.submit(() ->//获取线程对象名System.out.println(Thread.currentThread().getName() + "在执行了"));try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}executorService.submit(() ->System.out.println(Thread.currentThread().getName() + "在执行了"));executorService.shutdown();}
}
4.4 ThreadPoolExecutor
ThreadPoolExecutor是ExecutorService接口(线程池)的实现类,可以用来自创建一个线程池对象。
4.4.1 构造方法
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);
}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), handler);
}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;
}
4.4.2 使用流程
- 利用Executors工厂类的静态方法,创建线程池对象;
- 编写Runnable或Callable实现类的实例对象;
- 利用ExecutorService的submit方法或ScheduledExecutorService的schedule方 法提交并执行线程任务
- 如果有执行结果,则处理异步执行结果(Future)
- 调用shutdown()方法,关闭线程池。
示例代码 4.2
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class MyThreadPoolDemo3 {// 参数一:核心线程数量
// 参数二:最大线程数
// 参数三:空闲线程最大存活时间
// 参数四:时间单位
// 参数五:任务队列
// 参数六:创建线程工厂
// 参数七:任务的拒绝策略public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2, //参数123 核心线程数量为2,最大线程数为5,空闲线程最大存活时间2sTimeUnit.SECONDS, //参数四指定时间单位为秒new ArrayBlockingQueue<>(10), //创建任务队列Executors.defaultThreadFactory(), //创建线程工厂new ThreadPoolExecutor.AbortPolicy()); //任务的拒绝策略pool.submit(new MyRunnable());pool.submit(new MyRunnable());pool.shutdown();}
}
4.5 线程池参数详解
根据前面的[构造方法](#4.4.1 构造方法)可以看到,其需要如下几个参数:
corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
unit(必需):指定 keepAliveTime 参数的时间单位。常用的有TimeUnit.MILLISECONDS(毫 秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。常用的几个阻塞队列:
1.LinkedBlockingQueue
链式阻塞队列,底层数据结构是链表,默认大小是Integer.MAX_VALUE,也可以指定大小。
2.ArrayBlockingQueue
数组阻塞队列,底层数据结构是数组,需要指定队列的大小。
3.SynchronousQueue
同步队列,内部容量为0,每个put操作必须等待一个take操作,反之亦然。
4.DelayQueue
延迟队列,该队列中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素 。
注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。
threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。四种拒绝处理的策略为 :
1.ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛出RejectedExecutionException异常。
2.ThreadPoolExecutor.DiscardPolicy:丢弃新来的任务,但是不抛出异常。
3.ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列头部(最旧的)的任务,然后重新尝试执行程序(如果再次失败,重复此过程)。
4.ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)corePoolSize: 核心线程的最大值,不能小于0
maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
keepAliveTime: 空闲线程最大存活时间,不能小于0
unit: 时间单位
workQueue: 任务队列,不能为null
threadFactory: 创建线程工厂,不能为null
handler: 任务的拒绝策略,不能为null
4.6 定时器
4.6.1 Timer
Timer定时器主要做定时任务或者按照一定的时间间隔做任务,例如每天4点钟定时执行作业等
方法名 | 说明 |
---|---|
schedule(TimerTask task,Date time) | 在指定的时间执行指定的任务 |
schedule(TimerTask task,Date firstTime,long period) | 从指定的时间开始,对指定的任务执行重复的固定的延迟执行 |
schedule(TimerTask task,long delay) | 指定的延迟之后执行指定的任务 |
schedule(TimerTask task,long delay,long period) | 指定的延迟之后重新执行任务,等待delay延迟后每period重复执行 |
Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入。
可能因为其中的某个任务的异常使Timer线程死掉影响后续任务执行。
示例代码4.3
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;public class TimerDemo {public static void main(String[] args) {Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"AAA执行"+new Date());//睡眠线程使其乱轴
// try {// Thread.sleep(5000);
// } catch (InterruptedException e) {// e.printStackTrace();
// }}},0,3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"BBB执行"+new Date());
// try {// Thread.sleep(5000);
// } catch (InterruptedException e) {// e.printStackTrace();
// }}},0,3000);// timer.schedule(new TimerTask(){// @Override
// public void run() {// System.out.println("定时执行"+new Date());
// }
// },new DateTime());}
}
4.6.2 ScheduledExecutorService
ScheduledExecutorService是jdk1.5引入的并发包,目的是弥补Timer的缺陷,ScheduledExecutorService内部是线程池。
Executors的方法 | 说明 |
---|---|
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) | 得到线程池对象 |
ScheduledExecutorService的方法 | 说明 |
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit) | 周期调度方法 |
基于线程池,某个任务的执行情况不会影响其他定时任务的执行。
示例代码4.4
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class ScheduledExecutorServiceDemo {public static void main(String[] args) {ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10);scheduler.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "AAA开始执行了"+new Date());}},2,3, TimeUnit.SECONDS);}
}
小结
线程的六个状态:
线程池原理:
两个构造线程池的方法:
Executors:
ThreadPoolExecutor
ThreadPoolExecutor七个参数含义
两种定时器
五、 网络编程入门
5.1 网络编程三要素
5.1.1 IP地址
IP:全称互联网协议地址,是分配给上网设备的唯一标志。常见的IP分类为IPv4和IPv6。
IPv4:给每个链接在网络上的主机用二进制分配一个32字节的地址,常写成十进制形式,每个字节中间用“.”分隔(点分十进制法)。
IPv6:采用128bit地址长度,每16个字节一组,分成8组十六进制数,解决了网络地址资源数量不够的问题。
IP地址形式:分为公网地址和私有地址(局域网使用)
192.168.0.0–192.168.255.255就是常见的局域网地址,专门为组织机构内部使用。
特殊IP地址:
本机IP:127.0.0.1或者localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本机。
相关DOS命令
ipconfig 查看本机IP地址
ping IP地址 检查网络是否连通
5.1.2 端口
端口是设备上应用程序的唯一标识,本质上是两个应用程序的通信。端口号是用两个字节表示的整数,它的取值范围是0 - 65535。
- 公认端口:0 - 1023之间的端口号用于一些知名的网络服务和应用,比如80端口分配给www,21端口分配给FTP
- 注册端口:1024 - 49151 分配给用户进程或应用程序
- 动态/私有端口:49152 - 65535,因为它一般不固定分配某种进程,而是动态分配
- 如果端口号被另外一个服务或应用程序占用,会导致当前程序启动失败。
一个设备中不可以出现两个或多个相同端口号的程序。
相关DOS命令
- 查看所有端口:
netstat -ano
- 查看指定端口:
netstat -aon|findstr "80"
- 查看指定进程:
tasklist|findstr "12476"
- 查看具体程序:使用任务管理器查看PID
5.1.3 协议
计算机网络中,链接和通信的规则被称为网络通信协议。网络通信协议有两套参考模型:OSI参考模型和TCP/IP参考模型
OSI参考模型:世界互联协议标准,全球通信规范,由于此模型过于理想化,所以未能在因特网上进行广泛推广。
TCP/IP参考模型:事实上的国际标准。
传输层有两个常见协议:
UDP协议:用户数据报协议
特点:
- UDP是一种无连接、不可靠传输的协议
- 将数据源IP、目的地IP和端口封装成数据包,不需要建立连接
- 每个数据包的大小限制在64KB内
- 发送不管对方是否准备好,接收方收到也不确认,所以不可靠
- 可以广播发送,发送数据结束时无需释放资源,开销小速度快。
UDP协议通信场景:语音通话,视频会话等。
TCP协议:传输控制协议
特点:
- 使用TCP协议,必须双方先建立链接,它是一种面向连接的可靠通信协议。
- 传输前,采用“三次握手”方式建立连接,所以是可靠的。
- 在连接中可进行大数据量的传输。
- 链接、发送数据都需要确认,且传输完毕后,还需释放已建立的链接,通信效率低。
TCP协议通信场景:对信息安全要求较高的场景,例如:文件下载,金融等数据通信。
三次握手:
- 客户端向服务器发出连接请求等待服务期确认
- 服务器向客户端返回一个响应告诉客户端收到请求
- 客户端再次发出确认信息建立连接。
四次挥手:
- 客户端向服务器发出取消链接请求
- 服务器向客户端返回一个响应表示收到客户端取消请求
- 服务器将最后的数据处理完毕向客户端发出确认取消信息
- 客户端再次发送确认信息链接取消
5.1.4 InetAddress的使用
InetAddress是IP地址操作类,表示协议(IP)地址。
方法名 | 说明 |
---|---|
static InetAddress getLocalHost() | 返回本主机的地址对象 |
static InetAddress getByName(String host) | 得到指定主机的IP地址对象,参数是域名或者Ip地址 |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress() | 返回IP地址字符串 |
boolean isReachable(int timeout) | 在指定毫秒内连通该IP地址对应的主机,连通返回true |
示例代码
public class InetAdressDemo {public static void main(String[] args) throws IOException {//| static InetAddress getLocalHost() | 返回本主机的地址对象 |InetAddress address = InetAddress.getLocalHost();System.out.println(address);//static InetAddress getByName(String host)得到指定主机的IP地址对象,参数是域名或者Ip地址InetAddress address1 = InetAddress.getByName("baidu.com");
// InetAddress address1 = InetAddress.getByName("39.156.66.10");System.out.println(address1);//String getHostName()获取此IP地址的主机名String name = address.getHostName();System.out.println(name);//String getHostAddress()返回IP地址字符串String hostAddress = address.getHostAddress();System.out.println(hostAddress);//boolean isReachable(int timeout)在指定毫秒内连通该IP地址对应的主机,连通返回trueboolean b = address.isReachable(5000);System.out.println(b);}
}
5.2 UDP通信
5.2.1 UDP发送数据
UDP协议是一种不可靠的网络协议,它在通信的两端各建一个Socket对象,但这两个Socket只是发送、接收数据的对象,因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念。Java提供了DatagramSocket类作为基于UDP协议的Socket。
构造方法
方法名 | 说明 |
---|---|
DatagramSocket() | 创建发送端的Socket对象,系统会随机分配端口号 |
DatagramPacket(byte[] buf,int len,InetAddress add,int port) | 创建发送端数据包对象,发送长度为len的数据包到指定主机的指定端口 |
buf:要发送的内容,字节数组
len:发送的内容的字节长度
add:接收端的IP地址对象
port:接收端的端口号
相关方法
方法名 | 说明 |
---|---|
void send(DatagramPacket p) | 发送数据报包 |
void close() | 关闭数据报套接字 |
void receive(DatagramPacket p) | 从此套接字接受数据报包 |
发送数据的步骤
- 创建发送端的Socket对象(DatagramSocket)
- 创建数据,并把数据打包
- 调用DatagramSocket对象的方法发送数据
- 关闭发送端
示例代码5.1(仅作演示)
public class UDPDemo01 {public static void main(String[] args) throws IOException {//创建发送端的Socket对象(DatagramSocket)// DatagramSocket() 构造数据报套接字并将其绑定到本地主机上的任何可用端口DatagramSocket ds = new DatagramSocket();//创建数据,并把数据打包//DatagramPacket(byte[] buf, int length, InetAddress address, int port)//构造一个数据包,发送长度为 length的数据包到指定主机上的指定端口号。byte[] buf = "hello!".getBytes();DatagramPacket dp = new DatagramPacket(buf,buf.length, InetAddress.getByName("Devil"),10086);//调用DatagramSocket对象的方法发送数据//void send(DatagramPacket p) 从此套接字发送数据报包ds.send(dp);//关闭发送端//void close() 关闭此数据报套接字ds.close();}
}
5.2.2 UDP接收数据
接收数据步骤
- 创建接收端的Socket对象(DatagramSocket)
- 创建一个数据包,用于接收数据
- 调用DatagramSocket对象的方法接收数据
- 解析数据包,并把数据在控制台显示
- 关闭接收端
构造方法
方法名 | 说明 |
---|---|
DatagramSocket() | 创建接收端的Socket对象并指定端口号 |
DatagramPacket(byte[] buf, int len) | 创建一个DatagramPacket用于接收长度为len的数据包 |
相关方法
方法名 | 说明 |
---|---|
byte[] getData() | 返回数据缓冲区 |
int getLength() | 返回要发送的数据的长度或接收的数据的长度 |
示例代码5.2(仅作演示)
public class UDPDemo02 {public static void main(String[] args) throws IOException {//创建接收端的Socket对象(DatagramSocket)DatagramSocket ds = new DatagramSocket(10086);//创建一个数据包,用于接收数据byte[] bys = new byte[1024];DatagramPacket packet = new DatagramPacket(bys, bys.length);//调用DatagramSocket对象的方法接收数据ds.receive(packet);System.out.println("数据是:" + new String(packet.getData(), 0, packet.getLength()));}
}
5.2.3 UDP通信程序练习
案例需求:
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
示例代码5.3
import java.io.IOException;
import java.net.*;/*** UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收** @author Lenovo*/
public class ReceiveDemo {public static void main(String[] args) throws IOException {//创建接收对象,端口名19900DatagramSocket ds = new DatagramSocket(19900);while (true) {//创建一个数据包,用于接收数据byte[] bys = new byte[1024 * 64];DatagramPacket dp = new DatagramPacket(bys, bys.length);//调用DatagramSocket对象的方法接收数据ds.receive(dp);//解析数据包,并把数据在控制台显示 // dp.getData返回0~最后的数组元素由new String解析String s = new String(dp.getData(), 0, dp.getLength());System.out.println("数据是:" + s);if (s.equals("886")) {break;}}}
}import java.net.*;
import java.util.Scanner;/**UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束@author Lenovo*/
public class SendDemo {public static void main(String[] args) throws IOException {//创建发送对象和键盘录入对象DatagramSocket ds = new DatagramSocket();Scanner sc =new Scanner(System.in);while (true) {String s = sc.nextLine();//输入的数据是886,发送数据结束if ("886".equals(s)) {byte[] bys = s.getBytes();DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("127.0.0.1"), 19900);//调用DatagramSocket对象的方法发送数据ds.send(dp);break;}//创建数据,并把数据打包byte[] bys = s.getBytes();DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("127.0.0.1"), 19900);//调用DatagramSocket对象的方法发送数据ds.send(dp);}ds.close();}
}
5.2.4 UDP三种通讯方式
单播
单播用于两个主机之间的端对端通信
组播
组播用于对一组特定的主机进行通信
广播
广播用于一个主机对整个局域网上所有主机上的数据通信
5.2.5 UDP组播实现
UDP实现组播要用组播地址:224.0.0.0~239.255.255.255。发送端的数据包目的地是组播IP并指定端口,接收端必须绑定该组播地址,端口还要对应发送端的目的端口,这样即可接收该组播消息。DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP。
示例代码5.4
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;/**UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束@author Lenovo*/
public class SendDemo {public static void main(String[] args) throws IOException {//创建发送对象和键盘录入对象DatagramSocket ds = new DatagramSocket();Scanner sc =new Scanner(System.in);while (true) {System.out.println("请讲");String s = sc.nextLine();//输入的数据是886,发送数据结束if ("886".equals(s)) {byte[] bys = s.getBytes();//将组播地址封装进去DatagramPacket dp = new DatagramPacket(bys, bys.length,InetAddress.getByName("255.255.255.255"), 19900);//调用DatagramSocket对象的方法发送数据ds.send(dp);ds.close();break;}//创建数据,并把数据打包byte[] bys = s.getBytes();DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("127.0.0.1"), 19900);//调用DatagramSocket对象的方法发送数据ds.send(dp);}ds.close();}
}import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;/*** UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收** @author Lenovo*/
public class ReceiveDemo {public static void main(String[] args) throws IOException {//创建接收对象,端口指定SendDemo的Packet端口MulticastSocket ds = new MulticastSocket(19900);//把当前计算机绑定一个组播地址,表示添加到这一组中ds.joinGroup(InetAddress.getByName("225.25.25.25"));while (true) {//创建一个数据包,用于接收数据byte[] bys = new byte[1024 * 64];DatagramPacket dp = new DatagramPacket(bys, bys.length);//调用DatagramSocket对象的方法接收数据ds.receive(dp);//解析数据包,并把数据在控制台显示// dp.getData返回0~最后的数组元素由new String解析String s = new String(dp.getData(), 0, dp.getLength());System.out.print(dp.getSocketAddress().toString());System.out.println(dp.getPort());System.out.println("数据是:" + s);if (s.equals("886")) {break;}}}
}
5.2.6 UDP广播实现
UDP实现广播需要使用广播地址:255.255.255.255,发送端发送数据包的目的地要指定地址为广播地址,指定一个端口。接收端匹配该端口即可实现收到消息。
示例代码5.5
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;/**UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束@author Lenovo*/
public class SendDemo {public static void main(String[] args) throws IOException {//创建发送对象和键盘录入对象DatagramSocket ds = new DatagramSocket();Scanner sc =new Scanner(System.in);while (true) {System.out.println("请讲");String s = sc.nextLine();//输入的数据是886,发送数据结束if ("886".equals(s)) {byte[] bys = s.getBytes();//将广播地址封装进去DatagramPacket dp = new DatagramPacket(bys, bys.length,InetAddress.getByName("255.255.255.255"), 19900);//调用DatagramSocket对象的方法发送数据ds.send(dp);ds.close();break;}//创建数据,并把数据打包byte[] bys = s.getBytes();DatagramPacket dp = new DatagramPacket(bys, bys.length, InetAddress.getByName("127.0.0.1"), 19900);//调用DatagramSocket对象的方法发送数据ds.send(dp);}ds.close();}
}import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;/*** UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收** @author Lenovo*/
public class ReceiveDemo {public static void main(String[] args) throws IOException {//创建接收对象,端口指定SendDemo的Packet端口DatagramSocket ds = new DatagramSocket(19900);while (true) {//创建一个数据包,用于接收数据byte[] bys = new byte[1024 * 64];DatagramPacket dp = new DatagramPacket(bys, bys.length);//调用DatagramSocket对象的方法接收数据ds.receive(dp);//解析数据包,并把数据在控制台显示// dp.getData返回0~最后的数组元素由new String解析String s = new String(dp.getData(), 0, dp.getLength());System.out.print(dp.getSocketAddress().toString());System.out.println(dp.getPort());System.out.println("数据是:" + s);if (s.equals("886")) {break;}}}
}
UDP的接收端可以接收很多发送端的消息,接收端只负责接收数据包,无所谓是哪个发送端的数据包。
5.3 TCP通信
Java对基于TCP协议的网络提供了良好的封装,使用Socket对象来代表两端的通信端口。TCP利用IO流实现数据的传输。
客户端:TCP协议基于请求响应模式,第一次主动发起的程序被称为客户端程序(Client)。
服务端:第一次通讯中等待连接的程序被称为服务器端(Sercer)程序。
5.3.1 TCP客户端实现
Java为客户端提供了Socket类,构造方法如下
方法名 | 说明 |
---|---|
Socket(InetAddress address, int port) | 创建流套接字并将其连接到指定IP指定端口号 |
Socket(String host,int port) | 创建流套接字并将其连接到指定的主机上的指定端口号 |
相关方法
方法名 | 说明 |
---|---|
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
示例代码
public class ClientDemo {public static void main(String[] args) {try {//创建socket对象,指定本机Ip,指定端口号9999Socket socket = new Socket("127.0.0.1",9999);//使用getOutputStream方法获得OutputStream对象OutputStream os = socket.getOutputStream();//将低级字节流封装成高级的打印流PrintStream out = new PrintStream(os);//打印流输出并刷新out.print("我来啦".getBytes());out.flush();} catch (Exception e) {e.printStackTrace();}}
}
5.3.2 TCP服务端实现
构造方法
方法名 | 说明 |
---|---|
ServerSocket(int port) | 创建绑定到指定端口的服务器套接字 |
相关方法
方法名 | 说明 |
---|---|
Socket accept() | 监听要连接到此的套接字并接受 |
注意事项
- accept方法是阻塞的,作用就是等待客户端链接
- 客户端创建对象并连接服务器,此时是通过三次握手协议,保证跟服务器之间的连接
- 针对客户端来讲,是往外写的,所以是输出流
针对服务器来讲,是往里读的,所以是输入流- read方法也是阻塞的
- 客户端在关流的时候,还多了一个往服务器写结束标记的动作
- 最后一步断开连接,通过四次挥手协议保证连接终止
示例代码
public class SeverDemo {public static void main(String[] args) {try {//创建绑定到指定端口的服务器对象ServerSocket ss = new ServerSocket(9999);//Socket accept() 侦听要连接到此套接字并接受它Socket socket = ss.accept();//获取输入流读数据InputStream is = socket.getInputStream();//将低级字节流封装成缓冲字符流BufferedReader br = new BufferedReader(new InputStreamReader(is));String s;if ((s = br.readLine()) != null) {System.out.println(socket.getRemoteSocketAddress()+"说了:"+s);}} catch (IOException e) {e.printStackTrace();}}
}
5.3.3 TCP通信程序练习
案例需求
客户端:发送数据,接受服务器反馈
服务器:收到消息后给出反馈
案例分析
- 客户端创建对象,使用输出流输出数据
- 服务端创建对象,使用输入流接受数据
- 服务端使用输出流给出反馈数据
- 客户端使用输入流接受反馈数据
代码实现
import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; /**客户端*/ public class ClientDemo {public static void main(String[] args) {try {//创建socket对象,指定对方Ip为本机Ip,指定对方端口号9999Socket socket = new Socket("127.0.0.1", 9999);//使用getOutputStream方法获得OutputStream对象OutputStream os = socket.getOutputStream();//将低级字节流封装成高级的打印流PrintStream out = new PrintStream(os);Scanner sc = new Scanner(System.in);while (true) {System.out.println("请说");String s = sc.nextLine();//打印流输出并刷新out.print(s+"\n");out.flush();if (s.equals("88")) {break;}}} catch (Exception e) {e.printStackTrace();}} }import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; /**服务端*/ public class SeverDemo {public static void main(String[] args) {try {//创建绑定到指定端口的服务器对象ServerSocket ss = new ServerSocket(9999);//Socket accept() 侦听要连接到此套接字并接受它Socket socket = ss.accept();//获取输入流读数据InputStream is = socket.getInputStream();//将低级字节流封装成缓冲字符流BufferedReader br = new BufferedReader(new InputStreamReader(is));String s;while ((s = br.readLine()) != null) {System.out.println(socket.getRemoteSocketAddress() + "说了:" + s);if (s.equals("88")) {break;}}} catch (IOException e) {e.printStackTrace();}} }
5.3.4 TCP程序上传文件
案例需求
客户端:数据来自于本地文件,接收服务器反馈
服务器:接收到的数据写入本地文件,给出反馈
案例分析
- 创建客户端对象,创建输入流对象指向文件,每读一次数据就给服务器输出一次数据,输出结束后使用shutdownOutput()方法告知服务端传输结束
- 创建服务器对象,创建输出流对象指向文件,每接受一次数据就使用输出流输出到文件中,传输结束后。使用输出流给客户端反馈信息
- 客户端接受服务端的回馈信息
相关方法
方法名 说明 void shutdownInput() 将此套接字的输入流放置在“流的末尾” void shutdownOutput() 禁止用此套接字的输出流 代码实现
/**客户端*/ public class ClientDemo {public static void main(String[] args) {try {//创建客户端对象,目标IP为本机,端口设定9999Socket socket = new Socket("127.0.0.1",9999);//本地流读取文件BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\图片\\Saved Pictures\\壁纸.png"));//网络流OutputStream os = socket.getOutputStream();BufferedOutputStream bos = new BufferedOutputStream(os);//通过网络将数据写到服务器中byte[] bys = new byte[1024];int len;while ((len = bis.read(bys)) != -1) {bos.write(bys, 0, len);}bos.flush();//给服务器一个结束标记,告诉服务器文件已经传输完毕socket.shutdownOutput();//读取回传信息BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));String line;while ((line = br.readLine()) != null) {System.out.println(line);}//关闭IO流br.close();bos.close();bis.close();} catch (Exception e) {e.printStackTrace();}} } /**服务端*/ public class SeverDemo {public static void main(String[] args) {try {//创建绑定到指定端口的服务器对象ServerSocket ss = new ServerSocket(9999);//Socket accept() 侦听要连接到此套接字并接受它Socket socket = ss.accept();//获取输入流读数据//将网络流封装成字节缓冲输入流BufferedInputStream br = new BufferedInputStream(socket.getInputStream());//创建字节缓冲输出流对象将文件写到本地BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\图片\\Saved Pictures\\copy.png"));byte[] bys = new byte[1024];int len;while ((len = br.read(bys)) != -1) {bos.write(bys, 0, len);}//写入完成回传信息BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));bw.write("上传成功");//关闭IO流br.close();bw.close();bos.close();} catch (Exception e) {e.printStackTrace();}} }
5.3.5 TCP程序服务器优化——循环
刚才的需求中,服务器只能处理一个客户端请求,接收完一个文件后就关闭了。可以采用循环解决这个问题。
public class SeverDemo {public static void main(String[] args) {try {//创建绑定到指定端口的服务器对象ServerSocket ss = new ServerSocket(9999);while (true) {//将accept方法放在循环内,可以重复接收多个文件//Socket accept() 侦听要连接到此套接字并接受它Socket socket = ss.accept();//获取输入流读数据//将网络流封装成字节缓冲输入流BufferedInputStream br = new BufferedInputStream(socket.getInputStream());//创建字节缓冲输出流对象将文件写到本地BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\图片\\Saved Pictures\\copy.png"));byte[] bys = new byte[1024];int len;while ((len = br.read(bys)) != -1) {bos.write(bys, 0, len);}//写入完成回传信息BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));bw.write("上传成功");//完成后关闭IO流bw.close();bos.close();br.close();}} catch (Exception e) {e.printStackTrace();}}
}
5.3.6 TCP程序服务器优化——UUID
使用循环优化后,再次上传文件会覆盖掉刚上传后的文件,可以使用UUID.randomUUID()方法生成随机的文件名。
public class SeverDemo {public static void main(String[] args) {try {//创建绑定到指定端口的服务器对象ServerSocket ss = new ServerSocket(9999);while (true) {//将accept方法放在循环内,可以重复接收多个文件//Socket accept() 侦听要连接到此套接字并接受它Socket socket = ss.accept();//获取输入流读数据//将网络流封装成字节缓冲输入流BufferedInputStream br = new BufferedInputStream(socket.getInputStream());//创建字节缓冲输出流对象将文件写到本地BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\图片\\Saved Pictures\\"+ UUID.randomUUID().toString()+".png"));byte[] bys = new byte[1024];int len;while ((len = br.read(bys)) != -1) {bos.write(bys, 0, len);}//写入完成回传信息BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));bw.write("上传成功");//完成后关闭IO流bw.close();bos.close();br.close();}} catch (Exception e) {e.printStackTrace();}}
}
5.3.7 TCP程序服务器优化——多线程
循环和UUID可以解决服务器处理多个客户端请求,但仍无法同时和多个客户端进行通信。可以开启多线程来处理。
/**线程任务*/
public class ThreadSocket implements Runnable {private Socket acceptSocket;ThreadSocket(Socket accept) {this.acceptSocket = accept;}@Overridepublic void run() {while (true) {BufferedOutputStream bos = null;try {BufferedInputStream bis = new BufferedInputStream(acceptSocket.getInputStream());bos = new BufferedOutputStream(new FileOutputStream("D:\\图片\\Saved Pictures\\"+ UUID.randomUUID().toString() + ".png"));byte[] bys = new byte[1024];int len;while ((len = bis.read(bys)) != -1) {bos.write(bys, 0, len);}BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));bw.write("上传成功");bw.flush();//完成后关闭IO流bw.close();bis.close();} catch (Exception e) {e.printStackTrace();}finally {if (bos != null) {try {bos.close();}catch (IOException e) {e.printStackTrace();}}if (acceptSocket != null){try {acceptSocket.close();}catch (IOException e) {e.printStackTrace();}}}}}
}
/**服务器端*/
public class SeverDemo {public static void main(String[] args) {try {ServerSocket ss = new ServerSocket(9999);while (true) {Socket accept = ss.accept();ThreadSocket ts = new ThreadSocket(accept);new Thread(ts).start();}} catch (IOException e) {e.printStackTrace();}}
}
5.3.8 TCP程序服务器优化——线程池
使用多线程资源消耗过大,优化使用线程池。
public class ClientDemo {public static void main(String[] args) {try {//创建客户端对象,目标IP为本机,端口设定9999Socket socket = new Socket("127.0.0.1",9999);//本地流读取文件BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\图片\\Saved Pictures\\原神壁纸1.png"));//网络流OutputStream os = socket.getOutputStream();BufferedOutputStream bos = new BufferedOutputStream(os);//通过网络将数据写到服务器中byte[] bys = new byte[1024];int len;while ((len = bis.read(bys)) != -1) {bos.write(bys, 0, len);}bos.flush();//给服务器一个结束标记,告诉服务器文件已经传输完毕socket.shutdownOutput();//读取回传信息BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));String line;while ((line = br.readLine()) != null) {System.out.println(line);}//关闭IO流br.close();bos.close();bis.close();} catch (Exception e) {e.printStackTrace();}}
}
六、 日志
6.1 概述
之前记录程序运行采用输出语句将信息打印在控制台,但不能永久保存而且不方便。程序中的日志可以用来记录程序在运行时的细节,并且可以永久保存。日志和输出语句的区别如下:
输出语句 | 日日志技术 | |
---|---|---|
取消日志 | 需要修改代码,灵活性较差 | 不需要修改代码,灵活性比较好 |
输出位置 | 只能是控制台 | 可以将日志信息写入到文件或者数据库中 |
多线程 | 和业务代码处于一个线程中,性能较差 | 多线程方式记录日志,不影响业务代码的性能 |
6.2 体系结构
日志规范:一些接口,提供给日志的实现框架设计的标准。
常见的日志规范有:
- Commons Logging
- Simple Logging Facade for Java
日志框架:第三方已经做好的日志记录实现代码,可以直接拿去用。
常见的日志实现框架有:
- Log4J
- Logback
6.3 Logback
Logback是由log4j创始人设计的另一个开源日志组件,性能更好,Logback是基于slf4j的日志规范实现的框架。
Logback主要分为三个技术模块
- logback-core:是其他两个模块的基础
- logback-classic:是log4j的改良版,完整实现了slf4j API
- logback-access:模块与Tomcat和Jetty等Servlet容器集成,以提供HTTP访问日志功能。
通过使用logback,我们可以控制日志信息输送的目的地是控制台、文件等位置。
我们也可以控制每一条日志的输出格式。
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
这些可以通过一个配置文件来灵活地配置,不需要修改应用的代码。部分配置方法如下:
日志级别
级别程度依次是:TRACE<DEBUG<INFO<WARRN<ERROR,默认级别是debug(忽略大小写),对应其方法。
作用:用于控制系统中哪些日志级别是可以输出的,只输出级别不低于设定级别的日志信息。一般只输出ERROR。
ALL和OFF分别是打开全部日志信息和关闭全部日志信息。
6.4 入门案例
使用步骤
- 导入logback的相关jar包
- 编写logback配置文件
- 在代码中获取日志的对象
- 按照级别设置记录日志信息
代码示例
// 测试类 public class Test01 {//获取日志的对象private static final Logger LOGGER = LoggerFactory.getLogger(Test01.class);public static void main(String[] args) {//1.导入jar包//2.编写配置文件//3.在代码中获取日志的对象//4.按照日志级别设置日志信息LOGGER.debug("debug级别的日志");LOGGER.info("info级别的日志");LOGGER.warn("warn级别的日志");LOGGER.error("error级别的日志");} }
七、 枚举
7.1 概述
枚举可以间接的表示一些固定的值比如星期、季节、月份等,枚举将变量的值一一列出来,变量的值只能限制在列举出来的值范围内。
7.2 定义格式
定义格式
public enum s{enum1,emum2,enum3;
}
//注意:定义枚举要用关键字enum
示例代码
public enum Season{SPRING,SUMMER,AUTUMN,WINTER;
}
7.3 特点
- 所有枚举类都是Enum的子类
- 我们可以通过“枚举类名.枚举项名”去访问指定的枚举项
- 每一个枚举项其实就是该枚举的一个对象
- 枚举也是一个类,也可以去定义成员变量
- 枚举类的第一行上必须是枚举项,最后一个枚举项后的分号是可以省略的,但是如果枚举类有其他的东西,这个分号就不能省略。建议不省略
- 枚举类可以有构造器,但必须是private的,它的默认也是private的。
- 枚举项的用法比较特殊:枚举(“”);
- 枚举类也可以有抽象方法,但是枚举项必须重写该方法
示例代码
public enum Season {/*** 春天*/SPRING("春"){@Overridepublic void show() {System.out.println(this.name());}},/*** 夏天*/SUMMER("夏"){@Overridepublic void show() {System.out.println(this.name());}},/*** 秋天*/AUTUMN("秋"){@Overridepublic void show() {System.out.println(this.name());}},/*** 冬天*/WINTER("冬"){@Overridepublic void show() {System.out.println(this.name());}};Season(String name) {this.name = name;}private String name;public abstract void show();
}/**测试类*/
public class EnumDemo {public static void main(String[] args) {//我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项//每一个枚举项其实就是该枚举的一个对象Season spring = Season.SPRING;spring.show();System.out.println(Season.SUMMER);}
}
7.4 方法
方法名 | 说明 |
---|---|
String name() | 获取枚举项的名称 |
int ordinal() | 返回枚举项在枚举类中的索引值 |
int compareTo(E o) | 比较两个枚举项,返回的是索引值的差 |
String toString() | 返回枚举常量的名称 |
static T valueOf(Class type,String name) | 获取指定枚举类中的指定名称的枚举值 |
values() | 获得所有的枚举项 |
示例代码
public class EnumDemo {public static void main(String[] args) {//String name()获取枚举项的名称String name = Season.SPRING.name();System.out.println(name);// int ordinal()返回枚举项在枚举类中的索引值,从0开始计数
// int index = Season.WINTER.ordinal();int index = Season.SPRING.ordinal();System.out.println(index);// int compareTo(E o)比较两个枚举项,返回的是索引值的差//compareTo左边减右边int i = Season.SPRING.compareTo(Season.AUTUMN);System.out.println(i);// String toString()返回枚举常量的名称String s = Season.SPRING.toString();System.out.println(s);// static<T> T valueOf(Class<T> type,String name)// 获取指定枚举类中的指定名称的枚举值Season season = Season.valueOf(Season.class, "SUMMER");System.out.println(season);// values()获得所有的枚举项Season[] values = Season.values();for (Season value : values) {System.out.println(value.toString());}}/*** 春天*/SPRING("春"){@Overridepublic void show() {System.out.println(this.name());}},/*** 夏天*/SUMMER("夏"){@Overridepublic void show() {System.out.println(this.name());}},/*** 秋天*/AUTUMN("秋"){@Overridepublic void show() {System.out.println(this.name());}},/*** 冬天*/WINTER("冬"){@Overridepublic void show() {System.out.println(this.name());}};Season(String name) {this.name = name;}private String name;public abstract void show();
}/**测试类*/
public class EnumDemo {public static void main(String[] args) {//我们可以通过"枚举类名.枚举项名称"去访问指定的枚举项//每一个枚举项其实就是该枚举的一个对象Season spring = Season.SPRING;spring.show();System.out.println(Season.SUMMER);}
}
7.4 方法
方法名 | 说明 |
---|---|
String name() | 获取枚举项的名称 |
int ordinal() | 返回枚举项在枚举类中的索引值 |
int compareTo(E o) | 比较两个枚举项,返回的是索引值的差 |
String toString() | 返回枚举常量的名称 |
static T valueOf(Class type,String name) | 获取指定枚举类中的指定名称的枚举值 |
values() | 获得所有的枚举项 |
示例代码
public class EnumDemo {public static void main(String[] args) {//String name()获取枚举项的名称String name = Season.SPRING.name();System.out.println(name);// int ordinal()返回枚举项在枚举类中的索引值,从0开始计数
// int index = Season.WINTER.ordinal();int index = Season.SPRING.ordinal();System.out.println(index);// int compareTo(E o)比较两个枚举项,返回的是索引值的差//compareTo左边减右边int i = Season.SPRING.compareTo(Season.AUTUMN);System.out.println(i);// String toString()返回枚举常量的名称String s = Season.SPRING.toString();System.out.println(s);// static<T> T valueOf(Class<T> type,String name)// 获取指定枚举类中的指定名称的枚举值Season season = Season.valueOf(Season.class, "SUMMER");System.out.println(season);// values()获得所有的枚举项Season[] values = Season.values();for (Season value : values) {System.out.println(value.toString());}}
}
day16多线程网络编程日志枚举相关推荐
- Linux多线程网络编程要义丨epoll与reactor原理
linux多线程网络编程要义 1. epoll原理剖析 2. 单reactor原理以及应用 3. 多reactor原理以及应用 [Linux服务器系列]Linux多线程网络编程要义丨epoll与rea ...
- 【Linux服务器开发系列】详解多线程网络编程丨百分百干货分享丨学到就是赚到
90分钟搞懂多线程网络编程模型 1. 网络编程关注的问题 2. 网络编程的几种模型reactor,one loop per thread及其变种 3. skynet,redis,nginx,memca ...
- Java高并发与多线程网络编程
目录 一. 基础 1. 线程介绍 2. 创建并启动线程 3. 函数式接口编程 4. Thread 构造器 5. 守护线程 线程关系 6. join 7. interrupt 8. 优雅的结束线程 9. ...
- HSHA多线程网络编程模型介绍
我在上一篇的分享<Leader/Follower多线程网络模型介绍>中详细介绍了LF多线程网络模型的原理和代码分析.它的思路是减少线程上下文切换和数据拷贝时间,通过角色转换,来提高处 ...
- python多线程网络编程_python网络编程之线程
一 .背景知识 1.进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程.程序和进程的区别就在于:程序是指令 ...
- python多线程网络编程_python之网络编程-多线程
死锁现象,递归锁 所谓死锁:是指两个或两个以上的进程或线程在执行过程中,因为争夺资源而造成的一种互相等待的现象,若无外力作用,他们将无法推进下去,此时称系统处于死锁状态或系统产生了死锁,这些永远在相互 ...
- QT多线程网络编程程序崩溃问题与解决
环境:Ubuntu14.04,Qt5.5 平台:QtCreator 场景:有以下三个线程:1.gstreamer循环取摄像头视频帧,25fps:2.HTTP循环请求消息并显示在QDoubleSpinB ...
- java为什么需要网络编程,2022最新
跪求!阿里P9手写的这份1530页的Java核心编程技术手册的实现原理?否想知道对象关系映射(ORM)框架的实现原理?首线程池如何实现?先给大家展示一下这份手册的目录部分,需要获取的小伙伴可以直接转发 ...
- 多线程+SOCKET编程实现qq群聊的服务端和客户端
多线程+SOCKET编程实现qq群聊的服务端和客户端 标签(空格分隔): 多线程 网络编程 线程同步 一.设计思路 1.服务端 每来一个客户端连接,服务端起一个线程维护: 将收到的消息转发给所有的客户 ...
最新文章
- Java文件流应用:剪切文件
- 【主题演讲】探索云、视频会议,编解码的奥妙
- 连接moogDB数据库
- LC67---删除有序链表中重复的元素---牛客---JAVA
- 使用阿里云对象存储OSS收藏老婆新垣结衣日常照
- vue 循环遍历 搜寻资料
- 二维数组名作为实参或者形参
- 数学建模--预测方法
- html swf转mp4,《swf转换成mp4及高清视频的方法》.docx
- sf授权php,授权系统全解源码(支持分子系统)【原完整版】
- OpenCL 简单概念
- html table相同值合并单元格,ElementUI表格列相同值自动合并单元格( 单列 )
- 软件测试计划模板--云闪付平台
- 电脑桌面便签小工具下载,专业桌面待办便签软件
- expdp/impdp 使用总结
- 运算符(一元 二元)
- 大小限制_微信传文件有大小限制怎么办?教你3秒把100MPPT压缩成10M
- 根据year年的第week周,查询第week周的起止时间
- java字符串长度(java字符串长度压缩)
- \t\tsizeof(char*)几个字节?
热门文章
- 【解决方案】聚焦两会-国标EasyGBS流媒体平台在2021年两会重点工作环保充电桩的视频监控应用
- 藏在GPT背后的治理分歧:那些赞同和反对的人们|AI百态(下篇)
- gulp-less解决遇到错误停止执行task
- UVA-10929-You can say 11(秦九昭算法+同余与模算术)
- 设置firefox背景为黑夜模式
- 【参赛作品14】Windows安装华为openGauss数据库——openGauss基于x86架构openEuler虚拟机的编译以及JDBC的连接
- 【来日复制粘贴】用高级筛选和函数公式拆分数据列表
- nofollow标签的作用 nofollow标签添加方法
- go实现时针分针夹角问题
- 车辆的纵向控制之标定