Java高级(线程、定时器、网络编程)

1、多线程

1.1什么是线程

线程(thread) 是一个程序内部的一条执行路径

public class Main {
public static void main(String[] args) {
for (int i= 1 ; i < 100 ; i++){
System.out.println(i);
}}}
  • 程序中如果只有一条执行路径,那么这个程序就是单线程程序
  • 多线程:从硬件上实现多条执行流程的技术

1.2多线程的创建

Thread类 代表线程

多线程的实现方式一: 继承Thread类
  • 定义一个子类继承线程类Thread类
  • 重写run() 方法
  • 创建该子类的实例对象
  • 调用该对象的start()方法 启动线程,执行run方法
public class ThreadDemo1 {
public static void main(String[] args) {
//3 创建线程对象
Thread t = new MyThread();
//4 启动线程
t.start();
for (int i= 0 ; i < 100;i++){
System.out.println("主线程执行。。。。。。。。。。。");
}}}
// 定义一个线程类,继承Thread类
class MyThread extends Thread{
// 重写run方法 在run里面定义线程需要完成的功能
@Override
public void run() {
for (int i= 0 ; i < 100;i++){
System.out.println("mythread 线程执行。。。。。。。。。。。");
}}}

方式一的优缺点:

  • 优点:编码简单
  • 缺点:线程类已经继承了Thread类,因为Java是单继承,所以无法继承其他类,不利于扩展
思考:

为什么不直接调用run方法,而是调用start方法启动线程?

  • 直接调用run方法 此时他就是一个普通方法的执行,因此我们的程序就是单线程程序
  • 只有调用start方法 才是启动一个新的线程执行
实现方式二:实现 Runnable 接口的类。 该类然后实现 run 方法
Thread()分配新的 Thread 对象。
Thread(Runnable target)分配新的 Thread 对象。

步骤:

  • 定义一个线程任务类 实现Runnable接口,重写run方法
  • 创建线程任务类的对象
  • 把线程任务类对象交给Thread处理
  • 调用Thread类的start方法 启动线程
public class ThreadDemo2 {
public static void main(String[] args) {
// 2 创建线程任务类的对象
Runnable r = new MyRunnable();
//3 把线程任务类对象交给Thread处理
Thread t = new Thread(r);
//4 调用Thread类的start方法 启动线程
t.start();
for (int i= 0 ; i < 100;i++){
System.out.println("主线程执行。。。。。。。。。。。");
}
}
}
//1 定义一个线程任务类 实现Runnable接口,重写run方法
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i= 0 ; i < 100;i++){
System.out.println("MyRunnable 线程执行。。。。。。。。。。。");
}
}
}

优缺点:
优点:线程任务类只是实现接口,可以继续继承类或实现接口 扩展性强
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。
线程创建的不同写法:

//使用匿名内部类形式 创建线程
public class ThreadDemo3 {
public static void main(String[] args) {
// 实现Runnable接口
Runnable r = new Runnable() {
@Override
public void run() {
for (int i= 0 ; i < 100;i++){
System.out.println("MyRunnable 线程执行。。。。。。。。。。。");
}
}
};
Thread t = new Thread(r);
t.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i= 0 ; i < 100;i++){
System.out.println("子线程2执行。。。。。。。。。。。");
}
}
}).start();
//继承Thread类的方法
new Thread(()->{
for (int i= 0 ; i < 100;i++){
System.out.println("子线程3执行。。。。。。。。。。。");
}
}).start();
}
}
实现方式三:jdk5新增的实现方式 实现Callable接口

前两种方式都存在的问题:
他们重写run方法 都不能直接返回结果;
不适合需要返回线程执行结果的业务场景

解决方案:

jdk5 提供了Callable接口和FutureTask接口来实现
这种方式的优点:可以的带线程执行的结果
实现方式:利用Callable接口和FutureTask接口

  1. 得到任务对象
  2. 定义一个Callable接口实现,重写call方法,编写需要做的事情
  3. 用FUtureTask把Callable对象封装成线程任务对象
  4. 把线程任务对象交给Thread处理
  5. 调用Thread的start方法启动线程 执行任务
  6. 执行完毕后,通过FutureTask的get方法 获取任务执行后的返回结果
FutureTask(Callablecallable)创建一个 FutureTask ,在运行时将执行给定的Callable 。
Vget() 获取线程执行call方法返回的结果
public class ThreadDemo4 {
public static void main(String[] args) {
// 3 创建任务对象
Callable<String> call = new MyCallable(100);
//4 用FUtureTask把Callable对象封装成线程任务对象
FutureTask<String> f = new FutureTask<>(call);
//5把线程任务对象交给Thread处理
Thread t = new Thread(f);
//6 启动线程
t.start();
// 3 创建任务对象
Callable<String> call1 = new MyCallable(100);
//4 用FUtureTask把Callable对象封装成线程任务对象
FutureTask<String> f1 = new FutureTask<>(call1);
//5把线程任务对象交给Thread处理
Thread t1 = new Thread(f1);
//6 启动线程
t1.start();
//获得线程的执行结果
try {
// 如果f1任务没有执行完毕 这里的代码会已知等待,指导线程任务完成才提取结果
String s = f.get();
System.out.println("第一个线程执行的结果:" +s);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
try {
// 如果f1任务没有执行完毕 这里的代码会已知等待,指导线程任务完成才提取结果
String s1 = f1.get();
System.out.println("第二个线程执行的结果:" +s1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
//1. 定义一个Callable接口实现,重写call方法,编写需要做的事情
class MyCallable implements Callable<String>{
private int n;
public MyCallable(int n) {
this.n = n;
}
//重写call方法
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= n ; i++) {
sum +=i;
}
return "子线程执行的结果是:" + sum;
}
}

采用匿名内部类的写法

public class ThreadDemo5 {
public static void main(String[] args) {
FutureTask<String> f = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100 ; i++) {
sum +=i;
}
return "子线程执行的结果是:" + sum;
}
});
Thread t = new Thread(f);
try {
String s = f.get();
System.out.println(s);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
t.start();}}

