Java多线程课程随笔
1、多线程详解
- 线程简介
- 线程实现(重点)
- 线程状态
- 线程同步(线程通信问题)
- 高级主题
2、线程间接
多任务、线程、进程、多任务
- Process:程序是指令和数据的有序集合是静态的概念
- 进程是执行程序的一次过程是动态的
- Thread:通常一个进程中有多个线程,一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
本章核心概念
- 线程就是独立的执行程序
- 在程序运行时,即使没有自己创建线程,后台会有多个线程,如主线程,GC线程。
- Main()线程,只有一个主线程
3、线程创建
三种方式如下
Thread
- 继承Object
- 实现Runnable接口
- 自定义一个继承Thread的类、重写Run()方法、实现线程
Runnable
- 创建实现类实现Runnable接口
- 新建Thread类,传入Runnable的接口
- 避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用
- 创建线程,开启进程Start()并非立马启动是由CPU调度开启线程
- Thread类也实现了Runnable接口
两者启动方式不一样,一者直接New、一者实现Runnable接口,避免单继承局限性,方便一个对象被多个线程使用
例如:买火车票
- Runnable实现类
主程序
实现Callable接口
- 实现Callable接口 需要一个返回值类型
- 重写Call方法需要抛出异常
- 创建目标对象
- 创建执行服务、线程池
- 提交执行任务
- 获取结果
- 关闭服务
- 例子:
4、静态代理、线程底层原理
- 代理对象和实际对象同时实现一个接口
- 代理对象代理真实对象,传入真实对象
- 代理模式可以增强真实对象的方法。
- 真实对象专注于满足自己的需求
- 线程Thread也实现了Runnable接口、都有共同的Run方法、所以Thread不过是一个代理类而已、以静态代理的模式来实现多线程
5、Lamda表达式
- 为了避免匿名内部类定义过多
- 代码更加间接
- 去掉了一堆没有意义的代码,留下核心的逻辑
- 概念:如果接口只有唯一的一个方法,则是一个函数式接口
语法以及简化
Interface inteface = (参数类型 参数名)->{ };
Interface inteface = (参数名)->{ }; //简化参数类型
Interface inteface = 参数名->{ }; //简化参数类型和括号,只能有一个参数,多个参数时参数类型全部去掉
Interface inteface = 参数名-> ... ; //简化参数类型和括号和花括号 前提是方法体中只能有一行代码
6、线程状态
线程五大状态:新生、就绪、运行、阻塞、死亡
线程如何停止
- 建议线程正常结束 利用次数
- 建议设置一个标志位来结束线程
- 不要使用stop()、destroy等过时的或JDK不建议使用的方法
7、线程休眠
- sleep指定当前线程阻塞的毫秒数
- sleep存在异常
- sleep时间到后开始进入就绪状态
- sleep模拟网络延时、倒计时
- 每一个进程有一个锁,sleep时不会释放锁
8、线程礼让 yield
- 线程礼让、让当前正在执行的线程暂停、但是不阻塞
- 线程从运行状态转化为就绪状态
- 让CPU重新调度,礼让不一定成功、看CPU的调度
9、线程强制执行
- Join合并线程、待该线程执行完成之后在执行其他的线程
- 强制执行、导致阻塞不建议使用
10、线程状态观测
Thread.state
- New \ Runnable \ Blocked \ Waiting \ Terminating
- 线程一旦停止不可以再次使用
11、线程优先级
- 优先级默认最大值 10 最小值 1 默认值为5
- 使用get/setProprity();来设置优先级
- 优先级的大小并不起决定性的作用,还是取决于CPU的调度
- 一般要先设置优先级再启动
12、守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕。
- Thread,setDaemon(True); 表示该线程是一个守护线程 false表示是一个用户线程
13、线程同步
- 多个线程同时操作同一个资源、不过是一种等待机制罢了
- 队列+锁实现线程同步,解决线程不同步的关系
- synchronized排他锁,一个对象获得对象排他锁,独占资源其他线程需要等待,使用后会释放锁
- 一个线程只有锁会导致其他所有需要次所的线程挂起
- 多线程的竞争、加索、释放锁会导致较多的上下文切换和延时调度
- 如果优先级高的线程等待优先级低的线程释放锁会优先级倒置降低性能
同步方法
- synchronized方法控制对对象的访问、锁住一个方法、
- 在访问修饰符后加上synchronized的关键字
- 锁住的是类的本身相当与this
同步块
- synchronized锁一个对象
- 该对象称之为同步监视器
- 可以为任何对象,但是建议使用共享资源作为同步监视器,即变化的量。
执行过程如下
第一个线程访问,锁定同步监视器
第二个线程访问,同步监视器被锁定无法访问
第一个线程访问完毕、释放同步锁
第二个线程开始访问
14、JUC中的集合
CopyOnWriteArrayList并发编程中实现了并发编程的集合。
15、死锁
两个或者多个线程都在等待对对方释放资源,都停止执行的情形。
四个必要条件:
1、互斥条件、一个支援只能被一个进程使用
2、请求与保持条件:应为请求资源前不得释放资源
3、不剥夺条件:未使用完前不是放资源
4、循环等待条件:若干进程减形成一种头尾详解的循环等待资源
在锁中嵌套一个锁
16、Lock锁
可重用锁
拥有synchonized相同的并发性

