多线程笔记


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. 线程池线程复用的原理

  1. 使用了阻塞队列存储任务对象
  2. 规定了线程池核心线程数
  3. 每个线程都是循环执行,从任务队列取任务,执行完成再次取任务
  4. 如果线程总数大于核心线程数,则先执行的线程执行完任务会退出循环,执行结束,线程死亡,直至线程总数小于等于核心线程数

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:工具类、线程池的工程类,用于创建并返回不同的线程池

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值