方式三的优缺点:
优点:线程任务类只是实现了接口,可以继承类或实现别的接口,扩展性强 ;可以在线程执行完毕之
后获取线程的执行结果
缺点:编码复杂一点

2、Thread的常用方法

当很多线程在执行的时候 ,如何区分这些线程?
在这里插入图片描述

public class ThreadDemo6 {
public static void main(String[] args) {
Thread t1 = new MyThread1("1号");
t1.start();
System.out.println(t1.getName());
Thread t2 = new MyThread1();
t2.setName("2号");
t2.start();
System.out.println(t2.getName());
Thread main = Thread.currentThread();
System.out.println(main.getName());
main.setName("这是主线程");
for (int i =0 ; i < 5 ; i ++){
System.out.println(Thread.currentThread().getName()+"输出"+ i);
}
}
}
class MyThread1 extends Thread{
public MyThread1() {
}
public MyThread1(String name) {
//为当前线程对象设置名称 传递给父类构造器 初始化名称
super(name);
}
@Override
public void run() {
for (int i =0 ; i < 5 ; i ++){
System.out.println(Thread.currentThread().getName()+"输出"+ i);}}}

在这里插入图片描述

public class ThreadDemo7 {
//main的执行 是由主线程负责调用的
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i <=5 ; i++) {
System.out.println(Thread.currentThread()+"输出:"+i);
if (i==3){
// 让当前线程进入休眠状态 休眠3秒 继续执行
// 这行代码是经理让加 如果用户愿意付钱 我就注释掉
//Thread.sleep(3000);
}}}}

3、线程安全

3.1线程安全问题是什么、发生的原因

线程安全问题:
多个线程同时操作同一个共享资源(数据)的时候 ,可能会出现业务安全问题,称为线程安全问题
案例:
需求:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元。
分析:
0 账户类,代表的夫妻二人共享的账户
1 需要定义一个线程类,线程类 可以处理账户对象
2 创建2个线程 传入同一个账户对象
3 启动2个线程 去同一个账户对象中取钱10万

public class Account {
private String cardId;//账户id
private double money;//账户余额
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"cardId='" + cardId + '\'' +
", money=" + money +
'}';
}
//取钱的方法
public void drwaMoney(Double money){
//1 先获取钱账户 线程名字就表示取钱的人
String name = Thread.currentThread().getName();
// 2 判断账户余额
if (this.money >= money){
//取钱
System.out.println(name + "来取钱,取出了:"+money);
//更新账户余额
this.money -= money;
System.out.println(name + "取钱后的剩余金额:" + this.money);
}else{
System.out.println(name + "来取钱,余额不足");
}
}
}
//取钱的线程
public class DrawThread extends Thread{
// j接受和处理账户对象
private Account account;
public DrawThread(Account account,String name) {
super(name);
this.account = account;
}
@Override
public void run() {
//小名 小红 取钱
account.drwaMoney(100000.00);
}
}
public class ThreadDemo8 {
public static void main(String[] args) {
Account acc = new Account("icbc-111",100000.00);
// 创建2个线程对象 代表小明和小红 取钱
new DrawThread(acc,"小明").start();
new DrawThread(acc,"小红").start();
}
}

4、线程同步

4.1.线程 同步的思想

解决线程安全的问题:
如何保证线程安全?
让多个线程实现先后一次访问共享资源,这样就解决了线程安全的问题
线程同步的核心思想:
加锁。把共享资源进行上锁,每次只能一个线程进入访问完毕之后 释放锁,然后才能是的其他的线程进

4.2方式一:同步代码块

作用:把出现线程安全的核心代码给上锁
原理:每次只能有一个线程进入

synchronized(同步锁对象){
操作共享资源的代码
}
//取钱的方法
public void drwaMoney(Double money){
//1 先获取钱账户 线程名字就表示取钱的人
String name = Thread.currentThread().getName();
// 同步代码块 this=account 共享账户
//String a =" aaa";
synchronized (this){
// 2 判断账户余额
if (this.money >= money){
//取钱
System.out.println(name + "来取钱,取出了:"+money);
//更新账户余额
this.money -= money;
System.out.println(name + "取钱后的剩余金额:" + this.money);
}else{
System.out.println(name + "来取钱,余额不足");}}}}

锁对象:
理论上:锁对象只要对于当前同时执行的线程来说是同一个对象即可
锁对象用任意唯一的对象好不好呢?
不好,会影响到其他无关线程的执行。
锁对象的规范要求:
规范上:
建议使用共享资源作为锁对象
对于实例方法 建议使用this作为锁对象
对于静态方法建议使用字节码(类名.class)对象作为锁对象

4.3方式二:同步方法

作用:把出现线程安全的问题的方法给上锁
原理:每次只能有一个线程进入

修饰符 synchronized 返回值类型 方法名称(形参列表){
操作共享资源的代码
}
//取钱的方法
public synchronized void drwaMoney(Double money){
//1 先获取钱账户 线程名字就表示取钱的人
String name = Thread.currentThread().getName();
// 同步代码块 this=account 共享账户
// 2 判断账户余额
if (this.money >= money){
//取钱
System.out.println(name + "来取钱,取出了:"+money);
//更新账户余额
this.money -= money;
System.out.println(name + "取钱后的剩余金额:" + this.money);
}else {
System.out.println(name + "来取钱,余额不足");
}
}

使用synchronized修饰的方法 称为同步方法
同步方法的底层原理:
同步方法的底层也是隐藏了一个锁对象,只是锁的范围是整个方法代码
如果方法是实例方法,同步方法的默认锁对象就是this
如果是静态方法,同步方法的默认锁对象就是类对象 类名.class
思考:同步方法和同步代码块那个更好一点呢?
同步代码块锁的范围更小,同步方法的锁的范围更大

4.4 方式三:Lock锁

