文章目录
线程
一、线程的生命周期(线程类)
-
什么时候开始
-
当创建线程类的对象时?
-
使用线程类对象的
start()
方法?——( ✅ )-
思考,使用start方法开始运行了吗?
-
没有,run没有立即执行
- 因为CPU同时只能运行一个线程,但是每秒中可以快速运行非常多个线程,那么各个线程就需要有一个排队的概念
-
start方法的作用其实就是准备就绪,简单理解就是告诉CPU这个线程已经准备就绪了
- 什么时候开始运行?是由CPU决定
- 在Java中的线程是基于JVM下的线程,所以是由JVM控制
-
只有当这个线程就绪,才有可能会运行
-
-
-
-
什么时候结束?
- 当run方法执行完毕后,就结束了声明周期
-
什么时候会暂停?
- 通过
sleep()
方法,进入睡眠状态 wait()
进入等待状态- 遇到阻塞命令,进入阻塞状态
- 挂起状态,使用比较少了
pause()
- 通过
二、如何创建线程?
- 继承Thread
- 单继承
- 实现接口
Runnable
- 用的比较多,一个类可以实现多个接口,比较方法使用
- 这样的实现类又被成为子线程
- Swing 的定时器,定时任务等
三、子线程
1、使用思路
- 创建一个某个类,继承
Runnable
,并重写run()
方法 - 要调用这个线程,需要创建一个
Thread类
,并且将实现类作为构造函数的参数传入 - 调用
Thread类
的成员方法start()
启动即可
四、线程的属性
1、是否是守护线程(后台线程) (isDaemon)
- 什么是守护线程?
- 当进程中其他线程结束后,守护线程会自动死亡(结束)
- 守护的是用户线程。
- 它不影响JVM结束运行,当用户线程都结束了,它会和JVM一起结束。
- 守护线程三个特性:
- 线程类型默认继承自父线程,即守护线程创建的线程也是守护线程,
- 被谁启动,通常守护线程都是由JVM启动,在JVM启动的时候只有一个非守护线程,就是main线程,其他的都是守护线程。
- 不影响JVM的退出,JVM想退出的时候只看有没有用户线程,不看有没有守护线程。
- 用法:
setDaemon(true)
:设置为守护线程
2、优先级(Priority)
- 一般不进行设置 。程序设计不应该依赖于优先级
- 这个属性的目的是 —— 告诉线程调度器,用户希望哪些线程相对多运行、哪些少运行。
- 一共1到10,10 种优先级,未经修改的情况下都是5。
3、名字
-
一般也不进行设置。
-
用法
setName()
设置线程名字。-
会影响线程内执行获取线程名称是获取到的名字
获取名称的用法
Thread.currentThread.getName()
-
4、线程ID
- 和名字类似
- 同一个ID会被先后不同的线程使用,唯一性不能保证;
- ID不允许被修改。
- ID是从1开始,并自增的
五、线程的方法
1、join方法
- 用法:主线程方法内
thread1.join()
- 将线程1作为主线程的 的一部分(子线程)
- 作用:
- 只有当子线程运行结束后,主线程才会执行
- 用于控制线程运行顺序。
六、不同的线程对同一个对象操作?
比如,两个线程分别对一个int类型的num,进行100万次++
- 线程的执行
num++
的过程- 先从num取值
- 将值存放于寄存器,执行++操作
- 将++后结果存放回num出
- 两个线程同时操作num时,有可能第一步取值的时候,取到的是同一个值,就会导致丢失后续的第二步和第三步
那么要如何解决这个问题呢?
- 涉及到两个线程之间的关系
- 独占的资源:只运行一个人(线程)使用
- 当线程拿到资源,对其所做的操作其他线程无法干扰
- 做的操作【代码】 ——转换—— 【同步的代码块】
- 独占的资源:只运行一个人(线程)使用
七、Synchronized 同步代码块
面试常考
-
用法:(共三种用法)
synchronized (类名.class){ 类名.属性++; // 操作这个类的属性的 }
-
举个例子
public class ThreadAdd1 extends Thread{ @Override public void run() { for (int i = 0; i < 1000_0000; i++) { synchronized (DemoAdd.class){ DemoAdd.num++; } } System.out.println("1结果:"+DemoAdd.num); } }
-
其他相关知识点后续再补充
八、线程安全
线程安全?线程不安全?
对象或数组或集合:添加,删除,修改,查找
- 线程不安全:当两个线程对集合中某些内容进行添加,会导致覆盖的线程发生
- 解决方法,可以对添加操作 添加锁, 或者 选择线程安全的集合存放
- 线程安全:Vector 慢
- 线程不安全:ArrayList 快
## 完善单例模式
-
原本的单例模式,在多人同时调用生成对象方法
getInstance()
时,可能会同时生成多个对象 -
解决方法,加入同步锁
private static JdbcUtils jdbcUtils; private JdbcUtils() { } // 单例模式 public static JdbcUtils getInstance(){ // 加入同步锁,线程是安全的,速度就变慢了 synchronized (JdbcUtils.class){ if (jdbcUtils == null){ jdbcUtils = new JdbcUtils(); } } return jdbcUtils; }
- 加入同步锁后,线程是安全的,速度就变慢了
网络编程
一、继续完善群聊功能
- 建议使用两个模块,相互独立!
- 服务端:可能会阻塞的方法:aceept/readline
- 客户端:可能会阻塞的方法:readline
- 有阻塞的地方尽量放在线程内执行
1、优化服务器端
-
将原本存储scoket对象的列表,升级为 存放【线程】对象的列表。
-
并完善线程类
-
建立连接,获取封装流在构造函数内实现
-
新增方法封装了:实现输出信息的方法
-
如例子:
package com.cykj.net; import java.io.*; import java.net.Socket; public class SocketRevThread extends Thread{ private Socket socket; private BufferedReader reader; private PrintWriter printWriter; public SocketRevThread() { } public SocketRevThread(Socket socket) { this.socket = socket; try { // 读取socket的输入流 InputStream inputStream = socket.getInputStream(); // 转换为封装流 reader = new BufferedReader(new InputStreamReader(inputStream)); // 读取输出流并转为封装流 printWriter = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); } catch (IOException e) { e.printStackTrace(); } } // run方法要利用到readline的阻塞 @Override public void run() { try { // 加入while循环 让线程持续进行 while (true){ // 阻塞 。获取到输入的内容 String str = reader.readLine(); System.out.println(str); // 遍历列表 调用对应的write方法 实现消息群发 for (SocketRevThread socketThread : ServerMain.socketList ){ socketThread.write(str); } } } catch (IOException e) { e.printStackTrace(); } } // write方法用于 输出 public void write(String line) { printWriter.println(line); printWriter.flush(); } }
-
2、优化客户端
-
创建连接可以直接在main方法内实现
- 并且可以直接将对应的线程对象设置全局变量
-
访问数据库应该是由服务器端来处理——
优化访问数据库的方式
-
应该由服务器端来访问,
-
客户端给服务器传递消息: 有账号密码、聊天信息——如何区分?
- 如何区分不同的消息?
- 定义一个特殊的格式,用某个特殊符号,将一行内容分为两段
- 登陆信息:
login && 账号 && 密码
- 聊天信息:
chat && 聊天信息
- 注册信息:
regist && 账号 && 密码 && 性别。。。
- 登陆信息:
- 发送时,按照格式进行拼接
- 接受时,用
split()
方法,将字符串拆分好 - 再返回结果时,最好也是按这个格式来返回信息
- 如
login && true
- 如