线程池,多线程,面试常问的锁

线程池优化频繁创建,销毁线程的问题;

线程池解决问题的思路:把线程创建好之后,放到池子里;需要使用线程的时候就直接从线程池里取;线程用完了,就还到线程池里,而不是通过系统来销毁;

为啥把线程放到池子里,从池子里取线程就要比从系统创建线程来的快?

从线程池取,是纯用户态操作

通过系统来创建,涉及到内核态操作

我们通常认为,牵扯到内核态的操作,就要比纯用户态操作更低效;

线程池使用

创建线程池:下面就创建了固定10个线程的线程池;

里面的newFixedThreadPool是Executors的静态方法;(借助静态方法来创建实例;这样的方法称为“工厂方法”;对应的设计模式,就叫做“工厂设计模式”)

下面是 线程数量动态增加的线程池

 通过submit往线程池里提交任务,然后线程池去执行;

 工厂模式:通常情况下,创建对象,是借助new ,调用构造方法来实现的~~
但是C++ / Java里面的构造方法,有诸多限制,在很多时候不方便使用;(构造方法的限制就是构造方法名字必须和类名一样;但是想实现不同版本的构造,就得重载;重载要求参数类型合格书要不同);该模式就是规避构造方法自身的限制;


因此就需要给构造方法再包装-层,外面起到包装作用的方法,就是工厂方法~

举个例子:不用工厂模式:写两个构造方法表示一个点,一个用笛卡尔坐标系,一个用极坐标系;重载方法必须要参数列表不同,但是这两个相同就报错;

使用工厂模式:方法名不一样就避免了冲突

(越优秀的语言依赖的设计模式越少)

下面写一个简单地线程池

 class MyThreadPool{

    private BlockingQueue<Runnable> queue = new LinkedBlockingDeque<>();
//往线程池里插入任务
    public void submit(Runnable runnable) {
        try {
            queue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    //n个线程
    public MyThreadPool(int n){
//构造方法中创建线程完成上述执行任务的工作
        for(int i = 0;i<n;i++){
           Thread t = new Thread(()->{
               while(!Thread.currentThread().isInterrupted()){
                   try{
                       Runnable runnable  = queue.take();
                       runnable.run();
                   }catch (InterruptedException e){
                       e.printStackTrace();
                       break;
                   }
               }
           }) ;
           t.start();
        }
    }
}
public class Demo25{
  public static void main(String[] args){
   MyThreadPool myThreadPool = new MyThreadPool(n:10);
     for(int i=0;i<100;i++){
       myThreadPool.submit(new Runnable(){
@Override
   public void run(){
     System.out.println("hello");
  }
});
}
}

多线程的面试常考

1.乐观锁vs悲观锁

锁策略:加锁的时候咋加的问题

乐观锁:预测接下来锁冲突概率不大,要做的一类操作。

悲观锁:预测接下来加锁没有呢么容易出现锁冲突,要做的一类操作。

我们之前所了解的synchronize是一个自适应锁(既是悲观锁,也是乐观锁)

当synchronize以乐观锁的方式运行,往往是纯用户态执行的

当synchronize以悲观锁的方式运行,往往要进入内核,对当前线程进行挂起等待

 2.普通的互斥锁vs读写锁

synchronize是普通的互斥锁

读写锁就是:把加锁细化---》分为“加读锁”和“加写锁”

加读锁:如果代码只是进行读操作,就加读锁

加写锁:如果代码中只是进行了修改操作,就加写锁。

下面列举读写锁的场景:

读写锁在Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁.

ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行加锁解锁.

ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁.

 3.重量级锁vs轻量级锁

这个两个锁 和 上面的悲观乐观两个锁,含义上有一定的重叠。

悲观锁一般都是重量级的。

乐观锁一般都是轻量级的。但是也不绝对。

重量级锁,就是做了更多的事情,开销很大。主要是依赖操作系统提供的锁~这种锁就容易产生阻塞~ 在使用锁中,一般认为是基于内核的功能实现的【内核态】
轻量级锁,做的事情很少,开销也就很小。主要尽量避免使用操作系统提供的锁,而是尽量在用户态来完成功能~尽量避免用户态和内核态的切换,尽量避免挂起等待。在使用锁中,如果使用用户态去实现的,一般认为是轻量级锁。

4.自旋锁vs挂起等待锁

自旋锁:当发现冲突的时候,不会挂起等待,会迅速尝试看能不能获取到;也会更轻量,乐观锁

      优点是:一旦锁被释放,就可以第一时间获取到

      缺点是:如果锁一直不释放,就会消耗大量CPU

挂起等待锁:发现锁冲突,就挂起等待;也会更重量,悲观锁

      优点:在锁被其他线程占用时,会放弃CPU资源

      缺点:一旦锁被释放,不能第一时间获取到

 synchronize作为轻量级锁的时候,内部是自旋锁。

作为重量级锁的时候,内部是挂起等待锁。

5.公平锁vs非公平锁

公平锁:多个编程等待一把锁的时候,谁是先来的,谁就能获取到这把锁。(遵守先来后到)

  多个线程在竞争同一把锁,只有一个线程能够占用这把锁,其余的线程只能等待。那么,当我们占用锁的线程释放锁的时候,等待的线程,谁先来获取到这把锁。

synchronize提供一种非公平锁。

lock提供公平锁和非公平锁的机制。

6.可重入锁 和 不可重入锁

当连续针对同一个对象加锁两次,产生死锁,就是不可重入锁,这是个比较常见的问题~

比如:下面是一个死锁,逻辑上产生了循环依赖

 

为了避免上述情况产生,引入“可重入锁”

一个线程,可对同一个锁反复加锁多次;(可重入锁,当前加锁线程和持有锁线程是同一个,则不挂起等待,直接获取到锁;内部有计数器也会记录第几次加锁)

synchronize就是可重入锁 

面试问题

1、 你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?

    悲观锁认为多个线程访问同一个共享变量冲突的概率较大, 会在每次访问共享变量之前都去真正加锁。
   乐观锁认为多个线程访问同一个共享变量冲突的概率不大;并不会真的加锁, 而是直接尝试访问数据. 在访问的同时识别当前的数据是否出现访问冲突。
   悲观锁的实现就是先加锁(比如借助操作系统提供的 mutex), 获取到锁再操作数据,获取不到锁就等待。’
   乐观锁的实现可以通过当前的版本号与读取到的版本号对比是否一致,以确保数据的一致性。一致就执行更新操作;不一致就回滚当前操作校验过程中发现数据冲突,保证数据的正确性和并发操作的安全性;

2、 介绍下读写锁?

读写锁就是把读操作和写操作分别进行加锁.
读锁和读锁之间不互斥.
写锁和写锁之间互斥.
写锁和读锁之间互斥.
读写锁最主要用在 “频繁读, 不频繁写” 的场景中;

优势在于:允许多个线程同时读取共享数据,提高了并发性能,并保证了写操作的独占性,保证数据的一致。

 3、 什么是自旋锁,为什么要使用自旋锁策略呢,缺点是什么?

    如果获取锁失败, 立即再尝试获取锁, 无限循环, 直到获取到锁为止. 第一次获取锁失败, 第二次的尝试会在极短的时间内到来. 一旦锁被其他线程释放, 就能第一时间获取到锁。
   
    相比于挂起等待锁,
    优点: 没有放弃 CPU 资源, 一旦锁被释放就能第一时间获取到锁, 更高效。在锁持有时间比较短的场景下非常有用。
    缺点: 如果锁的持有时间较长, 就会浪费 CPU 资源。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值