多线程运用和网络编程小结

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

一、多线程高级

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协议的传输,其中线程状态和线程池与单例设计模式,简单认识了网络编程.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值