为了更加清晰的表达如何枷锁和释放锁,jdk5以后提供了一个新的锁对象 Lock 更加灵活 方便
Lock实现提供比使用synchronized方法和语句可以获得更管的锁定操作
Lock是接口不能直接实例化,采用他的实现类ReentrantLock来构建Lock锁对象
在这里插入图片描述

//取钱的方法
// final 修饰后 的
private final Lock lock = new ReentrantLock();
public void drwaMoney(Double money){
//1 先获取钱账户 线程名字就表示取钱的人
String name = Thread.currentThread().getName();
// 同步代码块 this=account 共享账户
lock.lock();//枷锁
// 2 判断账户余额
if (this.money >= money){
//取钱
System.out.println(name + "来取钱,取出了:"+money);
//更新账户余额
this.money -= money;
System.out.println(name + "取钱后的剩余金额:" + this.money);
}else {
System.out.println(name + "来取钱,余额不足");
}
lock.unlock();//释放锁
}
}

5、线程通信(了解)

什么是线程通信,如何实现?
所谓的线程通信就是线程间相互发送数据,线程间共享一个资源即可实现线程通信
常见形式:
通过共享一个数据的方式实现
根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做
线程通信常见的实际的使用场景:
生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费生产者锁生产的数据
要求:生产者线程生产完数据后 唤醒消费者,然后等待自己,消费者消费完数据后唤醒生产者,生产数
据,然后自己等待
案例:模拟客服系统,系统可以不断的接入电话和分发给客服
在这里插入图片描述

