多线程
1. 理解什么是多任务
- 在同一时间内同时干多件事情
2. 并发问题案例
- 案例1:购买火车票问题,总票数为10,若同时有12个人在同一时间购票,并且显示购票成功,但实际情况有2人未购票成功。
//买火车票例子
private int ticketNums = 10;
@Override
public void run(){
while(true) {
if (ticketNums<=0){
break;
}
try {
//模拟延迟
Threand.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
//票数的减少
System.out.println(Thread.currentThread().getName()+tickNums--);
}
}
public static void main(String[] args) {
TestThread ticket = new TestThread();
new Thread(ticket,"test1").start();
new Thread(ticket,"test2").start();
new Thread(ticket,"test3").start();
}
//结果出现问题,出票顺序出现混乱
//注意该类实现runnable接口
- 案例2:龟兔赛跑问题
//模拟龟兔赛跑
//重写run方法
public void run() {
for (int i = 0; i < 100; i++) {
//模拟兔子睡觉
if (Thread.currentThread().getName().equals("兔子") && i%10 == 0) {
try {
Thread.sleep(10);
} catch (InteruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(i);
if (flag){
break;
}
System.out.println(Thread.currentThread().getName+i)
}
}
//胜利者
private static String winner;
//判断是否有胜利者
public Boolean gameOver(int steps) {
if (winner!=null){
return true;
}else {
if (steps >= 100){
winner = Thread.currentThread().getName();
System.out.println(winner);
return true;
}
}
return false;
}
//测试
new Treand(race,"兔子").start();
new Thread(race,"乌龟").start();
//注意该类实现runnable接口
3. 线程
3.1 基础知识
- 线程由cpu进行调度,具体执行顺序由cpu决定
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
3.2 三种线程创建方法
- 继承Thread类
- 自定义线程类并继承Thread类
- 重写run()方法
- 调用start()开启线程
- 实现Runnable接口(推荐)
- 自定义线程类并继承Runnable接口
- 重写run()方法
- 执行线程,丢入接口实现类,调用start()开启线程
- 推荐使用:避免单继承局限性,方便同一个对象被多个线程使用
- 实现Callable接口
- 创建服务,设置线程池数量
- 执行方法
- 获取结果
- 关闭服务
- 可以定义返回值,可以抛出异常
3.3 线程操作
-
线程停止
- 建议正常停止,利用次数
- 利用标志位,线程运行前进行标志位判断
- 不要使用stop或者是destroy方法
-
线程休眠
- sleep函数:指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间打倒后线程进入就绪状态
- sleep可以模拟网络延迟(放大问题的发生性),倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
//利用sleep模拟计时 int num = 10; while (true){ Thread.sleep(1000); System.out.println(num--); if (num<=0){ break; } } //打印系统当前时间 //获取系统当前时间 Date startTime = new Date(System.out.currentTimeMillis()); while (true){ try { Thread.sleep(1000); System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); startTime = new Date(System.out.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } }
-
线程礼让Yield
- 将线程从运行状态转换为就绪状态
- 让正在执行的线程暂停,但不阻塞
- 案例伪代码
Thread.yield();
-
Join线程合并
- 优先执行当前线程,其它线程阻塞
- 伪代码
thread.join();//优先执行thread对象
-
线程状态观测
- 伪代码
//state为观测状态结果,thread为执行线程 Thread.State state = thread.getState();
- 注意:死亡之后的线程无法再次启动
- 线程状态图
-
线程优先级
- main方法默认优先级为5
- 设置优先级
thread.setPriority(a); //a为级别,取值范围:1-10
- 获取优先级
thread.getPriority();
-
守护线程(daemon)
- 如后台记录擦操作日志,监控内存,垃圾回收等待
- 虚拟机不用等待守护线程执行完毕
3.4 线程同步问题
-
队列机制
-
synchronized锁机制
- 线程获得对象的排它锁,独占资源,其它资源等待
- 缺点
- 引起上下文切换和调度延迟,引起性能问题。
- 性能导致问题
- synchronized同步方法
- synchronized同步块
- 锁的对象就是变化的量
-
线程同步不安全案例
- 不安全的买票
- 不安全的取钱
- List中添加数据
for (int i = 0; i < 1000; i++) { new Thread(()->{ sysnchronized (list){ list.add(Thread.currentThread()/getName()); } }) } //也可以直接用CopyOnWriteArrayList,是线程安全的
3.5 死锁
- 产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不放。
- 不剥夺条件:进程以获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源。
- 如何避免
- 对于产生死锁的必要条件,破坏任意一个或多个
3.6 Lock锁
-
显示定义同步锁
-
Lock只有代码块锁
-
相对于synchronized性能更好
-
利用ReentranLock(可重复锁)实现,可以显示加锁,释放锁
//伪代码 //定义锁 private final ReentrantLock lock = new ReentrantLock(); //使用 lock.lock();//加锁 lock.unlock();//解锁
3.7 线程协作
- 生产者和消费者问题
- 对于生产者:没有产品,通知消费者等待;有产品,通知消费者消费。
- 对于消费者:消费之后,通知生产者已结束消费,需再次生产。
- 线程通信问题
- 管程法:增加缓冲区
- 信号灯法:通过标志位来解决
3.8 线程池
-
背景:并发情况下多线程对性能影响很大。
-
思路:提前创建好相关线程
-
好处:
- 提高响应速度
- 降低资源消耗,重复利用,无需多次创建
- 便于线程管理
-
使用
- Executors工具类:用于创建并返回不同类型的线程池
//创建线程池,a为线程池大小 ExcutotService service = Executors.newFixedThreadPool(a); //执行线程 service.execute(); //关闭连接 service.shutdown();
感谢狂神老师的视频教学,特别说明,资料整理来自于视频点击跳转