Java多线程

May 17, 2017


线程的概念

多线程简介

多线程技术是一把双刃剑,在使用时需要充分考虑它的优缺点。

  1. 优点
    • 多线程技术使程序的响应速度更快,因为用户界面可以在进行其它工作的同时一直处于活动状态
    • 当前没有进行处理的任务时可以将处理器时间让给其它任务
    • 占用大量处理时间的任务可以定期将处理器时间让给其它任务
    • 可以随时停止任务
    • 可以分别设置各个任务的优先级以优化性能
  2. 缺点
    • 等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源 ,如打印机等。
    • 对线程进行管理要求额外的CPU开销。线程的使用会给系统带来上下文切换的额外负担。当这种负担超过一定程度时,多线程的特点主要表现在其缺点上,比如用独立的线程来更新数组内每个元素。
    • 线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。
    • 对公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,从而使前一个线程的参数被修改;另外 ,当公用变量的读写操作是非原子性时,在不同的机器上,中断时间的不确定性,会导致数据在一个线程内的操作产生错误,从而产生莫名其妙的错误,而这种错误是程序员无法预知的。
  3. 适合的场景
    • 耗时或大量占用处理器的任务阻塞用户界面操作
    • 各个任务必须等待外部资源 (如远程文件或 Internet连接)

创建新线程

  1. 继承Thread类
    优势是:
    编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
    劣势是:
    线程类已经继承了Thread类,所以不能再继承其他父类。

  2. 实现Runnable接口
    优势是:
    线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
    在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
    劣势是:
    编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。

线程状态

Java中的线程的生命周期大体可分为5种状态。

  1. NEW(新建):这种情况指的是,通过New关键字创建了Thread类(或其子类)的对象

  2. RUNNABLE(就绪):这种情况指的是Thread类的对象调用了start()方法,这时的线程就等待时间片轮转到自己这,以便获得CPU;第二种情况是线程在处于RUNNABLE状态时并没有运行完自己的run方法,时间片用完之后回到RUNNABLE状态;还有种情况就是处于BLOCKED状态的线程结束了当前的BLOCKED状态之后重新回到RUNNABLE状态。

  3. RUNNING(运行):这时的线程指的是获得CPU的RUNNABLE线程,RUNNING状态是所有线程都希望获得的状态。

  4. DEAD(阻塞):处于RUNNING状态的线程,在执行完run方法之后,就变成了DEAD状态了。

  5. BLOCKED(死亡):运行完run方法,线程结束,进入DEAD状态。

image

join练习

中断线程

守护线程

线程同步

线程同步

synchronized方法

死锁

wait和notify

高级concurrent包

ReentrantLock

ReadWriteLock

Condition

Concurrent集合

Atomic

ExecutorService

Future

CompletableFuture

Fork/Join

线程工具类

ThreadLocal

线程池

线程池的作用

限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

线程池的优势

  1. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

JDK自带的四种线程池

  1. newSingleThreadExecutor
    创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
  2. newFixedThreadPool
    创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  3. newCachedThreadPool
    创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
  4. newScheduledThreadPool 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

集合的线程安全问题

  1. Vector 线程安全,但是效率较低,现在已经不建议使用。
    Stack 线程安全,是Vector的一个子类,它实现了一个标准的后进先出的栈。
    ArrayList、LinkedList线程不安全。
  2. Hashtable 线程安全
    HashMap 线程不安全
  3. ConcurrentHashMap 支持高并发、高吞吐量的线程安全HashMap实现。
  4. HashMap