Java总结PART4

part4 多线程

080 线程优先级

Q: Java 线程优先级是怎么定义的,Java 线程有几种状态?

  1. Java 线程的优先级定义为从 1 到 10 的等级,默认为 5,设置和获取线程优先级的方法是 setPriority(int newPriority) 和 getPriority(),优先级对操作系统而言更多的是一种建议和提示而非强制
  2. Java Thread 可以通过 State getState() 来获取线程状态,Thread.State 枚举定义了 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 六种线程状态,其实真正严格意义来说线程只有就绪、阻塞、运行三种状态,Java 线程之所以有六种状态其实是站在 Thread 对象实例的角度来看待的
    • NEW(新建),表示线程 Thread 刚被创建,还没调用 start 方法。
    • RUNNABLE(运行,实质对应就绪和运行状态),表示 Thread 线程正在 JVM 中运行,也就是说处于就绪和运行状态的线程在 Thread 中都表现为 RUNNABLE。
    • BLOCKED(阻塞,实质对应阻塞状态),表示等待监视锁可以重新进行同步代码块中执行,此线程需要获得某个锁才能继续执行,而这个锁目前被其他线程持有,所以进入了被动的等待状态,
    • WAITING(等待,实质对应阻塞状态),表示此线程正处于无限期的主动等待中,直到有人唤醒它它才会再次进入就绪状态。某一线程因为调用下不带超时值的 wait、不带超时值的 join、LockSupport.park 会进入等待状态,
    • TIMED_WAITING(有限等待,实质对应阻塞状态),表示此线程正处于有限期的主动等待中,要么有人唤醒它,要么等待够了一定时间之后才会再次进入就绪状态,譬如调用带有超时的 sleep、join、wait 方法可能导致线程处于等待状态。
    • TERMINATED(终止),表示线程执行完毕,已经退出。

081 Synchronized

Q: 简单谈谈你对 Java synchronized 关键字的理解?

  1. synchronized关键字主要用来解决多线程并发同步问题,可以修饰类的静态方式,实例方法,代码块
  2. synchronized具备可重入性,对同一个线程获得锁之后再调用其他需要同样锁的代码时可以直接调用
  3. synchronized具备可见性
  4. synchronized是重量级锁,底层通过一个监视器对象来完成,监视器的本质依赖于底层操作系统的互斥锁事项
  5. synchronized在发生异常会自动释放线程占用的锁资源

082 死锁

Q:什么是死锁?请模拟写出一段 Java 死锁的核心代码?如何避免死锁?

  1. 关于什么是死锁其实就是陷入互相等待谁都无法执行的一种状态,譬如有 A B 两个线程,A 持有 LockA 锁且在等待 LockB 锁,而 B 持有锁 LockB 且在等待 LockA 锁,LockA LockB 陷入互相等待导致谁都无法执行的一种现象

  2. 避免死锁:

    • 考虑加锁顺序: 当多个线程需要相同的一些锁但每个线程又按照不同顺序加锁则很容易发生死锁(如上面死锁的例子),如果能确保所有的线程都是按照相同的顺序获得锁则发生死锁的情况就不存在了
    • 考虑加锁时限: 可以在尝试获取锁的时候加一个超时时间,若一个线程没有在给定的时限内成功获得所有需要的锁则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试,这段随机的等待时间让其它线程有机会尝试获取相同的这些锁,并且让该应用在没有获得锁的时候可以继续运行。

083 线程停止

Q:说说你知道的 Java 停止线程的方式及优劣点?

  1. Thread的stop():恶意中断,一旦执行stop,无法保证线程逻辑是否完成,同时会破坏原子性操作;
  2. Thread的interrupt():看场景,中断不是强迫终止一个线程,而是给对应线程传递取消信号的协作机制,由对应的线程决定如何及时推出;线程的对象isInterrupted()返回中断标志是否为true,线程的静态方法interrupted()返回中断标志,但是会清空中断标志位

