title: 多线程
date: 2021/7/10
1. 线程的生命周期及状态
新生、就绪、运行、阻塞、死亡
2. sleep、wait、join、yield
3. 对线程安全的理解
例子1:假如有10张票,三个人同时购买,由于没有线程同步,则他们有可能买到同一张票,当只剩最后一张票时,有可能两个人甚至三个人都抢到了最后一张票,则有可能出现负数的票
例子2:你的银行账户有100块,你和媳妇一起取钱,一个人取50块,另一个人取100块,可能会造成银行亏钱
例子3:list集合在添加数值时,两个线程在同一时刻将数值添加到了同一位置,会造成元素数量减少
解决:synchronized(队列+锁)关键字
1.同步方法
2.同步块:synchronized(Obj){},监视的对象obj是需要进行增删改查的对象
原理:synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
4. Thread和Runnable
5. 说说你对守护线程的理解
线程分为用户线程和守护线程
虚拟机必须确保用户执行完毕
用户线程执行完毕后,虚拟机不用等待守护线程执行完毕,会立即停止
如后台记录操作日志,监控内存,垃圾回收等待
thread.setDaemon(true); //默认是false表示用户线程,正常的线程都是用户线程
6. Thread Local的原理及使用场景
**ThreadLocal的作用:**主要是做数据隔离,一个ThreadLocal在一个线程中是共享的,在不同线程之间又是隔离的(每个线程都只能看到自己线程的值),填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,防止自己的变量被其它线程篡改。
原理:
每个Thread对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。
存入的ThreadLocal中的数据实际上并没有存到ThreadLocal对象中去,而是以这个ThreadLocal实例作为key存到了当前线程中的一个Map中去了,获取ThreadLocal的值时同样也是这个道理。这也就是为什么ThreadLocal可以实现线程之间隔离的原因了。
**使用场景:**1.保存每个线程独享的对象、为每个线程创建一个副本,每个副本只为当前的线程服务,这样每个线程可以修改自己所拥有的副本
2.保存每个线程中需要独立保存的信息,以便其他方法可以方便的获取
7. ThreadLocal内存泄漏问题,如何避免
**什么是内存泄漏:**程序中已经动态分配的堆内存由于某种原因, 程序未释放或者无法释放, 造成系统内部的浪费, 导致程序运行速度减缓甚至系统崩溃等严重结果. 内存泄漏的堆积终将导致内存溢出
**ThreadLocal内存泄漏:**如果是在线程池中,线程执行完后不被回收,而是返回线程池中,Thread有个强引用指向ThreadLocalMap,ThreadLocalMap有强引用指向Entry,导致value无法被回收,一直存在内存中。
**解决方法:**在执行了ThreadLocal.set()方法之后一定要记得使用ThreadLocal.remove(),将不要的数据移除掉,避免内存泄漏
8. 并发、并行、串行
9. 并发的三大特性
10. 为什么使用线程池,参数解释
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
7个参数:
1.corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
2.maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
3.keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
4.unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
5.workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
6.threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
7.handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
11. 线程池线程复用的原理
- 使用了阻塞队列存储任务对象
- 规定了线程池核心线程数
- 每个线程都是循环执行,从任务队列取任务,执行完成再次取任务
- 如果线程总数大于核心线程数,则先执行的线程执行完任务会退出循环,执行结束,线程死亡,直至线程总数小于等于核心线程数
12. 创建线程的两种方式
12.1 实现Thread类
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
public class TsetThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码---"+i);
}
}
public static void main(String[] args) {
//main线程,主线程
//1.创建一个线程对象
TsetThread1 tsetThread1 = new TsetThread1();
//2.调用start()方法开启线程
tsetThread1.start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程--"+i);
}
}
}
12.2 实现Runnable
//创建线程方式二:实现Runnable接口,重写run()方法,调用start开启线程
public class TsetThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码---"+i);
}
}
public static void main(String[] args) {
//main线程,主线程
//1.创建一个线程对象
TsetThread2 tsetThread2 = new TsetThread2();
//创建线程对象,通过线程对象来开启线程,代理
Thread thread = new Thread(tsetThread2);
thread.start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程--"+i);
}
}
}
13. Lambda表达式
- lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹
- 前提是接口为函数式接口(只有一个方法)
- 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
public class TsetLambda {
public static void main(String[] args) {
Ilove love = null;
love = (a, b, c) -> {
System.out.println("I Love you-->"+a+b+c);
};
love.love(1,2,3);
}
}
interface Ilove{
void love(int a,int b,int c);
}
简化流程;普通接口实现类->静态内部类->局部内部类->匿名内部类->lambda表达式
14. sleep
thread.sleep(100) //停止100ms
- sleep指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
15. yield(线程礼让)
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让CPU重新调度,礼让不一定成功,看CPU心情
public class TsetYield{
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}
16. Join
join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程vip来了"+i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动TestJoin的线程
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
//启动主线程
for (int i = 0; i < 500; i++) {
if(i==200){
thread.join();//插队
}
System.out.println("main"+i);
}
}
}
17. 线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
线程的优先级用数字表示,范围从1-10
Thread.MIN_PRIOPRITY = 1;
Thread.MAX_PRIOPRITY = 10;
Thread.NORM_PRIOPRITY = 5;
使用以下方式改变或获取优先级
getPriority().setPriority(int ×××)
18.线程不安全的例子
18.1 不安全买票
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
Thread thread1 = new Thread(buyTicket,"我");
Thread thread2 = new Thread(buyTicket,"你");
Thread thread3 = new Thread(buyTicket,"黄牛");
thread1.start();
thread2.start();
thread3.start();
}
}
class BuyTicket implements Runnable{
//票的数量
private int ticketNums = 10;
//外部停止方式
boolean flag = true;
@Override
public void run() {
//买票
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//买票的方法,synchronized同步方法,锁的是this
private synchronized void buy() throws InterruptedException {
//判断是否有票
if (ticketNums<=0) {
flag = false;
return;
}else {
//模拟延时
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "拿到" + ticketNums--);
}
}
}
18.2 不安全取钱
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100, "结婚基金");
Drawing you = new Drawing(account, 50, "你");
Drawing girlfriend = new Drawing(account, 100, "girlfriend");
you.start();
girlfriend.start();
}
}
//账户
class Account{
int money; //余额
String name; //卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行
class Drawing extends Thread{
Account account;//账户
int drawingMoney;//取了多少钱
int nowMoney; //你手里的钱
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//取钱
@Override
public void run() {
//锁的对象是变化的量,需要增删改的对象
synchronized (account){
//判断有没有钱
if(account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了!");
return;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额
account.money = account.money-drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"的余额为:"+account.money);
System.out.println(Thread.currentThread().getName()+"手里的钱:"+nowMoney);
}
}
}
18.3 不安全集合
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(3000);
System.out.println(list.size());
}
}
19. 锁
19.1 死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上的对象的锁”时,就可能会发生“死锁”的问题
19.2 Lock锁
private final ReentrantLock lock = new ReentranLock();
try{
lock.lock();//加锁
...
}finally{
//解锁
lock.unlock();
}
20. wait()
作用:表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout):指定等待的毫秒数
21. 线程池
public class TestPool {
public static void main(String[] args) {
//创建服务,创建线程池
//newFixedThreadPool 参数为线程池的大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
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(Thread.currentThread().getName());
}
}
ExecutorService:真正的线程池接口
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
void shutdown():关闭连接池
Executors:工具类、线程池的工程类,用于创建并返回不同的线程池