public class CallSystem {
//定义一个变量,记录当前呼入进来的电话
public static int num = 0;// 最多只能接听一个点话
//接入电话
public synchronized static void call(){
num++;
System.out.println("成功接入一个客户,等待分发--------");
//唤醒一个客服
try {
CallSystem.class.notify();
//让当前线程对象进入等待状态
CallSystem.class.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//分发电话
public synchronized static void receive(){
String name = Thread.currentThread().getName();
if (num == 1){
System.out.println("此电话已经分发给客服并接听完毕------");
num--;
//唤醒别人 :1个
try {
CallSystem.class.notify();
CallSystem.class.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}else{
try {
CallSystem.class.notify();
CallSystem.class.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
// 模拟客户线程
public class CallThread extends Thread{
@Override
public void run() {
while (true){
CallSystem.call();
}
}
}
// 模拟我们客服线程
public class ReceiveThread extends Thread{
@Override
public void run() {
while (true){
CallSystem.receive();
}
}
}
public class TestDemo {
public static void main(String[] args) {
// 1 生产者线程,负责不断的接受打进来的电话
CallThread call= new CallThread();
call.start();
//2 消费者线程 客服 每个客服每次只能接听一个电话
ReceiveThread receive = new ReceiveThread();
receive.start();
}
}

5.1.线程的调度模型

分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么随机选择一个,优先
级高的线程获取CPU的执行权的几率就会大一些。
Java使用的是抢占式调度模型。
在这里插入图片描述

public class ThreadDemo9 {
public static void main(String[] args) {
ThreadPriority t1 = new ThreadPriority();
t1.setName("高铁");
ThreadPriority t2 = new ThreadPriority();
t2.setName("飞机");
ThreadPriority t3 = new ThreadPriority();
t3.setName("汽车");
t1.setPriority(10);
t2.setPriority(5);
t3.setPriority(1);
t1.start();
t2.start();
t3.start();
}
}
class ThreadPriority extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
}

5.2.互斥锁

多线程间通信问题:
jdk5 新的特性 互斥锁
互斥锁:指的是一次最多只能有一个线程持有锁,在jdk5之前 我们通常使用synchronized控制多个线程
对于共享资源的访问,而现在Lock提供了synchronized更广法的操作
线程同步: 使用Lock的lock和unlock
线程通信:ReentrantLock类的new Condition()方法 获得Condition对象await() 或者signal(),不同的
线程使用的不同的condition 这样就能区分唤醒的是哪一个线程。

public class LockDemo {
public static void main(String[] args) {
LockDemo ld = new LockDemo();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
ld.print1();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
ld.print2();
}
}
}).start();
new Thread(){
@Override
public void run() {
ld.print3();
}
}.start();
}
public void print1(){
System.out.print("山");
System.out.print("西");
System.out.print("晋");
System.out.print("中");
System.out.print("理");
System.out.print("工");
System.out.print("学");
System.out.print("院");
System.out.println();
}
public void print2(){
System.out.print("中");
System.out.print("北");
System.out.print("大");
System.out.print("学");
System.out.println();
}
public void print3(){
System.out.print("国");
System.out.print("防");
System.out.print("科");
System.out.print("技");
System.out.print("大");
System.out.print("学");
System.out.println();
}
}

以上代码存在线程安全的问题,要实现多线程间的通信,实现顺序输出:

public class LockDemo {
public static void main(String[] args) {
LockDemo ld = new LockDemo();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
ld.print1();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
ld.print2();
}
}
}).start();
new Thread(){
@Override
public void run() {
while (true){
ld.print3();
}
}
}.start();
}
//创建锁对象
Lock lock = new ReentrantLock();
// 通信条件
Condition c1 = lock.newCondition();
Condition c2 =lock.newCondition();
Condition c3 = lock.newCondition();
int flag= 1;//标记当前需要打印的方法的序号
public void print1(){
lock.lock();// 上锁
if (flag != 1){
try {
c1.await();//等待
flag=2;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("山");
System.out.print("西");
System.out.print("晋");
System.out.print("中");
System.out.print("理");
System.out.print("工");
System.out.print("学");
System.out.print("院");
System.out.println();
flag = 2;//表示下一个执行的方法的序号为2
c2.signal();//唤醒c2
lock.unlock();//释放锁
}
public void print2(){
lock.lock();
if (flag != 2){
try {
c2.await();//等待
flag = 3;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("中");
System.out.print("北");
System.out.print("大");
System.out.print("学");
System.out.println();
flag =3;
c3.signal();
lock.unlock();
}
public void print3(){
lock.lock();
if (flag != 3){
try {
c3.await();//等待
flag = 1;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.print("国");
System.out.print("防");
System.out.print("科");
System.out.print("技");
System.out.print("大");
System.out.print("学");
System.out.println();
flag =1;
c1.signal();
lock.unlock();
}
}

6、设计模式

Java中总共有23中设计模式
单例设计模式:在系统中 始终只有该类的一个实例对象。
单例设计模式–饿汉式

public class SingletonDemo1 {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
class Singleton{
private static Singleton instance= new Singleton();
//将构造方法私有化
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}

单例设计模式–懒汉式

public class SingletonDemo1 {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
class Singleton{
private static Singleton instance;
//将构造方法私有化
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}

懒汉式存在线程安全的问题

public class SingletonDemo1 {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
class Singleton{
private static Singleton instance;
//将构造方法私有化
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}

7、线程安全的类

StringBuffer 线程安全 可变字符序列
StringBuilder 线程不安全 支持StringBuffer 的所有操作 效率更高
ArrayList LinkedArrayList HashSet TreeSet HashMap TreeMap 都是线程不安全
HashTable是一个线程安全的。 是哈希表的实现

8、线程相关的API

线程的调度模式:分时调度和抢占式调度

8.1线程优先级:1-10 默认:5

setPriority 设置优先级
getpriority 获取当前线程的优先级
在这里插入图片描述

public class TheadDemo1 {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep();
ThreadSleep ts2 = new ThreadSleep();
ThreadSleep ts3 = new ThreadSleep();
ts1.setName("曹操");
ts2.setName("刘备");
ts3.setName("孙权");
ts1.start();
ts2.start();
ts3.start();
}
}
class ThreadSleep extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
try {
Thread.sleep(1000);//使得当前线程进入休眠状态 单位是毫秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class TheadDemo2 {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();
tj1.setName("曹操");
tj2.setName("刘备");
tj3.setName("孙权");
tj1.start();
try {
tj1.join();//等待当前线程死亡 在继续执行后边的线程
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
tj2.start();
tj3.start();
}
}
class ThreadJoin extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
}

8.3线程的分类(理解)

  • Java中的线程分为两类: 一类是守护线程 一类是用户线程
  • 他们在几乎每个方面都是相同的,唯一的区别是判断jvm何时离开
  • 守护线程:是用来服务用户线程的,通过在start方法钱调用setDaemon(true)就可以将一个线程编程,java垃圾回收就是一个典型的守护线程
package cn.sxjzit.thread;
public class ThreadDemo3 {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
ThreadDaemon td3 = new ThreadDaemon();
td1.setName("关羽");
td2.setName("张飞");
// 设置当前主线程为刘备
td3.setName("刘备");
//设置守护线程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
td3.start();
}
}
class ThreadDaemon extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
}

当用户线程执行结束,可能守护线程还会继续执行一段时间,当用户线程结束的时候,此时无论守护线程是否执行结束,都将结束执行,jvm就会退出。

9、线程池(了解)

9.1线程池概述

  • 线程池就一个可以复用线程的技术

如果不适用线程池,用户没发起一个请求,后台就会创建一个新的线程来处理,下次新任务来的时候又要创建新的线程, 而创建线程的开销是很大的,这样会严重的影响系统的性能。

  • 线程池的工作原理:

在程序启动之处,就创建处一定数量的线程,将他们统一管理起来,管理者就称为线程池,当需要线程的时候,就从线程池里边来获取,当使用结束之后,线程不会被直接销毁,而是归还到线程池,共下一个请求使用。从而从达到线程复用的作用。

9.2.线程池如何实现呢?

谁代表线程池?
jdk5开始提供了代表线程池的接口:ExecutorService
如何得到线程池对象呢?
方式一:使用ExecutorService的实现类ThreadPoolExecutor可以创建一个线程池
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象
ThreadPoolExecutor构造函数:
在这里插入图片描述

ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
  • corePoolSize:指定线程池的线程的数量 值不能小于0
  • maximumPoolSize 指定线程池可支持的最大线程数 最大的数量>=corePoolSize
  • keepAliveTime:指定临时线程的最大存活时间 keepAliveTime>=0
  • unit 指定存活时间的单位(秒 分 时 天) unit时间单位
  • workQueue:任务队列 workQueue不能为null
  • threadFactory:指定用那个线程工厂来创建线程 threadFactory不能为null
  • handler:指定线程忙,任务满的时候,新任务来了该怎么办。handler不能为null

线程池常见的面试题:
临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列满了,并且还没有超过最大线程数,就可以创建临时线程什么时候会开始拒绝任务?核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝。

9.3线程池处理Runnable任务

ThreadPoolExecutor创建线程池对象

public static void main(String[] args) {
ExecutorService pool = new ThreadPoolExecutor(3,5,10, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),newThreadPoolExecutor.AbortPolicy());
}

ExecutorService常用方法
在这里插入图片描述
新任务的拒绝策列:
提供了四种预定义的处理程序策

  1. 在默认值 ThreadPoolExecutor.AbortPolicy 中 , 丢弃任务并抛出
    RejectedExecutionException 。 默认策略
  2. 在 ThreadPoolExecutor.CallerRunsPolicy 中 ,由主线程负责调用任务的run方法从而绕过线
    程池 直接执行
  3. 在 ThreadPoolExecutor.DiscardPolicy 中 ,丢弃任务 不抛出异常
  4. 在 ThreadPoolExecutor.DiscardOldestPolicy 中 ,抛弃队列中等待最久的任务 然后把当前任
    务加入到队列中
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//1 创建线程池
ExecutorService pool = new ThreadPoolExecutor(3,5,10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>
(5), Executors.defaultThreadFactory(),
new
ThreadPoolExecutor.AbortPolicy());
//2 把任务交给线程池处理
Runnable target = new MyRunnable();
<T>
Future<T>
submit(Callable<T>
task)
提交值返回任务以执行并返回表示任务的挂起结果
的Future2.3.线程池处理Callable任务
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
// pool.execute(target);
// pool.execute(target);
// pool.execute(target);
// pool.execute(target);
// pool.execute(target);
//创建临时线程
pool.execute(target);
pool.execute(target);
// pool.execute(target);
// pool.execute(target);
//不创建线程 执行拒绝策略
// pool.execute(target);
// pool.execute(target);
//关闭线程池
//pool.shutdown();
pool.shutdownNow();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"输出了:hello
world==>" + i);
}
System.out.println(Thread.currentThread().getName()+"本任务与线程绑定了,线程
进入休眠-------------");
try {
Thread.sleep(10000000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}

9.4线程池处理Callable任务

Futuresubmit(Callabletask)
提交值返回任务以执行并返回表示任务的挂起结果的Future

public class ThreadPoolDemo2 {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
//1 创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(3,5,10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//2 将任务交给线程池处理
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
Future<String> f5 = pool.submit(new MyCallable(500));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
System.out.println(f5.get());
pool.shutdown();
}
}
//1 定义一个任务类 实现Callable接口 返回一个结果
class MyCallable implements Callable<String>{
private int n ;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0 ;
for (int i = 0; i < n; i++) {
sum+= n;
}
return Thread.currentThread().getName() + "执行了1-"+n+"的求和,结果为:" +
sum;
}
}

9.5Executors工具类实现线程池

Executors得到线程池对象:
可以使用提供的静态方法创建不同类型的线程池
在这里插入图片描述

public class ThreadPoolDemo3 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
pool.execute(new MyRunnable());
}
}

