【多线程】 七

本文主要介绍了线程池的概念以及它的创建,同时包含有关线程池类的常见面试题,以及常见的锁策略。

一.线程池

如果我们需要频繁的创建和销毁线程,此时创建销毁线程的成本,就不能忽视了。

因此就可以使用线程池

提前创建好一波线程,后续需要使用线程,就可以直接从池子里拿一个即可。

本来,需要创建/销毁线程;

现在,从池子里获取现成的线程,并且把线程归还到池子中;

为什么这样更高效?

如果从系统创建,需要调用系统api,进一步的由操作系统内核完成线程的创建过程(内核给所有的进程提供服务)

如果是从线程池这里获得线程,上述的内核中进行的操作,都是提前做好的,现在的取线程的过程,是纯粹的用户代码完成(纯用户态)

1.工厂模式

Java标准库中,提供了现成的线程池。

创建线程池对象的过程,Executors称为 工厂类

我们把它创建线程的方式叫做:工厂模式

优点:

使用工厂模式可以解决构造方法的一些缺陷(构造方法名固定,但有的类需要不同的构造方法,又无法使用重载)

而工厂模式使用普通的方法来构造对象,方法名任意。

在普通方法内部,再来new对象。(由于普通的方法目的是为了创建出对象,这样的方法一般为静态。)

2.ThreadPoolExecutor线程池类

除了上述的线程池之外,标准库还提供了一个接口更丰富的线程池类

ThreadPoolExecutor类对上述做了封装。

3.经典面试题

谈谈Java标准库中,线程池类(ThreadPoolExecutor)构造方法的参数和含义。

注意:线程池里面的线程个数,并非是固定不变的,会根据当前任务的情况动态发生变化(自适应 )

参数:

1)Int corePoolSize 核心线程数

至少得有这些线程,哪怕线程池一点任务也没有

2)Int maximumPoolSize 最大线程数

最多不饿能超过这些线程,哪怕你的线程忙的冒烟,也不能比这个数目更多了

做到既能够保证繁忙的时候高效的处理任务,又能保证空闲的时候不会浪费资源。

3)Long keepAliveTime  ,  TimeUnit unit

允许线程空闲的最大时候

超过指定时间就销毁

4)BlockkingQueue workQueue

线程池内部有很多任务,这些任务,

可以使用阻塞队列来管理

5)ThreadFactory threadFactory

通过这个工厂类来创建线程

6)RejectedExecutionHandler handler

(线程池考察的重点,拒绝方式/策略)

丢弃策略:

线程池,有一个阻塞队列列

当阻塞队列满了之后,继续添加任务,该如何应对

4.丢弃策略

1)ThreadPoolExecutor.CallerRunsPolicy

谁是添加这个新任务的线程,谁就去执行这个任务

2)ThreadPoolExecutor.DiscardOldestPolicy

丢弃最早的任务,执行性的任务

3)ThreadPoolExecutor.DiscarPolicy

丢弃新的任务

继续执行之前的任务

总结:

上面谈到的线程池

一组是封装过的 Executors

一组是ThreadPoolExecutor原生的(有的公司推荐这种,阿里使用)

具体使用,主要还是看实际的需求。

二.实现一个自己的线程池

class MyThreadPool{

    private BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>();

    //通过这个方法,来把这个任务添加到线程池中
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    //创建n个线程
    public MyThreadPool(int n){
        for(int i=0;i<n;i++){
            Thread t=new Thread(()->{
                while(true){
                    //取出任务并执行
                    try {
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }
}

//线程池
public class TestDemo14 {
    public static void main(String[] args) throws InterruptedException {
        MyThreadPool pool=new MyThreadPool(4);
        for(int i=0;i<1000;i++){
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    //要执行的工作
                    System.out.println(Thread.currentThread().getName()+ "hello");
                }
            });
        }
    }
}

三.锁策略

面试题:

常见的锁策略

主要是认识几种常见的锁策略,能够知道概念。

1.乐观锁  悲观锁

不是具体的锁,抽象的概念,描述的是锁的特性

描述的是一类锁

乐观锁:预测该场景中,不太会出现锁冲突的情况

后续做的工作少

悲观锁:预测该场景中,非常容易出现锁冲突

后续做的工作多

2.重量级锁  轻量级锁

重量级锁:加锁的开销是比较大的 (花的时间多,占用系统资源多)

轻量级锁:加锁开销比较小的 (花的时间少,占用系统资源少)

一个悲观锁,很可能是重量级锁(不绝对)

一个乐观锁,很可能是轻量级锁(不绝对)

悲观乐观,是在加锁之前,对锁冲突概率的预测,决定工作的多少

重量轻量,是在加锁之后,考量实际的锁的开销。

3.自旋锁  挂起等待锁

自旋锁:是轻量级锁的一种典型实现

在用户态下,通过自旋的方式(while循环),实现类似于加锁的效果的

这种锁,会消耗一定的cpu资源,但是可以做到最快速度拿到锁

挂起等待锁:是重量级锁的一种典型实现

通过内核态,借助系统提供的锁机制,当出现锁冲突的时候,会牵扯到内核

对于线程的调度,是冲突的线程出现挂起(阻塞等待)

这种方式,消耗cpu资源更少,也就无法保证第一时间拿到锁

4.读写锁  互斥锁

读写锁:把读操作加锁和写操作加锁分开了

数据库事务 隔离级别(这两个读操作加锁 和 写操作加锁,不太一样)

事务中的读加锁,写加锁要比此处的读写锁,粒度更细,情况分更多。

注意:多线程同时去读同一个变量,不涉及到线程安全问题

如果两个线程,

一个线程读加锁,另一个线程也是读加锁,不会产生锁竞争

一个线程写加锁,另一个线程也是写加锁,会产生锁竞争

一个线程写加锁,另一个线程读加锁,也会产生锁竞争

实际开发中,读操作频率比写操作,高很多。

Java标准库中,也提供了线程的读写锁

5.公平锁  非公平锁

公平锁:遵循先来后到

非公平锁:看起来是概率均等,但是实际上是不公平的(每个线程阻塞时间不一样)

操作自带的锁属于是非公平锁

要想实现公平锁,就需要一些额外的数据结构来支持(比如需要有办法记录每个

线程的阻塞等待时间)

6.可重入锁  不可重入锁

可重入锁:有一个线程,针对同一把锁,没有产生死锁,就是可重入锁;

不可重入锁:有一个线程,针对同一把锁,如果产生了死锁,那就是不可重入锁;

这里的关键在于,两次加锁,都是同一个线程

第二次尝试加锁的时候,该线程已经有了这个锁的权限了,这个时候不应该该加锁

失败,不应该阻塞等待。

如果是不可重入锁,这把锁不会保存是哪个线程对它加的锁,只要它当前处于加锁状态之后,收到了 加锁 这样的请求,就会拒绝当前加锁,而不管当下的线程是哪个,就会产生死锁;

如果是可重入锁,则是会让这个锁保存,是哪个线程加上的锁,后续收到加锁请求之后,就会先对比一下,看看加锁的线程是不是当前持有这把锁的线程,灵活判定;

实际上,synchronized本身就是一个可重入锁,不会产生死锁;

锁什么时候释放?

让锁拥有一个计数器:

让锁对象不光要记录是哪个线程持有的锁,同时再通过一个整型变量记录当前

线程加了几次锁

每遇到一个加锁操作,计数器加1;

每遇到一个解锁操作,计数器减1;

当减到0的时候,才真正执行释放锁操作,其他时候不释放

也叫做  引用计数。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值