2019-11-29

1.关于线程池参数的含义
a.核心线程数
b 最大线程数
c 线程空闲时间
d 阻塞队列大小:queueCapacity
e 任务拒绝处理器 :rejectedExceptionHandler

2.实现线程池,有四种策略:
CachedThreadPool
FixedThreadPool
SingleThreadExecutor
ScheduledThreadPool

3.线程的几种状态以及它们之间如何切换
线程在一定条件下,状态会发生变化。线程一共有以下几种状态:

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:

(1)、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,

(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

(3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

在这里插入图片描述
4.volatile关键字的作用
1 保证内存可见性
2 禁止指令重排

5、Java锁的分类,像乐观锁悲观锁,共享锁和排它锁之类的。还问了一下CAS

  1. 悲观锁:悲观锁的实现是synchronized关键字和接口Lock的实现类,适用写的操作多的场景
  2. 乐观锁:乐观锁的实现是CAS算法,例如并发包下的AtomicInteger类的原子自增是CAS的自旋操作实现,下面会根据源码分析CAS算法和自旋;

CAS算法源码分析和应用场景

CAS(compareAndSwap),比较和交换,是一种无锁原子算法,有三个参数CAS(V,E,N),V表示要更新变量的值,E表示预期值,N表示新值。当V值等于E时,才会将V设置为N;如果V和E不相等,说明已经有其他线程对V做了更新,则当前线程什么都不做,最后,CAS返回当前V的真实值!CAS 操作时抱着乐观的态度进行的,它总是认为自己可以成功完成操作。
通过AtomicIntegeraddAndGet()方法,这个方法是基于CAS写的;

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
 
    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
 
    static {
        try {
            //得到value这个值在内存中的偏移量即地址
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
//共享变量使用volatile关键字修饰,保证共享变量的可见性  AtomicInteger的构造函数中对value进行复制
private volatile int value;
 
public final int addAndGet(int delta) {
//调用时Unsafe类的getAndAddInt()方法
 
 return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
 
}
 
 public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        //自旋
        do {
            //得到此时var2这个偏移量在内存中的值,即期望值var5
            var5 = this.getIntVolatile(var1, var2);
 
                    //compareAndSwapInt通过var1, var2得出实际值,然后和var5进行对比,
                    //如果相同则把var5 + var4(新值写入内存),如果不相同不断do,while循环(自旋)直到相同。
 
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
 
        return var5;
    }
 

上述代码块就是自旋算法,其中的compareAndSwapInt()是原子操作,不会引发并发问题;

CAS缺点

CAS算法高效的解决了原子操作,但是CAS仍然存在三个问题,ABA问题、循环时间长开销大、只能保证一个共享变量的原子操作

ABA问题

候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

循环时间长开销大

自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

6.Java泛型的类型擦除
Java语言的泛型采用的是擦除法实现的伪泛型,泛型信息(类型变量、参数化类型)编译之后通通被除掉了。使用擦除法的好处就是实现简单、非常容易Backport,运行期也能够节省一些类型所占的内存空间。而擦除法的坏处就是,通过这种机制实现的泛型远不如真泛型灵活和强大。Java选取这种方法是一种折中,因为Java最开始的版本是不支持泛型的,为了兼容以前的库而不得不使用擦除法。

泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换成它们非泛型上界。

7、HashMap结构,是否线程安全。ConcurrentHashMap如何保证线程安全

  1. HashMap是非线程安全的,ConurrentHashMap是线程安全的。

  2. ConcurrentHashMap将整个Hash桶进行了分段segment,也就是将这个大的数组分成了几个小的片段segment,而且每个小的片段segment上面都有锁的存在,那么在插入元素的时候就需要先找到应该插入到哪一个片段segment,然后再在这个片段上面进行插入,而且这里还需要获取segment锁。

3.ConcurrentHashMap让锁的粒度更精细一些,并发性能更好。


  1. HashTable容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器中的一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的分段锁技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

  2. get操作的高效之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读。get方法里将要使用的共享变量都定义成volatile,如果用于统计当前segment大小的count字段和用于存储值的HashEntry的value。定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写(有一种情况可以被多线程写,就是写入的值不依赖于原值),在get操作里只需要读不需要写共享变量的count和value,所以可以不用加锁。

  3. Put方法首先定位到segment,然后在segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对segment里的HashEntry数组进行扩容,第二部定位添加元素的位置然后放在HashEntry数组里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值