10、并发 、并行

并发与并行: 正在运行的程序就时一个独立的进程,线程属于进程,多个线程其实时并发与并行同时进行。

  • 并发: CPU同时处理线程的数量时有限的 CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程时在同时自行的,这就是并发
  • 并行:在同一时刻,同时有多个线程在CPU上处理并执行

11、线程的生命周期

线程的状态:线程从生到死的过程,以及中间经历的各种状态 以及状态的转换
线程的状态:Java中 总共定义了6中状态

public enum State {
/**
* 新建状态 线程刚被创建 还未启动
*/
NEW,
/**
* 可运行(就绪) 线程已经调用start 等待获取cpu的执行权
*/
RUNNABLE,
/**
*阻塞 线程在执行的时候 没有抢到锁对象 进入到了阻塞状态
*/
BLOCKED,
/**
*无限等待 一个线程进入waitting状态,另一个线程调用notify或者notifyall方法才能唤醒
*/
WAITING,
/**
* 计时等待 在计时结束之后 自定醒来
*/
TIMED_WAITING,
/**
*终止状态(死亡) 因为run方法正常退出而死亡,或者因为没有捕获异常终止了run方而死亡
*/
TERMINATED;
}

线程间相互转化

12、定时器

定时器时一种控制任务演示调用,或者周期调用的技术
定时器的实现方式:
方式一:Timer 线程的工具,用于在后台线程中安排将来执行的任务。 可以将任务安排为一次性执行,
或者以固定间隔重复执行。
方式二:ScheduledExecutorService 可以安排命令在给定延迟后运行,或定期执行
在这里插入图片描述

public class TimerDemo1 {
public static void main(String[] args) {
// 1 创建定时器 Timer
Timer timer = new Timer();
// 2 调用方法 处理定时任务 立即执行 每个一秒执行一次
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行----"+
new Date());
}
},0,1000);
}
}

Timer定时器的特点和存在的问题
1 Timer定时器时单线程 处理多个任务按照顺序执行,存在延时和设置定时器的时间有出入
2 可能因为某个任务的异常导致Timer线程死掉,从而影响后续任务的执行

public class TimerDemo1 {
public static void main(String[] args) {
// 1 创建定时器 Timer
Timer timer = new Timer();
// 2 调用方法 处理定时任务 立即执行 每个一秒执行一次
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行----"+
new Date());
}
},0,1000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行----"+
new Date());
System.out.println(10/0);
}
},0,2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行----"+
new Date());
}
},0,3000);
}
}

ScheduledExecutorService 定时器
在这里插入图片描述

public class TimerDemo2 {
public static void main(String[] args) {
// 1 创建一个线程池 作为定时器
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
//2 开启定时任务
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行----"+
new Date());
}
},0,2, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行----"+
new Date());
System.out.println(10/0);//此处发生异常 ,只影响此线程,而对其他线程没有
任何影响
}
},0,2, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行----"+
new Date());
}
},0,2, TimeUnit.SECONDS);
}
}

13、网络编程

网络编程可以让程序与网络上的其他的设备中的程序进行数据交互
网络通信的基本模式:C/S(client-server) B/S(Browser -Srever)

  • Client(客户端): 需要程序员开发实现 用户需要安装客户端
  • Server(服务端):需要程序员开发实现的 需要部署在服务器上的
  • Browser(浏览器) :不需要程序员开发实现 需要用户安装浏览器
  • Server(服务端):需要程序员开发实现的 需要部署在服务器上的

13.1、网络编程的要素

  • IP地址:设备在网络中的地址 是唯一的标识
  • 端口:应用程序在设备中的唯一的标识
  • 协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议

IP地址:IPV4和IPV6
ipv4:192.168.12.222 4个字节 32为
IPV6:16个字节(128位)号称可以为地球上每一粒沙子都可以给一个IP地址 ipv6:分成8个整数,每个整数用4个16进制位表示,数字之间用冒号分开
在这里插入图片描述
IP地址的形式:公网地址和私有地址(局域网地址)
192.168.开头的地址就是常见的局域网地址 范围192.168.0.0----192.168.255.255 专门提供组织机构内
部使用
110.或者10.开头的地址 是公网地址
IP的命令:
ipconfig 查看本机的IP地址

13.2、IP地址操作类–InetAddress