缺点:这种方式中断线程需要看场合决定,如果线程处于 RUNNABLE 状态且无 IO 操作则 interrupt() 方法只会设置线程的中断标志位而无其他任何作用,所以这种情况下我们需要在线程运行的合适位置检查中断标记来中断线程;如果线程处于 WAITING、TIMED_WAITING 状态则 interrupt() 方法会使该线程抛出 InterruptedException 异常且在异常抛出后中断标志会被清空;如果线程处于 BLOCKED 锁等待状态则 interrupt() 方法只会设置线程的中断标志位而无其他任何作用

  1. 自定义中断信号量方式,这种方式中断 RUNNABLE 状态的线程也是很常见的,使用 volatile 的原子性共享标记变量来告诉线程必须停止正在运行的任务。

084 原子变量

Q:什么是原子变量?为什么需要他们?Java 提供了哪些原子变量类型?

  1. 原子变量就是原子操作,是在多线程环境下避免数据不一致必须的手段
  2. synchronized 是一种阻塞式算法的悲观锁,但是原子变量的更新逻辑是非阻塞式乐观的,synchronized 得不到锁就会进入等待状态和有线程切换开销,原子变量更新冲突时会循环重试,不会阻塞和上下文切换开销。

085 内存模型

Q:简单谈谈你对 Java 虚拟机内存模型 JMM 的认识

  1. 基本模型: Java 内存模型规定所有变量都存储在主内存中,每条线程还有自己的工作内存,工作内存保存了该线程使用到的变量到主内存副本拷贝,线程对变量的所有操作(读取、赋值)都必须在工作内存中进行而不能直接读写主内存中的变量,不同线程之间无法相互直接访问对方工作内存中的变量,线程间变量值的传递均需要在主内存来完成
  2. 操作:lock,unlock,read,load,store,write,use,assign
    如果要把一个变量从主内存复制到工作内存就必须按顺序执行 read 和 load 操作,从工作内存同步回主内存就必须顺序执行 store 和 write 操作
  3. 可见性,有序性,原子性

086 可见性,有序性,原子性

Q:并发中的原子性、可见性、有序性的理解?

  1. 原子性:一个操作不能被打算,要么执行要么不执行,Java基本类型数据的访问大都是原子操作,long,double在32位JVM不是原子性操作;保证原子性:synchronized,atomic
  2. 可见性: 当一个线程对共享变量做了修改后其他线程可以立即感知到该共享变量的改变。volatile,final,lock,synchronized,atomic等可以保证可见性
  3. 有序性: java内存模型的指令重排序不会影响单线程的执行顺序,但是会影响多线程并发执行的正确性;除lock,volatile等关键字外,java内存模型通过happen-before原则保证一定的执行顺序。

087 volatile

Q:volatile 关键字的理解和使用场景举例?

  1. volatile修饰的变量具备可见性(一个线程修改一个变量的值对于另一个线程来说是立即可见的)与有序性(禁止指令重排序) ,
  2. 有序性的保证:当程序执行到volatile变量的读或写操作时其在其前面的操作的更改已经全部进行且结果已经对后面的操作可见,
  3. 使用场景:多线程自定义条件标记变量中断和单例模式的double check等

088 锁

