zoukankan      html  css  js  c++  java
  • 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 提供的工厂类,将会忽略很多线程池的参数设置,工厂类一旦选择设置默认参数,就很容易导致无法调优参数设置,从而产生性能问题或者资源浪费。

  • 相关阅读:
    反转字符串
    数组
    复杂度分析(二)
    复杂度分析(一)
    业务应该这么写--特性
    5种方法快速启动一个应用程序
    业务应该这么写--表达式树
    业务应该这么写--泛型
    业务应该这么写--异常处理
    关于关系型数据库外键,要减轻数据库压力的一些说法
  • 原文地址:https://www.cnblogs.com/wangb0402/p/12655508.html
Copyright © 2011-2022 走看看