JAVA虚拟机JVM-7.多线程常见问题刨析

Posted 名字可以起这么长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA虚拟机JVM-7.多线程常见问题刨析相关的知识,希望对你有一定的参考价值。

多线程概念

进程与线程

进程是操作系统进行资源分配的最小单位,CPU从一个进程切换到另一个进程叫做进程上下文切换。

线程是CPU调度的最小单位,是进程的一部分,由进程创建,一个进程拥有1~N个线程。线程又分为用户线程和守护线程,两者的区别是,后者会随着主线程结束而结束。

Thread线程类

继承thread类,重写run()方法即可。

wait和slepp的区别

  • wait()方法必须在synchornized同步代码块中使用。
  • wait()方法会释放由synchornized锁上的对象锁,而sleep()不会。
  • 由wait()方法形成的阻塞,可以通过针对同一个对象锁的synchornized作用域调用notify()或者notifyAll()来“唤醒”,而sleep()无法被唤醒,只能定时醒来或者被interrupt()中断。

sleep和yield的区别

  • sleep()执行后转入阻塞,在一段时间后自动醒来,回到就绪状态;而yield方法后,线程直接转入就绪状态。
  • sleep()执行后,其他线程无论优先级高低都可以获取机会运行;而执行yield()只会给相同优先级或者更高优先级的线程运行的机会。
  • sleep()会抛出interruptedException,而yield()没有任何异常声明。
  • sleep()比yield()更好移植,在循环中使用yield()容易产生死循环,当前线程优先级很高,执行yield之后又抢占到了CPU。

Runnable接口

实现runnable接口,实现run()方法。线程池也只能接受Runnable或者Callable接口类型的对象作为线程池任务。

线程池

线程的创建和销毁会消耗资源,在大量并发的情况下,最好是预先创建多个线程,并集中管理,形成一个线程池。

Executor

Java 最开始提供了 ThreadPool 实现了线程池,为了更好地实现用户级的线程调度,更有效地帮助开发人员进行多线程开发,Java 提供了一套 Executor 框架。

 这个框架中包括了 ScheduledThreadPoolExecutor 和 ThreadPoolExecutor 两个核心线程池。前者是用来定时执行任务,后者是用来执行被提交的任务。调用他们的submit()以及execute()方法向线程池提交任务。鉴于这两个线程池的核心原理是一样的,Executors 实现了以下四种类型的 ThreadPoolExecutor:

Callable和Future

这两个接口可以使线程能执行带返回值的任务。

Wait()和Notify()、NotifyAll()

wait()方法会使当前当前线程放弃它持有的对象锁,而进入阻塞状态,而notify()、notifyAll()不会释放对象锁。

线程安全的容器

ConcurrentHashMap

将自身空间划分成若干个segment,并在每个分段各应用一个锁(ReentrantLock),这样减少了锁竞争,也允许一定数量的线程进行读操作,从而提高了并发能力。

ConcurrentSkipListMap/ConcurrentSkipListSet

这两个容器分别是并发的TreeMap以及TreeSet。跳跃表是可以替代平衡二叉树的数据结构,通过“空间换取时间”的算法,实现了线程安全的排序映射表。

CopyOrWriteArrayList/CopyOrWriteArraySet

CopyOrWrite本质是利用高并发时候读多写少的情况,对读不加锁,写的时候复制一份新的集合,在新的集合上面修改,再用新集合替换旧集合。后者是由前者实现的,不同的set在add方法的时候需要调用addIfAbsent()方法,其遍历当前数组。(去掉重复)

ConcurrentLinkedQueue/ConcurrentLinkedDeque

基于链表实现的非阻塞式并发队列和双端队列。

BlockingQueue/BlockingDeque

阻塞式并发队列接口,提供可阻塞的插入put方法以及获取take方法。调用的时候如果队列已满,put方法将阻塞当前线程,队列数据为空时,take方法阻塞当前线程。

ThreadLocal

成为线程本地变量或者线程本地存储,作用是为当前线程提供临时持有和传递对象的方法。

CountDownLatch计数器

java.util.current.CountDownLatch计数器,相当于一个倒序计数器,用来协调多个线程的执行。多个线程调用他们共享的计数器,的countDown()方法让计数器减一。可以通过CountDownLatch对象的await()方法阻塞线程,知道计数器的值为0。

CyclicBarrier栅栏

java.util.current.CyclicBarrier是一种可以重用的线程阻塞器。通过调用await()方法在代码中形成栅栏,率先到达栅栏的线程被栅栏阻塞,知道指定的数量的线程都达到栅栏处。

Semaphore

是用于保护一个或者多个共享资源的访问。内部维护一个计数器,表示同时访问共享资源的的数量。当前成访问共享资源,先要获取一个信号量,如果信号量计数器值大于1,那么可以访问,否则线程被阻塞。

fork/join框架

思想就是将大任务拆分若干个小任务,提高任务的处理速度。

ForkJoinPoll

专门用于执行ForkJoinTask的线程池,而ForkJoinTask被用于封装可以拆分的任务对象,它的两个子类RecursiveActionRecusiveTask分别用于有返回值的任务对象和没有返回值的任务对象。

为了提高多线程处理的效率,避免出现饥饿,Java的fork/join框架采用了一种名为“工作窃取”的方法。每个线程都有自己的双端任务队列,一般情况下线程从头部获取任务,当某个任务队列为空的时候,它会尝试从其它线程任务队列的尾部“窃取任务”来执行。

 

Executors 利用工厂模式实现的四种线程池,我们在使用的时候需要结合生产环境下的实际场景。不过我不太推荐使用它们,因为选择使用 Executors 提供的工厂类,将会忽略很多线程池的参数设置,工厂类一旦选择设置默认参数,就很容易导致无法调优参数设置,从而产生性能问题或者资源浪费。

以上是关于JAVA虚拟机JVM-7.多线程常见问题刨析的主要内容,如果未能解决你的问题,请参考以下文章

JAVA虚拟机内存模型

《Java多线程编程核心技术一》--- 快速认识线程

Java虚拟机最多支持多少个线程的探讨

Java虚拟机最多支持多少个线程?

Java虚拟机最多支持多少个线程?

java 多线程 System.exit; 是退出当前线程,还是结束整个虚拟机