0
点赞
收藏
分享

微信扫一扫

线程、线程池、CAS相关面试题

树下的老石头 2022-04-06 阅读 66
面试

1.线程的创建方式

继承Thread类 

实现Runnable接口

实现Callable接口

使用线程池创建线程

--------------------------------------------------------------------------------

四种创建线程方法对比:

实现Runnable接口和实现Callable接口的方式基本相同,只不过Callable方式有方法的返回值,可以看成一种实现方式,与继承Thread相比:

多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

继承Thread类的实现方式不能继承其他的父类。

前三种方式如果创建关闭线程频繁,会消耗系统资源影响性能。在实际开发中主要使用线程池创建线程,因为线程池的线程可以回收利用,减少由线程创建和销毁所消耗的系统资源,可以提高性能。

创建线程本质只有1种,即创建Thread类,以上的所谓创建方式其实是实现run方法的方式的封装:

1、实现runnable接口的run方法,并把runnable实例作为target对象,传给thread类,最终调用target.run

2、继承Thread类,重写Thread的run方法,Thread.start会执行run方法

2.线程池的创建方式

1.使用Excetors中的静态方法进行创建六中不同的线程池

        Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
        Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
        Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
        Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
        Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
        Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】

2.创建ThreadPoolExcetor对象创建一个线程池

        

        任务拒绝策略:

        也可以自定义任务拒绝策略        

 3.对比

使用Executors的弊端是可能会创建大量的线程或者堆积大量的请求,导致oom,即java.lang.OutOfMemoryError,意思就是说,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。所以更推荐ThreadPoolExcetor方式来创建线程池,因为这种创建方式更可控,并且更加明确了线程池的运行规则,可以规避一些未知的风险。

 

   3.CAS

CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。

CAS算法: 有三个操作数(内存值V, 旧的预期值 A, 要修改的值B)

             当旧的预期值A= = 内存值   此时修改成功  将V改为B

             当旧的预期值A != 内存值   此时修改失败   不做任何操作  并重新获取现在的最新值(自旋)

CAS(乐观锁)从乐观的角度出发   假设每次获取数据别人都不会修改 所以不会上锁  只在修改共享数据的时候会检查一下

         如果修改过  重新获取数据

        如果没有修改过 就直接修改

缺点:

1.循环时间长开销很大

自循环CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。很多时候,CAS思想体现,是有个自旋次数的,就是为了避开这个耗时问题。

2.只能保证一个共享变量的原子操作:

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

自适应自旋解决的是“锁竞争时间不确定”的问题,目标是降低线程切换的成本。

3.ABA问题

如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗?

如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

利用版本号机制解决ABA问题,一般是在数据表中加上一个数据库版本号version字段,表述数据被修改的次数当数据被修改时version值会加1。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

优点:

可以避免优先级倒置和死锁等危险,竞争比较便宜,协调发生在更细的粒度级别,允许更高程度的并行机制等等。

CAS 是非阻塞的轻量级乐观锁,通过 CPU 指令实现。在资源竞争不激烈的情况下,synchronized 重量锁会进行比较复杂的加锁、解锁和唤醒操作,而 CAS 不会加锁,性能高。

        

举报

相关推荐

0 条评论