Q: 简单说说你所了解的 Java 锁分类和特点有哪些?

  1. 公平锁、非公平锁:公平锁指多个线程按照申请锁的顺序来获取锁,非公平锁是没有顺序完全随机,所以可能造成优先级反转或者饥饿;synchronized 就是非公平锁,ReentrantLock(使用 CAS 和 AQS 实现) 通过构造参数可以决定是非公平锁还是公平锁,默认构造是非公平锁;非公平锁的吞吐量性能比公平锁大好。
  2. 可重入锁:又名递归锁,指在同一个线程在外层方法获取锁的时候在进入内层方法会自动获取锁,synchronized 和 ReentrantLock 都是可重入锁,可重入锁可以在一定程度避免死锁。
  3. 独享锁、共享锁:独享锁是指该锁一次只能被一个线程持有,共享锁指该锁可以被多个线程持有;synchronized 和 ReentrantLock 都是独享锁,ReadWriteLock 的读锁是共享锁,写锁是独占锁;ReentrantLock 的独享锁和共享锁也是通过 AQS 来实现的
  4. 乐观锁、悲观锁:这个分类不是具体锁的分类,而是看待并发同步的角度;悲观锁认为对于同一个数据的并发操作一定是会发生修改的,因此对于同一个数据的并发操作悲观锁采取加锁的形式,;乐观锁则认为对于同一个数据的并发操作是不会发生修改的,在更新数据的时候会采用不断的尝试更新,乐观锁认为不加锁的并发操作是没事的;由此可以看出悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升,悲观锁在 java 中很常见,乐观锁其实就是基于 CAS 的无锁编程,譬如 java 的原子类就是通过 CAS 自旋实现的。
  5. 分段锁:实质是一种锁的设计策略,不是具体的锁,对于 ConcurrentHashMap 而言其并发的实现就是通过分段锁的形式来实现高效并发操作;当要 put 元素时并不是对整个 hashmap 加锁,而是先通过 hashcode 知道它要放在哪个分段,然后对分段进行加锁,所以多线程 put 元素时只要放在的不是同一个分段就做到了真正的并行插入,但是统计 size 时就需要获取所有的分段锁才能统计;分段锁的设计是为了细化锁的粒度。
  6. 自旋锁:其实是相对于互斥锁的概念,互斥锁线程会进入 WAITING 状态和 RUNNABLE 状态的切换,涉及上下文切换、cpu 抢占等开销,自旋锁的线程一直是 RUNNABLE 状态的,一直在那循环检测锁标志位,机制不重复,但是自旋锁加锁全程消耗 cpu,起始开销虽然低于互斥锁,但随着持锁时间加锁开销是线性增长。
  7. 可中断锁:synchronized 是不可中断的,Lock 是可中断的,这里的可中断建立在阻塞等待中断,运行中是无法中断的

089 await,signal,signalAll

Q: Java 并发显式协作 Condition 的 await、signal、signalAll 理解?同时说说他们和普通并发协作 wait、notify、notifyAll 等方法的区别?

  1. Java 显式锁 Lock 可以认为是对 synchronized 的升级,所以 java 1.5 出现的显式协作 Condition 接口的 await、signal、signalAll 也可以说是普通并发协作 wait、notify、notifyAll 的升级;普通并发协作 wait、notify、notifyAll 需要与 synchronized 配合使用,显式协作 Condition 的 await、signal、signalAll 需要与显式锁 Lock 配合使用(Lock.newCondition()),调用 await、signal、signalAll 方法都必须在 lock 保护之内。
  2. 和 wait 一样,await 在进入等待队列后会释放锁和 cpu,当被其他线程唤醒或者超时或中断后都需要重新获取锁,获取锁后才会从 await 方法中退出,await 同样和 wait 一样存在等待返回不代表条件成立的问题,所以也需要主动循环条件判断;await 提供了比 wait 更加强大的机制,譬如提供了可中断或者不可中断的 await 机制等;

090 ThreadLocal

Q: ThreadLocal 的认识?其与同步机制有何区别?其实现原理及使用场景和存在的问题有哪些?

  1. 理解:ThreadLocal 不是用来解决对象共享访问问题的,而是线程本地变量为了隔离本地变量,不让其他线程访问操作,其 set 的对象作用域为当前线程内部,生命周期伴随线程执行而终止,多个线程间不共享
  2. 是一个泛型类,提供的方法: get,set,initialValue,remove
  3. ThreadLocal 和同步机制面对的是不同场景的问题,两者没有任何关系,同步机制是为了同步多线程对相同资源的并发访问,是为了线程间数据共享的问题,而 ThreadLocal 是隔离多线程数据共享,从根本上就不在多个线程之间共享资源
  4. 原理:每个线程都有自己的类型为ThreadMap的map,调用set实际是在线程自己的 Map 里设置了一个条目,键为当前 ThreadLocal 实例,值为 set 方法的泛型 value 参数,ThreadLocalMap 是一个专门用于 ThreadLocal 的一个静态内部类,与一般的 Map 不同(和一般 Map 也没有继承等任何关系),它的键类型为 WeakReference;其 get 方法的实现是拿到当前线程的 ThreadLocalMap 引用,以当前 ThreadLocal 实例为键从 Map 中获取条目取其 value 值,如果 Map 中没有就返回 initialValue 方法初始化的值;所以通过每个 Thread 自己的 Map 存取 value 就达到了线程本地变量的效果。
  5. 场景: ThreadLocal 的使用场景核心原则就是按线程多实例(每个线程对应一个实例)的对象访问,并且这个对象很多地方都会用到,譬如 Android 的 Looper 实现等,达到每个线程只初始化一次对应实例等效果,其他应用场景譬如解决数据库连接,Session 管理等。
  6. 注意事项: 一般使用 ThreadLocal 都不会将共享变量放到线程的 ThreadLocal 中;使用 ThreadLocal 配合线程池使用时要特别注意,线程池中的线程是复用的机制,set 的值会被带到下一个任务中,所以这种情况下一定要记得使用 ThreadLocal 前先复位初值或者使用后 remove 清空

