java--并发

并发

  • 1.java的线程状态
    • (1)sleep wait的区别和联系
  • 2.线程池的核心参数
  • 3.lock 和 synchronized
  • 4.volatile能否保证线程安全
  • 5.java中的悲观锁和乐观锁
  • 6.Hashtable和ConcurrentHashMap
  • 7.对ThreadLocal

1.java的线程状态

new 新建:普通的类,还没有和真正的线程关联起来,调用start()之后才会和真正的线程关联起来
RUNNABLE:获取锁之后发现条件不满足,释放锁,进入WAITING等待,会被其他线程唤醒

java--并发_第1张图片

(1)sleep wait的区别和联系

共同点:wait() wait(long) sleep(long) 的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态

方法归属不同:sleep(long) 是Thread的静态方法
            wait() wait(long) 都是Object的成员方法,每个对象都有

醒来时机不同:执行 wait(long) sleep(long) 的线程都会在等待相应毫秒后醒来
            wait() wait(long) 还可以被notify唤醒,wait()如果不唤醒就一直等下去
            它们都可以被打断唤醒
       
锁特性不同:wait方法的调用必须先获取wait对象的锁,而sleep则无此限制
          wait方法执行后会释放对象锁,允许其他线程获得该对象锁
          而sleep如果在synchronized代码块中执行,并不会释放对象锁

2.线程池的核心参数

corePoolSize:核心线程数目,最多保留的线程数,可以为0
maximumPoolSize:最大线程数目,核心线程+救急线程
keepAliveTime:生存时间,针对救急线程
unit:时间单位,针对救急线程
workQueue:阻塞队列,当核心线程都在使用时,有新任务来了会加入阻塞队列,等核心线程空闲时从阻塞队列中取出执行;当
阻塞队列满了,就会调用救急线程执行任务
handler:拒绝策略,当阻塞队列满了并且线程数达到最大线程数目会使用拒绝策略

java--并发_第2张图片

3.lock 和 synchronized

(1)语法层面

synchronized是关键字,源码在JVM中,用C++实现的
lock是接口,源码由JDK提供,用java语言实现的
使用synchronized时,退出同步代码块锁会自动释放;而使用lock时,需要手动调用unlock方法释放锁

(2)功能层面

二者均属于悲观锁,都具备基本的互斥、同步、锁重入功能
lock提供了许多synchronized不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量
lock有适合不同场景的实现,如ReentrantLock,ReentrantReadWriteLock

(3)性能层面

在没有竞争时,synchronized做了很多优化,如偏向锁、轻量级锁,性能较好
在竞争激烈时,lock的实现通常会提供更好的性能

4.volatile能否保证线程安全

线程安全要考虑三个方面:可见性、有序性、原子性
可见性:一个线程对共享变量修改,另一个线程能看到最新的结果
有序性:一个线程内代码按编写的顺序执行
原子性:一个线程内多行代码以一个整体运行,期间不能有其他线程代码插队

volatile能够保证可见性与有序性,不能保证原子性

(1)可见性–不可见的原因

JVM中有一个JIT,会将一些热点的(频繁在内存调用的变量)字节码的机器码缓存起来,这样就会导致某个线程访问到的变量的值一直
都是之前某个时间点访问到的变量的值。这样就会导致即使其他线程已经修改了这个变量,他也不会读取到被修改的变量,这就造成了
不可见。

而如果使用volatile修饰后,JIT就不会去修改这个变量的值了

(2)有序性

有序性的检测需要大量数据进行压力测试
内存屏障:写变量时:保证屏障上边的数据不能在屏障下方再写,所以此时需要把加了volatile的变量放在下边执行
        读变量时:保证屏障下边的数据不能在屏障上方读取,所以此时需要把加了volatile的变量放在上边执行

5.java中的悲观锁和乐观锁

(1)悲观锁

悲观锁的代表是lock 和 synchronized
(1)核心思想是:线程只有占有了锁,才能去操作共享变量,每次只有一个线程占锁成功,获取锁失败的线程都需要停下来等待
(2)线程从运行到阻塞、再从阻塞到唤醒,涉及线程上下文切换,如果频繁发生,影响性能
(3)实际上,线程在获取lock 和 synchronized锁时,如果锁已被占用,都会做几次重试操作,减少阻塞的机会

(2)乐观锁

乐观锁的代表是AtomicInteger,使用 CAS 来保证原子性
(1)核心思想是:无需加锁,每次只有一个线程能成功修改共享变量,其它失败的线程不需要停止,不断重试直至成功
(2)由于线程一直运行,不需要阻塞,因此不涉及线程上下文切换
(3)需要多核CPU支持,且线程数不应超过CPU核数

使用CAS时需要配合volatile一起使用

6.Hashtable和ConcurrentHashMap

Hashtable和ConcurrentHashMap都是线程安全的Map集合

(1)Hashtable并发度低,整个Hashtable对应一把锁,同一时刻,只能有一个线程操作他

(2)JDK1.8之前ConcurrentHashMap使用了Segment+数组+链表的结构,每个Segment对应一把锁,如果多个线程访问不同的
Segment,则不会冲突
JDK1.8之后ConcurrentHashMap将数组的每个头节点作为锁,如果多个线程访问的头节点不同,则不会冲突

7.对ThreadLocal

ThreadLocal可以实现资源对象的线程隔离,让每个线程各用各的资源对象,避免争用引发的线程安全问题。
ThreadLocal同时实现了线程内的资源共享

原理:每个线程内有一个ThreadLocalMap类型的成员变量,用来存储资源对象
(1)调用set方法,就是以ThreadLocal自己作为key,资源对象作为value,方法当线程的ThreadLocalMap集合中
(2)调用get方法,就是以ThreadLocal自己作为Key,到当前线程中查找关联的资源值
(3)调用remove方法,就是以ThreadLocal自己作为key,移除当前线程关联的资源值

为什么ThreadLocalMap中的key(即ThreadLocal)要设计为弱引用:
(1)Thread可能需要长时间运行(如线程池中的线程)。如果key不再使用,需要在内存不足(GC)时释放其占用的内存
(2)但GC仅是让key的内存释放,后续还要根据key是否为null来进一步释放值的内存,释放时机有:
      获取key发现null key(ThreadLocalMap与其他Map集合不同,他发现要获取的ke为null时会把这个key加进去)
      set key时,会使用启发式扫描,清除临近null key,启发次数与元素个数,是否发现null key有关
      remove时(推荐),因为一般使用ThreadLocal时都把它作为静态变量,因此GC无法回收

你可能感兴趣的:(java,面试)