此类表示Internet协议(IP)地址。
IP地址是IP使用的32位或128位无符号数,IP是低级协议,在其上构建UDP和TCP等协议
在这里插入图片描述

public static void main(String[] args) throws IOException {
//1 获取本机地址对象
InetAddress ip = InetAddress.getLocalHost();
System.out.println(ip.getHostName());//主机名称
System.out.println(ip.getHostAddress());//主机地址
// 2 获取域名的ip对象
InetAddress ip1 = InetAddress.getByName("www.baidu.com");
System.out.println(ip1.getHostName());//主机名称
System.out.println(ip1.getHostAddress());//主机地址
//3 获取公网IP对象
InetAddress ip2 = InetAddress.getByName("123.6.77.65");
System.out.println(ip2.getHostName());//主机名称
System.out.println(ip2.getHostAddress());//主机地址
//4 判断是否联通ping 5S测试时间
System.out.println(ip1.isReachable(5));
}

13.2、端口

端口号:标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围0-65535
端口的类型:

  • 周知端口:0-1023 被预先定义的知名应用占用(Http默认端口:80 ,FTP:21)
  • 注册端口:1024-49151 分配给用户进程或某些应用程序(tomcat:8080 mysql:3306 oracle:1521)
  • 动态端口:49152-65535 因为他一般不固定分配给某中进程,而是动态分配

注意:我们自己开发程序,选择注册端口,且一个设备中不能出现两个端口号一样的程序,否则会出错

13.4、协议

通信协议:链接和通信数据的规则,也被称为网络通信协议
网络通信协议有两套参考模型:
oSI参考模型:世界互联协议的标准 全球通信的规范 由于此模型太过理想化, 未能在网络上进行广泛使

TCP/IP模型(TCP/IP协议):事实上的国际标准
在这里插入图片描述
TCP协议的特点:
使用TCP协议,必须双方先建立连接,他是一种面向链接可靠的通信协议

  • 传输前:采用三次握手,方式建立链接,所以是可靠的 在连接中可进行大数据量的传输
    链接、发送数据都要确认,且传输完毕之后,还要释放已建立的链接,四次挥手,通信效率低

三次握手:三次握手

tcp的四次挥手:
四次挥手

  • UDP协议的特点: UDP协议是一种无连接协议 不可靠的传输协议 每个数据包的的大小限制在64内
    发送不管对方是否准备好,接收方收到也不确认,因此是不可靠的 可以广播方式,发送的数据结束后 无需释放资源 开销小 速度快

14、UDP通信

udp 协议是一种无连接 不可靠的传输协议
将数据源IP 目的地的IP和端口以及数据封装成一个数据包,大小限制在64K以内,直接发送出去即可

DatagramPacket数据包对象(空投资源)

在这里插入图片描述

DatagramSocket 发送端和接收端的对象

在这里插入图片描述

public class SenderDemo {
public static void main(String[] args) throws IOException {
System.out.println("============发送端启动==========");
//1 创建发送端对象 发送端自带默认的端口号
DatagramSocket socket = new DatagramSocket(6666);
//2 准备要发送的数据报包
byte[] buf = "我是一个数据包,里面有很重要的数据。".getBytes();
DatagramPacket packet = new DatagramPacket(buf,buf.length,
InetAddress.getLocalHost(),8888);
//3 把数据发送出去
socket.send(packet);
//4 释放资源
socket.close();
}
}

接收端的实现步骤:

  1. 创建DatagramSocket对象 并指定端口,接收端对象
  2. 创建DatagramPacket对象接收数据
  3. 使用DatagramSocket对象的receive方法 传入DatagramPacket对象 接收数据报包
  4. 释放资源
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
System.out.println("=============接收端启动====================");
// 1 创建接收端对象
DatagramSocket socket = new DatagramSocket(8888);
// 2 创建一个数据报包对象 接收数据
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf,buf.length);
// 3 接收数据
socket.receive(packet);
// 4 取出数据
int len = packet.getLength();
byte[] rbuf = packet.getData();
String rs = new String(rbuf,0,len);
System.out.println(rs);
//5 获取发送端的信息 ip 和端口
String ip = packet.getSocketAddress().toString();
System.out.println("对方的地址:" + ip);
int port = packet.getPort();
System.out.println("对方的端口:" + port);
socket.close();
}
}

14.1、UDP协议:多发多收

过程:

  • 发送端可以一直发送信息
  • 接收端可以不断的接收多个发送端发送过来的信息 并将信息展示出来
  • 发送端发送了exit 则结束发送端程序

发送端可以反复的发送数据:
1 创建DatagramSocker对象
2 使用while死循环不断的接收用户的输入,如果用户输入的是exit 则退出程序
3 如果用户输入的不是exit 把数据封装成DatagramPaket
4 调用DatagramSocker对象的send方法将数据包发送出去
5 释放资源

public class UDPDemo1 {
public static void main(String[] args) throws IOException {
System.out.println("============发送端启动=======================");
// 1 创建DatagramSocker对象
DatagramSocket socket = new DatagramSocket(7777);
// 2 使用while死循环不断的接收用户的输入,如果用户输入的是exit 则退出程序
Scanner sc = new Scanner(System.in);
while(true){
System.out.println("请说:");
String msg = sc.nextLine();
if ("exit".equals(msg)){
System.out.println("再见");
// 5 释放资源
socket.close();
break;
}
// 3 如果用户输入的不是exit 把数据封装成DatagramPaket
byte[] data = msg.getBytes();
DatagramPacket dp = new DatagramPacket(data, data.length,
InetAddress.getLocalHost(),8888);
// 4 调用DatagramSocker对象的send方法将数据包发送出去
socket.send(dp);
}}}

接收端可以反复的接收数据
1 创建DatagramSocker对象并需要指定端口—接收端对象
2 创建DatagramPacket对象接收数据----数据包对象
3 使用while死循环不断进行第4步
4 调用DatagramSocker对象的receive方法传入DatagramPacket对象 解析数据包 并输出其中的数据