特点对比
- Lock是显式的锁手动开启和关闭锁,synchronized是隐式锁除了作用域自动释放。
- Lock锁只有代码块锁
- Lock锁,JVM会花费较少的时间调度线程,性能会更好。
- 优先顺序:Lock>同步代码块>同步方法
17、线程协作/通信
生产者消费者模式
应用场景
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者从仓库中取走消费产品。
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待消费者取走产品
- 如果仓库中放油产品,则消费者则可以将产品取走消费,否则停止消费
线程通信API Wait、notify、notifyAll。
生产者->缓冲区(用数组代替容器大小)-> 消费者
例如:
package com.work.productor; //消费者模型 利用缓冲区解决 管惩罚 import java.awt.*; //生产者 消费者 缓冲区 public class TestPC { public static void main(String[] args) { //创建一个容器 SynContainer container = new SynContainer(); //生产者 new Productor(container).start(); //消费者 new Consumer(container).start(); } } class Productor extends Thread{ SynContainer container; public Productor(SynContainer container){ this.container = container; } //生产线程 @Override public void run() { for (int i = 0; i < 100; i++){ container.pushChicken(new Chicken(i)); System.out.println("生产了"+i+"只鸡"); } } } //消费者 class Consumer extends Thread{ SynContainer container; public Consumer(SynContainer container){ this.container = container; } //消费线程 @Override public void run() { for(int i = 0; i < 100; i++){ System.out.println("消费了"+container.pop().id+"只鸡"); } } } //产品 class Chicken{ int id; public Chicken(int id) { this.id = id; } } //缓冲区 class SynContainer{ //需要一个容器大小 Chicken chickens[] = new Chicken[10]; //容器计数器 int count = 0; //生产者放入产品 public synchronized void pushChicken(Chicken chicken){ if(count == chickens.length){ //通知消费者消费 该方法被锁住不能使用 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果没有满我们就要丢入产品 chickens[count] = chicken; count++; this.notifyAll(); } //消费者消费铲平 public synchronized Chicken pop(){ //判断能否消费 if(count == 0){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果可以消费 count--; Chicken chicken = chickens[count]; notifyAll(); //返回的被吃的鸡 return chicken; } }
- 信号灯法
- 使用标志位控制何时通知,何时等待
19、线程池
package com.work.Pool; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPool { public static void main(String[] args) { //创建线程池 //newFixedThreadPool 参数为线程池大小 ExecutorService service = Executors.newFixedThreadPool(3); //执行线程 service.execute(new MyThread()); service.execute(new MyThread()); service.execute(new MyThread()); //关闭 service.shutdown(); } } class MyThread implements Runnable{ @Override public void run() { System.out.println("开始了111"); } }
20、总结
- 创建线程三种方式 Thread Runnable Callable
- 线程通信
- juc中的安全类
- Lock的使用
- Lamda表达式