提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
一、多线程高级
1.线程状态:
java官方的几个状态(Thread的内部类state离所声明的):
NEW新建 RUNNABLE可运行状态 BLOCKED阻塞状态 WAITING无限等待
TIMED_WAITING 计时等待 TERMINATED终止
2.线程池
介绍:线程池也是一个池子,养的是[线程]
线程池存在的意义:
系统创建一个线程的成本是比较高的,如果没有线程池,每执行一个线程任务,就会创建线程对象执行完过后再销毁,其生命周期很短,频繁的创建和销毁线程会很浪费资源.
线程池在启动时,会创建大量空闲线程,当向线程池提交线程任务时,就会启动一个线程来执行该任务,任务结束后,线程会返回到线程池,并回到空闲状态
总结:当线程对象交给线程池维护,就可以较少线程的创建和销毁,从而提升程序性能
线程池的创建
1.通过Executors中的静态方法创建.(不推荐)
方法介绍
static ExecutorService newCachedThreadPool ( ) 创建一个默认的线程池
static newFixedThreadPool ( int nThreads ) 创建一个指定最多线程数量的线程池
代码实现(默认线程池)
public static void main(String[] args) throws InterruptedException {
// 1,创建一个默认的线程池对象.池子中默认是空的.默认最多可以容纳int类型的最大值.
ExecutorService executorService = Executors.newCachedThreadPool();
// Executors --- 可以帮助我们创建线程池对象
// ExecutorService --- 可以帮助我们控制线程池 服务通过Executors创建的线程池
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
// Thread.sleep(2000);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
executorService.shutdown();
}
代码实现(指定最多线程数量) 通过newFixedThreadPool方法,
public static void main(String[] args) {
// 参数不是初始值而是最大值
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
executorService.submit(()->{
System.out.println(Thread.currentThread().getName() + "在执行了");
});
}
看似能指定线程数量,其实源码中仍然使用的Integer的最大值
注意:
关于线程资源必须通过线程池来提供,不允许在应用中自行显示的创建线程
这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;
另一方面线程的细节管理交给线程池处理,优化了资源的开销。
而线程池[不允许]使用[Executors]去创建,而要通过[ThreadPoolExecutor]方式
2.通过ThreadPoolExecutor创建线程池
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
用给定的初始参数创建一个新的 ThreadPoolExecutor
corePoolSize:核心线程数量
maximumPoolSize:最大线程数 核心线程+空闲线程(核心成员+临时工)
keepAliveTime: 空闲线程最大存货时间
unit: 时间单位 通过调用[TimeUnit]中的枚举
BlockingQueue workQueue: 任务队列
又分为创建 ArrayBlockingQueue 有界队列 LinkedBlockingQueue 无界队列 对象
threadFactory:创建格式工厂 通过Executors(为Executor服务的工具类)中defaultThreadFactory
Executor(执行器)(也就是线程池)是来管理Thread(线程)对象的,从而达到简化并发编程的目的
handler:拒绝策略(policy 策略,方针)
分为四种:new ThreadPoolExecutor.AbortPolicy()
丢弃任务并抛出异常,是[默认策略]
ThreadPoolExecutor.DiscardPolicy()
丢弃任务,但是不抛出异常(不推荐)
DiscardOldestPolicy()
抛弃队列中等待最久的任务,然后把当前任务加入队列中
CalledrRunsPolicy()
绕过线程池,通过main线程调用其run方法
代码实现
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
5,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
pool.submit(new MyRunnable());//通过submit方法向线程池提交线程任务
pool.submit(new MyRunnable());
pool.shutdown();
}
问题:临时线程什么时候创建?
回答:新任务提交时,发现核心线程都在忙(且任务队列也满了 核心线程数+任务队列)
问题:什么时候开启拒绝策略
线程池参数的设置:结合项目的用户访问量进行思考
-务必跟上级进行沟通
核心线程数量: 取用户访问量的平均值
最大线程数量:高峰值2
任务队列:最大线程数量1.5 可以根据实际情况改变
单例设计模式
设计模式的介绍
前人总结一些好方法,用来解决经常遇到的问题
单列设计模式:
解决的问题: 保证[类的对象],在内存中只有一份
常见例子: windows任务管理器,回收站,网站的计数器对象
饿汉式
写法一
class Single{
//私有构造方法
private Single(){}
//创建对象,首先要被其他类调用 加static,避免被别人修改 加final 再加权限修饰词 public
public final static Single s = new Single();
}
写法二
class Single{
//私有构造方法
private Single(){}
//创建对象,私有对象变量,让外界通过一个方法调用并返回,这里加static是因为静态只能调用静态
private final static Single s = new Single();
public static Single getInstance(){
return s;
}
}
饿汉式是先创建一个对象(吃了再说)
懒汉式
代码如下
写法一:
class Single{
//私有构造方法
private Single(){}
//定义个对象变量,但不赋值
private static Single s;
//直接在方法里创建对象
public static Single getInstance(){
//判断s是否为空,如果为空,则创建对象并赋值给s
if(s==null){
s = new Single();
}
return s;
}
}
弊端:多线程操作时,有可能会创建多个对象
写法二:
class Single{
//私有构造方法
private Single(){}
//定义个对象变量,但不赋值
private static Single s;
//直接在方法里创建对象
public static Single getInstance(){
//给判断语句上锁,避免多线程操作时,出现抢夺CPU控制权导致创建多个对象
synchronized (Single.class) {
//判断s是否为空,如果为空,则创建对象并赋值给s
if(s==null){
s = new Single();
}
}
return s;
}
}
弊端:效率低, 因为当线程A会导致线程B阻塞在锁外面, 而且线程B明知道线程A从里面出来就会创建一个对象,没必要抢着进锁来判断是否对象为null(因为线程A结束就会创建一个对象). 所以需要设计如何让线程B非要进入锁,只需要在锁外在设置个判断
写法三:
class Single{
//私有构造方法
private Single(){}
//定义个对象变量,但不赋值
private static Single s;
//直接在方法里创建对象
public static Single getInstance(){
/*用来判断线程是否需要进去到锁里面(为了创建对象),如果有一个线程已经进去,
肯定会创建对象,后面的线程就没必要进入锁,从而提高效率*/
if (s==null) {
//给判断语句上锁,避免多线程操作时,出现抢夺CPU控制权导致创建多个对象
synchronized (Single.class) {
//判断s是否为空,如果为空,则创建对象并赋值给s
if(s==null){
s = new Single();
}
}
}
return s;
}
}
二、网络编程
1.网络编程三要素
IP地址:设备在网络中的唯一标识
端口号:程序在设备中的唯一标识
协议: 网络协议 数据收发
1.UDP协议
2.TCP协议
在进行数据传输的时候,底层依旧是依赖IO流技术
1.1.IP地址
常见分类IPV4,IPV6
IPV4 32bit(4字节)–>二进制–>点分十进制标识法(192.167.1.66) 交换机
IPV6:IPV4不能满足数量需求而产生
采用128位,分成8组,每两个字节分为一组,采用[冒分十六进制标识法] 省略前面的0,
特殊情况:如果计算出的16进制表示形式连续很多0
FF01::1101 计算机会自动补足.
ipconfig all查看本机IP地址
ping IP地址 检查网络是否连通
特殊IP地址:本机IP:127…0.0.1或者localhost
1.2.InetAddress类
常用方法:
InetAddress.getByName(String host) host(主机)
将IP 封装为InetAddress对象 直接写IP地址
address.getHostName 获取到的[主机名]
getHostAddress:获取IP
1.3.端口号
用两个字节表示的整数,他的取值范围为0-65535
其中0-1023之间的端口号用于一些知名的网络服务或者应用
常见端口号:
8080:tomcat服务器
3306:MySQL数据库
注意:写代码的时候,注意端口绑定,不要出现端口冲突
2.UDP协议和TCP协议
2.1介绍
UDP:
[面向无连接]通信协议 速度快,数据不安全,有大小限制([一次]只能传输64K)
理解为:发短信 腾讯会议 屏幕共享
TCP:
[面向有连接] 数据安全,速度慢,没有大小限制
理解为打电话
三次握手:
1.客户端向服务端发起请求
2.服务端相应客户端的请求
3.数据的传输.
2.2UDP协议收发数据
使用DatagramSocket 创建码头对象 和 DatagramPacket 创建包裹对象
1.DatagramSocket:码头对象
2,DatagramPacket:包裹对象
send:发送 receive接收(只会把内容取出来,把包裹还回去)需要自己创建一个包裹对象
最后需要调用socket.close().关流
客户端:码头对象空参构造
服务端:码头对象有参构造(指定端口号)
客户端:
代码如下:
public static void main(String[] args) throws Exception {
// 1. 创建DatagramSocket对象 (码头) 随机端口号
DatagramSocket socket = new DatagramSocket();
// 2. 创建DatagramPacket对象 (包裹对象)
String content = "你可能说不出哪里好";
//将字符串转为字节数组
byte[] bytes = content.getBytes();
DatagramPacket packet =
new DatagramPacket
(bytes, bytes.length,
InetAddress.getByName("127.0.0.1"),
8888);
// 3. 调用码头对象的发送方法, 将包裹对象发送出去
socket.send(packet);
// 4. 关闭流释放资源
socket.close();
}
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
参数1: 将传输的数据, 转换为字节, 并存入一个数组
参数2: 数组的长度
参数3: InetAddress(IP对象)
参数4: 端口号
服务端:
代码如下:
public static void main(String[] args) throws IOException {
// 1. 创建DatagramSocket对象(码头), 绑定端口为8888(必须给出端口号)
DatagramSocket socket = new DatagramSocket(8888);
// 2. 创建DatagramPacket对象(包裹对象)
byte[] bys = new byte[1024];
DatagramPacket packet = new DatagramPacket(bys, bys.length);
// 3. 调用码头对象的接受方法(receive), 将数据, 接收到自己的包裹当中
socket.receive(packet);
System.out.println(packet.getAddress());
//将接受的字节数组转化为字符串,需要注意的是长度是包裹对象的长度
String s = new String(bys, 0, packet.getLength());
System.out.println(s);
// 4. 关闭流释放资源(服务端一般可以不管流)
socket.close();
}
总结
今天学了多线程的高级运用和网络编程的UDP协议的传输,其中线程状态和线程池与单例设计模式,简单认识了网络编程.