public class UDPReceive1 {
public static void main(String[] args) throws IOException {
System.out.println("===============接收端启动===============");
// 1 创建DatagramSocker对象并需要指定端口---接收端对象
DatagramSocket socket = new DatagramSocket(8888);
// 2 创建DatagramPacket对象接收数据----数据包对象
byte[] data = new byte[1024];
DatagramPacket dp = new DatagramPacket(data, data.length);
// 3 使用while死循环不断进行第4步
while(true){
// 4 调用DatagramSocker对象的receive方法传入DatagramPacket对
象 解析数据包 并输出其中的数据
socket.receive(dp);
int len = dp.getLength();
String rs = new String(dp.getData(),0,len);
System.out.println("收到来自:"+dp.getAddress()+",对方端口
为:"+dp.getPort()+"的信息为:"+rs);
}
}
}

14.2、UDP通信-广播,组播

UDP通信中有三种方式:

  • 单播:单台主机与单台主机之间的通信
  • 广播:当前主机与所在网络中所有的主机通信
  • 组播:当前主句与选定的一组主机的通信

UDP协议如何实现广播:
广播地址:255.255.255.255
具体操作:

  • 发送端发送的数据包的目的地的地址写的广播地址 且指定端口
  • 本机所在的网段的其他主机的程序只要注册对应端口就可以接收到信息
public class UDPSend1 {
public static void main(String[] args) throws IOException {
System.out.println("============发送端启动=======================");
// 1 创建DatagramSocker对象
DatagramSocket socket = new DatagramSocket(7777);
// 2 使用while死循环不断的接收用户的输入,如果用户输入的是exit 则退出程序
Scanner sc = new Scanner(System.in);
while(true){
System.out.println("请说:");
String msg = sc.nextLine();
if ("exit".equals(msg)){
System.out.println("再见");
// 5 释放资源
socket.close();
break;
}
// 3 如果用户输入的不是exit 把数据封装成DatagramPaket
byte[] data = msg.getBytes();
DatagramPacket dp = new DatagramPacket(data, data.length,
InetAddress.getByName("255.255.255.255"),8888);
// 4 调用DatagramSocker对象的send方法将数据包发送出去
socket.send(dp);
}
}
}
public class UDPReceive1 {
public static void main(String[] args) throws IOException {
System.out.println("===============接收端启动===============");
// 1 创建DatagramSocker对象并需要指定端口---接收端对象
DatagramSocket socket = new DatagramSocket(8888);
// 2 创建DatagramPacket对象接收数据----数据包对象
byte[] data = new byte[1024];
DatagramPacket dp = new DatagramPacket(data, data.length);
// 3 使用while死循环不断进行第4步
while(true){
// 4 调用DatagramSocker对象的receive方法传入DatagramPacket对
象 解析数据包 并输出其中的数据
socket.receive(dp);
int len = dp.getLength();
String rs = new String(dp.getData(),0,len);
System.out.println("收到来自:"+dp.getAddress()+",对方端口
为:"+dp.getPort()+"的信息为:"+rs);
}
}
}

组播:地址 224.0.0.0~239.255.255.255
具体操作:发送端的数据包的目的地址是组播IP(224.0.1.1 端口:9999)
接收端必须绑定组播ip(224.0.1.1) 端口还要注册发送端的目的地端口9999
DatagramSoket的子类MulticastSocket

public class UDPSend1 {
public static void main(String[] args) throws IOException {
System.out.println("============发送端启动=======================");
// 1 创建DatagramSocker对象
DatagramSocket socket = new DatagramSocket(7777);
// 2 使用while死循环不断的接收用户的输入,如果用户输入的是exit 则退出程序
Scanner sc = new Scanner(System.in);
while(true){
System.out.println("请说:");
String msg = sc.nextLine();
if ("exit".equals(msg)){
System.out.println("再见");
// 5 释放资源
socket.close();
break;
}
// 3 如果用户输入的不是exit 把数据封装成DatagramPaket
byte[] data = msg.getBytes();
// DatagramPacket dp = new DatagramPacket(data, data.length,
InetAddress.getByName("255.255.255.255"),8888);
DatagramPacket dp = new DatagramPacket(data, data.length,
InetAddress.getByName("224.0.1.1"),9999);
// 4 调用DatagramSocker对象的send方法将数据包发送出去
socket.send(dp);
}
}
}
public class UDPReceive1 {
public static void main(String[] args) throws IOException {
System.out.println("===============接收端启动===============");
// 1 创建DatagramSocker对象并需要指定端口---接收端对象
// DatagramSocket socket = new DatagramSocket(8888);
MulticastSocket socket = new MulticastSocket(9999);
// 绑定组播地址
socket.joinGroup(new
InetSocketAddress(InetAddress.getByName("224.0.1.1"),9999),NetworkInterface.getB
yInetAddress(InetAddress.getLocalHost()));
// 2 创建DatagramPacket对象接收数据----数据包对象
byte[] data = new byte[1024];
DatagramPacket dp = new DatagramPacket(data, data.length);
// 3 使用while死循环不断进行第4步
while(true){
// 4 调用DatagramSocker对象的receive方法传入DatagramPacket对
象 解析数据包 并输出其中的数据
socket.receive(dp);
int len = dp.getLength();
String rs = new String(dp.getData(),0,len);
System.out.println("收到来自:"+dp.getAddress()+",对方端口
为:"+dp.getPort()+"的信息为:"+rs);
}
}
}

15、TCP协议

15.1、客户端

在这里插入图片描述

public class ClientDemo1 {
public static void main(String[] args) throws IOException {
System.out.println("==========客户端启动========================");
// 1 创建客户端Socket对象,请求与服务端的链接
Socket socket = new Socket("127.0.0.1",8888);
//2 使用socket对象调用`getOutputStream()`方法得到字节输出流
OutputStream os = socket.getOutputStream();
// 3 使用字节输出流完成数据的发送
PrintStream ps = new PrintStream(os);
ps.println("我是TCP的客户端,我们可以建立链接进行通信吗?");
ps.flush();
// 4 释放资源 关闭socket
socket.close();
}
}

服务端的实现:
ServerSocket(服务端)

ServerSocket(int port)创建绑定到指定端口的服务器套接字。
Socket accept()侦听对此套接字的连接并接受它。

实现步骤:
1 创建ServerSocket对象 代表服务端
2 调用ServerSockt对象的accept方法 等待客户端的链接请求,并接受请求,与其建立链接,得到
Socket对象
3 通过Sokcet调用的getInputStream方法 得到字节输入流,读取客户端发送过来的数据
4 释放资源 关闭socket

public class ServerDemo1 {
public static void main(String[] args) throws IOException {
System.out.println("==========服务端启动========================");
// 1 创建ServerSocket对象 代表服务端
ServerSocket serverSocket = new ServerSocket(8888);
// 2 调用ServerSockt对象的accept方法 等待客户端的链接请求,并接受请求,与其建立链
接,得到Socket对象
Socket socket = serverSocket.accept();
// 3 通过Sokcet调用的getInputStream方法 得到字节输入流,读取客户端发送过来的数据
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// 按行读取信息
String msg ;
if((msg = br.readLine())!= null){
System.out.println(socket.getRemoteSocketAddress()+"说了:" + msg);
}
// 4 释放资源 关闭socket
socket.close();
serverSocket.close();
}
}

15.2、基于TCP协议的多发多收(掌握)

具体步骤:
用使用循环控制服务端和客户端进行持续的读写数据

public class ServerDemo1 {
public static void main(String[] args) throws IOException {
System.out.println("===========服务端启动================");
ServerSocket server= new ServerSocket(8888);
while(true){
Socket socket = server.accept();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg ;
while((msg = br.readLine())!=null){
System.out.println(socket.getRemoteSocketAddress()+"说了:"
+msg);
}
}
}
}
public class CilentDemo1 {
public static void main(String[] args) throws IOException {
System.out.println("==================客户端启动====================");
Socket socket = new Socket("127.0.0.1",8888);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while(true){
System.out.println("请说:");
String msg =sc .nextLine();
ps.println(msg);
ps.flush();
}
}
}

15.3、TCP-同时接受多个客户端信息(重点)

public class ClientDemo2 {
public static void main(String[] args) throws IOException {
System.out.println("=========客户端启动==============");
Socket socket = new Socket("127.0.0.1",8888);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while(true){
System.out.println("请说:");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
}
}
public class ServerDemo2 {
public static void main(String[] args) throws IOException {
System.out.println("========启动服务端=============");
ServerSocket serverSocket = new ServerSocket(8888);
while (true){
Socket socket = serverSocket.accept();//表示一个客户端
new ServerSocketThread(socket).start();//一个客户端对应一个服务端 一个线程
}
}
}
class ServerSocketThread extends Thread{
private Socket socket;
public ServerSocketThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while ((msg = br.readLine())!=null) {
System.out.println(socket.getRemoteSocketAddress()+"说了:" +
msg);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

15.4、TCP实现-即时通信

即时通信:一个客户端的消息发出去,其他的客户端可以接收到

public class ClientReaderThread extends Thread{
private Socket socket;
public ClientReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
InputStream is = null;
try {
is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while((msg = br.readLine())!=null){
System.out.println(socket.getRemoteSocketAddress()+"收到了:"
+msg);
}
} catch (IOException e) {
System.out.println("服务端把你给剔除了----");
}
}
}
/**
* 客户端发送消息的同时,随时有可能收到别的客户端发送来的消息
* 服务端:接受消息之后,消息推送给其他所有的在线的客户端socket
*/
public class ClientDemo3 {
public static void main(String[] args) throws IOException {
System.out.println("========================客户端启动=================");
Socket socket = new Socket("127.0.0.1",8888);
//1 当客户端建立之后 马上为客户端分配一个独立的线程 随时准备负责读取他客户端发送过来
的的消息(经过服务端转发的)
new ClientReaderThread(socket).start();
//2 从socket通信管道中得到一个字节输出流 负责发送数据
OutputStream os= socket.getOutputStream();
PrintStream ps= new PrintStream(os);
Scanner sc = new Scanner(System.in);
while(true){
System.out.println("请说:");
String msg = sc.nextLine();
//3 发送消息
ps.println(msg);
ps.flush();
}
}
}
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(){
}
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//1 得到输入流 读
try {
InputStream is = this.socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg;
while((msg= br.readLine()) !=null){
System.out.println(socket.getRemoteSocketAddress()+"说了:"+msg);
//把这个消息发送给当前所有在线的socket客户端
sendMsgToAll(msg);
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress()+"下线了");
//从在线集合中删除客户端的socket
ServerDemo3.onlineSockets.remove(socket);
}
}
private void sendMsgToAll(String msg){
try{
// 遍历所有当前在线的socket 将信息发送给他们
for (Socket onlineSocket:ServerDemo3.onlineSockets) {
// 发送给除了自己意外的其他的客户端 需要把自己排除在外
if (onlineSocket != this.socket){
PrintStream ps = new
PrintStream(onlineSocket.getOutputStream());
ps.println(msg);
ps.flush();
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
public class ServerDemo3 {
//表示所有当前在线的客户端socket集合
public static List<Socket> onlineSockets = new ArrayList<>();
public static void main(String[] args) throws IOException {
System.out.println("===========服务端启动=============");
ServerSocket server= new ServerSocket(8888);
while(true){
// 每接收到一个客户端的socket 交给一个独立的子线程去负责读取消息 并将其加入到当
前在线socket集合
Socket socket = server.accept();
System.out.println(socket.getRemoteSocketAddress()+"上线了");
onlineSockets.add(socket);
// 开始建立线程 读取信息
new ServerReaderThread(socket).start();
}
}
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值