091 线程池 ThreadPoolExecutor

Q: 谈谈你对 java 线程池 ThreadPoolExecutor 与 ScheduledThreadPoolExecutor 的理解及相关构造参数的含义?

  1. 线程池由任务队列和工作线程组成,可以重用线程来避免线程创建的开销;任务过多时,通过排队避免创建过多线程来减少系统资源消耗与竞争。确保任务有序完成
  2. 继承树: ThreadPoolExecutor,继承AbstractExecutorService,实现ExecutorService接口;ScheduleThreadPoolExecutor继承ThreadPoolExecutor接口,实现了ExecutorSerivce,SecheduleExecutorSerive接口,
  3. 参数含义:

        public ThreadPoolExecutor(int corePoolSize,int maximunPoolSize,long keepAliveTime
                                    BlockingQueue<Runnable>workQueue,ThreadFactory threadFactory,
                                    RejectedExecutionHandle handler){...}
    1. corePoolSize:核心运行的线程个数,超过这个数,将新的异步任务放入到等待队列中,小于这个数添加进来的异步任务直接新建Thread
    2. maxiumPoolSize: 最大线程个数,当大于了这个值就会将准备新加的异步任务由一个丢弃处理机制来处理,大于 corePoolSize 且小于 maximumPoolSize 则新建 Thread 执行,但是当通过 newFixedThreadPool 创建的时候,corePoolSize 和 maximumPoolSize 是一样的,而 corePoolSize 是先执行的,所以他会先被放入等待队列而不会执行到下面的丢弃处理中;
    3. workQueue:任务等待队列,当达到 corePoolSize 的时候就向该等待队列放入线程信息(默认为一个LinkedBlockingQueue);
    4. keepAliveTime:默认是 0,当线程没有任务处理后空闲线程保持多长时间,不推荐使用;
    5. threadFactory:是构造 Thread 的方法,一个接口类,可以使用默认的 default 实现,也可以自己去包装和传递,主要实现 newThread 方法即可;
    6. handler:当参数 maximumPoolSize 达到后丢弃处理的方法实现,java 提供了 5 种丢弃处理的方法,当然你也可以自己弄,主要是要实现接口 RejectedExecutionHandler 中 rejectedExecution(Runnabler, ThreadPoolExecutor e) 方法,java 默认使用的是 AbortPolicy,他的作用是当出现这种情况的时候抛出一个异常

092 CAS

Q: 简单谈谈你对 CAS 的认识和理解?

  1. CAS(compare and swap)实质就是比较交换策略,设计并发算法时常用到的一种技术,java.util.concurrent 包基本都建立在 CAS 之上,CAS 有内存值V、旧的预期值A、要修改的值B三个操作数,当且仅当预期值A和内存值V相同时才会将内存值修改为B并返回 true,否则什么也不做且返回 false。
  2. CAS 是通过 Unsafe 类来实现的,Unsafe 提供了硬件级别的原子操作,
  3. 场景:CAS 最常见和基础的使用地方在 java.util.concurrent.atomic 包下面
  4. 问题:ABA 问题如果一个变量 V 初次读取时值为 A,并且在准备赋值时检查到仍然是 A,而这时候又在多线程的场景下我们是无法确认这段时间之内是否值被其他线程先修改为 B 接着改回了 A,解决方案: 处理 ABA 场景就可以使用 AtomicStampedReference,在修改值的同时传入一个唯一标记(譬如时间戳),只有值和标记都想等才进行修改,使用 AtomicMarkableReference 也可以,只不过标记是 boolean 类型。

093 饥饿与死锁

