多线程概念
进程与线程
进程是操作系统进行资源分配的最小单位,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被用于封装可以拆分的任务对象,它的两个子类RecursiveAction和RecusiveTask分别用于有返回值的任务对象和没有返回值的任务对象。
为了提高多线程处理的效率,避免出现饥饿,Java的fork/join框架采用了一种名为“工作窃取”的方法。每个线程都有自己的双端任务队列,一般情况下线程从头部获取任务,当某个任务队列为空的时候,它会尝试从其它线程任务队列的尾部“窃取任务”来执行。
Executors 利用工厂模式实现的四种线程池,我们在使用的时候需要结合生产环境下的实际场景。不过我不太推荐使用它们,因为选择使用 Executors 提供的工厂类,将会忽略很多线程池的参数设置,工厂类一旦选择设置默认参数,就很容易导致无法调优参数设置,从而产生性能问题或者资源浪费。