线程
线程Thread
[toc]
Thread隶属于java.lang包下的类 ,java语言的JVM允许程序运行多个线程
Thread:每个线程通过特定Thread对象的run()方法来完成操作,经常把
程序,线程和进程
**程序(Program):**为了完成特定任务, 用某种语言编写的一组指令集合. 即一段静态代码,静态对象
**进程(process):**是指程序的一次执行过程, 或是正在运行的一个程序. 动态过程: 有它自身的产生, 存在和消亡的过程.
如: 运行中的软件qq 浏览器等
程序是静态的,运行的进程是动态的
**线程(Thread):**进程细化一些为线程,是一个程序内部的一条执行路径
当热程序在同一时间可以有多个执行路径, 也就是支持多线程
基本概念
java中线程分为:守护线程(daemon)和用户线程
虚拟机必须保证用户线程执行完毕,守护线程不需要等待: 如后台记录日志,内存监控,gc垃圾回收等
守护线程设置: thread.setDemon(true为守护线程)
1 | 1.线程就是独立执行的路径 |
线程的生命周期
在JDK中Thread.State枚举表示了线程的六种状态
getState():获取状态:hamburger:
- 让我们看一下源码, 看一下线程的几个状态
java
1 | // java中hread的内部枚举 |
如图
线程生命周期
线程的三种创建方式
Junit4单元测试线程异常: 具体原因不太了解但是和junit的实现有关(记到小本本–下次在了解)
一 . 继承Thread类
重写run()方法 ,并且用thread.start()启动线程
java
1 | public class ThreadBuild { |
程序中创建了一个 线程1 线程 在主线程中启动 线程间是独立执行的 ,如图:
执行方式图解
所以会看到并行交替执行的情况:
并没有顺序
在Thread中有以下代码保证了Start()方法不能重复调用:
start的源码
练习:
用多线程写图片复制/用线程实现3个售票窗口(要考虑产生资源冲突)(代码emmmm就不粘了)
二. 实现Runnable接口
实现Runnable接口并且重写run()方法
由于Thread类为一个代理类 代理了Runnable
java
1 | // 实现接口的线程创建方式 |
java
1 | // lamdba表达式(1.8新特性) |
- Thread 本身就实现了Runnable接口
- 实现方式优于继承方式
- 避免了java中单继承的局限性
- 在操作同一份资源中,更适合使用实现方式
- 一个对象的资源给多个线程使用
三. 实现Callable接口
重写的call()方法
需要执行服务和关闭服务(ExecutorService的对象 )
ExecutorService service = Executors.newFixedThreadPool(3);提交执行 submit(线程对象)
Future f1 = service.submit(c);获取返回值 记得结束服务
String s1 = (String) f1.get();service.shutdown();
Thread类常用方法
start(): 启动线程并执行响应的run()方法
run():子线程要执行的代码
currentThread(): 静态的,调用当前的线程
getName()/setName(): 获取和设置线程名字
yield(): 调用此方法的线程放弃当前cpu的执行权,礼让别的线程(我让了但是,你们抢不抢得到那不归我管)
join(): 在A线程中调用B.join()方法. (插入A线程中)
表示: A线程停止,直到B线程执行完毕 A线程在执行后面的代码
==sleep()和yield()方法==
sleep不考虑优先级/yield同优先级
sleep 后是阻塞/yield是就绪状态
sleep有异常 /而且不会释放线程 – 都不会释放标志锁
sleep比yield有更好的移植性
wait(): 当前线程等待执行wait的线程(要加同步锁 谁等就锁谁)
和notify()/notifyAll() 一起用
必须在synchronized中使用
wait会释放标志锁/sleep和yield不会
sleep是Thread的 / wait是Object的
sleep有异常需要捕获
isAlive(): 判断当前线程是否存活
sleep(long time): 当前线程睡眠time毫秒(1000ms = 1s)
- 不会释放线程
线程通信中wait()
notify():释放优先级高的等待的线程
notifyAll():释放所有等待的线程
设置线程的优先级
getPriority(): 返回当前线程优先级
setPriority(int newPriority): 改变当前线程优先级 : 正常为5,最大10,最小是1
线程安全
保证线程安全
- 尽量避免共享资源的存取冲突, 如果必须有共享资源, 那就设计一个规则(锁)来保证, 同时间只有一个线程访问资源 而且一个客户的计算工作由一个线程解决
线程不安全的集合:
线程不安全 对应线程安全 1 ArrayList CopyOnWriterArrayLis/vector 2 HashMap HashTable 3 StringBuilder StringBuffer
- Servlet/Controller线程不安全的
刚才的窗口买票程序就会出现安全问题
原因:由于多个线程在操作共享的数据时,在其中一个线程未执行完毕时,另一个线程进入,导致共享数据出现问题
线程安全出现的原因.png
如何解决线程安全的问题?
想办法热那个一个线程操作数据完毕后,其他线程才能操作
java中实现线程安全,线程同步机制:
一 同步代码块
synchronized关键字 — 要有一个对象充当同步监视器(缺点:会影响效率)
同步监视器:由一个类的对象充当.当一个线程获取监视器,就执行代码.(通一把锁的对象一定是同一个对象) — 锁
synchronized.png
1 | 实现代码: |
二 同步方法
保证同一时间只有一个线程访问此方法
用synchronized修饰方法: public synchronized void method()叫同步方法
同步监视器对象默认为当前对象
在单例模式中 – 锁的问题(双重锁定)
死锁
- 概念: 不同的线程分别占用对方的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁
- 解决办法:
- 算法, 原则
- 尽量减少同步资源的定义
LOCK锁
- 从JDk5.0开始, JAVA提供了更强大的线程同步机制–通过显式定义同步锁对象来实现同步. 同步锁使用Lock对象充当
- Java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具. 锁提供了对共享资源的独占访问, 每次只能有一个线程对Lock对象加锁, 线程开始访问共享资源之前硬获得Lock对象
- ReentrantLock(可重入锁)类实现了Lock, 它拥有与synchronized相同的并发性,在实现线程安全控制中,比较常用的是ReentrantLock, 可以显式加锁,释放锁
Lock与synchronized的对比
- Lock是显式锁(需要手动开关) synchronized是隐式锁,自动释放
- Lock只是锁代码块
- 使用Lock, JVM调度线程花费时间少,性能好. 而且具有更好的扩展性(提供更多的子类)
- 优先级:
- Lock > 同步代码块>同步方法
java
1 | private final ReentrantLock lock = new ReentrantLock(); |
ThreadLocal
一种处理并发数据的方式 将数据 隔离 每个线程单独持有
- 用空间换时间
- 创建副本进行数据隔离
要求是多个线程用一个对象,但是每个线程的对象是独立的
每个线程独立的改变自己的变量副本,不去影响其他线程
Thread里面包含 ThreadLocal
image-20200720211033368.png
image-20200720211926178.png
内部类ThreadLocalMap: k-v组成的entry[]数组
key是ThreadLocal的弱引用
- val就是值
get: 获取
set: 存储
remove: 移除
initialValue :重写
内存泄漏问题
由于key的Thread是弱引用 所以会存在key是null 但是val是强引用 就会内存泄漏
解决方法:是每一次get和set,remove时都会清楚为null的val 下一次垃圾回收就会清除了
所以调用一下remove就行了
java
1 | // 创建线程池 |
线程通信
关键字:wait(), notify()和 notifyAll() – 线程调度的方法
- wait(): 令当前线程挂起cpu/同步资源器, 使别的线程可以访问并修改公共资源,当前线程会在排队等待状态.
- notify():唤醒正在排队等待同步资源的的优先级最高的线程 – 结束等待
- notifyAll(): 唤醒正在排队等待同步资源的的所有线程 – 结束等待
注意: 这三个方法为java.lang.Object下的 而且只有在synchronized方法或代码块中才会使用.
写个生产者/消费者练习(不是OO的Pattern)
image.png
分析:
markdown
1
2
3
4 1. 是否考虑线程
2. 是否涉及共享数据
3. 如果有共享数据 考虑线程安全/同步的问题
4. 是否有线程通信
java
1 | // 管城法 |
出现的问题
在等待中推荐使用wait 如果使用 if 将会有肯能出现虚假唤醒问题:
线程可以唤醒但是不会被通知, 中断或者超时,即所谓的虚假唤醒. 发生概率虽然还很小, 但是程序必须通过测试应该使线程被唤醒的条件来防范, 并且如果条件不满足则继续等待.
等待应该总是出现在循环中
原因:
本质在于 if 和while 的区别 : if中挂起了 如果被唤醒会继续执行但是这个唤醒有可能不正确
- 两个消费者线程发现剩余0 于是唤醒生产者 自己挂起;(if判断)
- 生产者生产一个 唤醒所有消费者 两个消费者 不管谁先获得锁 都是继续if 执行
- 但是 其中一个消费过后 下一个应该挂起但是没有了判断 所以被虚假唤醒了
解决 把等待放到while中 让他循环判断
java
1 | /* |
线程池
- 将线程放入池子,方便使用和管理,也避免了销毁造成的浪费
- 3.Java线程池(工具)
- 线程池有很多种,但是核心线程池ThreadPoolExecutor。
- ExecutorService:线程池接口
- Executors(Runable 接口):工具类,线程池工厂
java
1 | //线程池:内含了一些线程数量,以及拒绝策略,阻塞队列...内容。 |
假设线程池中有20个线程,10个核心线程数,当有10个任务来临,优先使用核心线程来工作,如果又来1个任务:
- 第一,创建一个非核心线程工作
- 第二,进入阻塞队列
- 阻塞队列共有4种:S>L>A:
- SynchronousQueue:同步队列,如果有任务进入队列,直接创建一个线程工作。
- LinkedBlockingQueue:链表阻塞队列,如果有任务进入队列,直接排队,按照先进先出的规则进行执行,先排队,先执行。
- ArrayBlockingQueue:数组阻塞队列,如果有任务进入队列,直接排队,按照先进先出的规则进行执行,先排队,先执行。
- DelayQueue:延迟队列,如果有任务进入队列,先排队,但是不立即执行,而是等到延迟时间到了,再执行。
拒绝策略也有4种:当有任务来临时,如果超过了线程池的规定线程数,可以选择任务执行的策略。
- 直接丢弃(DiscardPolicy)
- 队列中最老的任务(DiscardOldestPolicy)
- 抛出异常(AbortPolicy),默认策略
- 将任务分配调用线程来执行(CallerRunsPolicy)
java
1 public class ThreadPoolDemo {
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 //定义核心线程数
private static final int MAX_CORE = 4;
//定义最大线程数
private static final int MAX_THREAD_NUM = 10;
//定义活跃时间,单位毫秒
private static final long MAX_ACTIVE_TIME = 3000;
//定义阻塞队列
private static LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
//定义一个线程执行器
private static ThreadPoolExecutor threadPoolExecutor;
//静态代码块
static {
//实例化一个线程池执行器
ThreadPoolExecutor executor = new ThreadPoolExecutor(MAX_CORE,
MAX_THREAD_NUM,
MAX_ACTIVE_TIME,
TimeUnit.MILLISECONDS,
workQueue
);
//允许核心线程超时,核心线程也在销毁之内
executor.allowCoreThreadTimeOut(true);
//将创建出来的线程池执行器赋值给成员变量
threadPoolExecutor = executor;
}
public static void main(String[] args) {
/*threadPoolExecutor = new ThreadPoolExecutor(MAX_CORE,
MAX_THREAD_NUM,
MAX_ACTIVE_TIME,
TimeUnit.MILLISECONDS,
workQueue);*/
for(int i = 1;i <= 20;i++){
Runnable r = new Runnable(){
public void run(){
try{
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
for(int i = 1;i <= 10;i++){
System.out.println("i = " + i);
}
}
};
threadPoolExecutor.execute(r);
}
}}
1
java
1 | ExecutorService service = Executors.newFixedThreadPool(3);// 数量 |
同步/异步
- 异步: (非阻塞)
- 当程序正在执行一个较长时间的程序, 不等待他的返回,就是异步
- 异步效率高.
- 同步:(阻塞)
- 当程序中存在竞争资源, 或者正在读取的数据可能被修改 就使用同步存取
- 如果有公共数据就要用同步方法
- 数据库的排它锁