Q:并发饥饿与死锁的区别?

  1. 饥饿是指系统不能保证某个进程的等待时间上界,从而使该进程长时间等待,当等待时间给进程推进和响应带来明显影响时,称发生了进程饥饿
  2. 死锁是指在多道程序系统中,一组进程中的每一个进程都无限期等待被该组进程中的另一个进程所占有且永远不会释放的资源。
  3. 相同点:竞争资源而引起的
  4. 不同点:
    • 死锁进程等待永远不会被释放的资源,饿死进程等待会被释放但却不会分配给自己的资源,表现为等待时限没有上界
    • 死锁一定发生了循环等待,而饿死则不然。这也表明通过资源分配图可以检测死锁存在与否,但却不能检测是否有进程饿死;
    • 死锁一定涉及多个进程,而饥饿或被饿死的进程可能只有一个
    • 在饥饿的情形下,系统中有至少一个进程能正常运行,只是饥饿进程得不到执行机会,而死锁则可能会最终使整个系统陷入死锁并崩溃

094 sleep yield

Q: Thread 的 sleep() 和 yield() 方法的区别?
1. Sleep() 方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;而 yield() 方法只会给相同优先级或更高优先级的线程以运行的机会,甚至可能会是自己继续得到运行机会。
2. 线程执行 sleep() 方法后转入阻塞(blocked)状态,而执行 yield() 方法后转入就绪(ready)状态。
3. sleep() 方法声明抛出 InterruptedException,而 yield() 方法没有声明任何异常。
4. sleep() 方法比 yield() 方法(跟操作系统 CPU 调度相关)具有更好的可移植性。

095 JOIN yield

Q: Thread 的 join() 和 yield() 方法的区别?

  1. join() 方法的作用是让 “主线程” 等待 “子线程” 结束之后才能继续运行。
  2. yield() 方法的作用是可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法而且只保证当前线程放弃 CPU 占用而不能保证使其它线程一定能占用 CPU,执行 yield() 的线程有可能在进入到暂停状态后马上又被执行。

096 Sleep wait

Java 中 sleep() 与 wait() 方法的区别?

  1. 概述:sleep() 方法使当前线程进入停滞状态(阻塞当前线程),让出 CUP 的使用,目的是不让当前线程独自霸占该进程所获的 CPU 资源该方法是 Thread 类的静态方法,当在一个 synchronized 块中调用 sleep() 方法时,线程虽然休眠了,但是其占用的锁并没有被释放;当 sleep() 休眠时间期满后,该线程不一定会立即执行,因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。
  2. 概述: wait() 方法是 Object 类的,当一个线程执行到 wait() 方法时就进入到一个和该对象相关的等待池中,同时释放对象的锁(对于 wait(long timeout) 方法来说是暂时释放锁,因为超时时间到后还需要返还对象锁),其他线程可以访问。wait() 使用 notify() 或 notifyAll() 或者指定睡眠时间来唤醒当前等待池中的线程。wait() 必须放在 synchronized 块中使用,否则会在运行时抛出 IllegalMonitorStateException 异常。
  3. 区别:
    • sleep() 不释放同步锁,wait() 释放同步锁。
    • sleep() 是 Thread 的静态方法,而 wait() 是 Object 的方法。
    • sleep(milliseconds) 可以用时间指定来使他自动醒过来,如果时间没到则只能调用 interreput() 方法来强行打断(不建议,会抛出 InterruptedException),而 wait() 可以用 notify() 直接唤起
    • wait()、notify()、notifyAll() 方法只能在同步控制方法或者同步控制块里面使用,而 sleep() 方法可以在任何地方使用。

097 锁池与等待池

Q:简单说说你对 Java 对象的锁池与等待池的理解?

  1. 锁池: 假设线程A已经拥有了某个对象的锁,而其他线程线程b,c想要调用这个对象的某个Synchronized方法,那么b,c就进入该对象的锁池
  2. 等待池: 线程A调用某个对象的wait方法,线程A就释放这个对象的锁,同时线程A就进入到该对象的等待池中,如果此时线程B调用了相同对象的notifyAll/notify,则处于该对象等待池中的线程就会全部/只有一个 进入该对象的锁池中准备争夺锁的拥有权,
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值