线程的概念
多线程简介
多线程技术是一把双刃剑,在使用时需要充分考虑它的优缺点。
- 优点
- 多线程技术使程序的响应速度更快,因为用户界面可以在进行其它工作的同时一直处于活动状态
- 当前没有进行处理的任务时可以将处理器时间让给其它任务
- 占用大量处理时间的任务可以定期将处理器时间让给其它任务
- 可以随时停止任务
- 可以分别设置各个任务的优先级以优化性能
- 缺点
- 等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源 ,如打印机等。
- 对线程进行管理要求额外的CPU开销。线程的使用会给系统带来上下文切换的额外负担。当这种负担超过一定程度时,多线程的特点主要表现在其缺点上,比如用独立的线程来更新数组内每个元素。
- 线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。
- 对公有变量的同时读或写。当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,从而使前一个线程的参数被修改;另外 ,当公用变量的读写操作是非原子性时,在不同的机器上,中断时间的不确定性,会导致数据在一个线程内的操作产生错误,从而产生莫名其妙的错误,而这种错误是程序员无法预知的。
- 适合的场景
- 耗时或大量占用处理器的任务阻塞用户界面操作
- 各个任务必须等待外部资源 (如远程文件或 Internet连接)
创建新线程
-
继承Thread类
优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。 -
实现Runnable接口
优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
线程状态
Java中的线程的生命周期大体可分为5种状态。
-
NEW(新建):这种情况指的是,通过New关键字创建了Thread类(或其子类)的对象
-
RUNNABLE(就绪):这种情况指的是Thread类的对象调用了start()方法,这时的线程就等待时间片轮转到自己这,以便获得CPU;第二种情况是线程在处于RUNNABLE状态时并没有运行完自己的run方法,时间片用完之后回到RUNNABLE状态;还有种情况就是处于BLOCKED状态的线程结束了当前的BLOCKED状态之后重新回到RUNNABLE状态。
-
RUNNING(运行):这时的线程指的是获得CPU的RUNNABLE线程,RUNNING状态是所有线程都希望获得的状态。
-
DEAD(阻塞):处于RUNNING状态的线程,在执行完run方法之后,就变成了DEAD状态了。
-
BLOCKED(死亡):运行完run方法,线程结束,进入DEAD状态。
join练习
中断线程
守护线程
线程同步
线程同步
synchronized方法
死锁
wait和notify
高级concurrent包
ReentrantLock
ReadWriteLock
Condition
Concurrent集合
Atomic
ExecutorService
Future
CompletableFuture
Fork/Join
线程工具类
ThreadLocal
线程池
线程池的作用
限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
线程池的优势
- 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 可以根据系统的承受能力,调整线程池中工作线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
JDK自带的四种线程池
- newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 - newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 - newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 - newScheduledThreadPool 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
集合的线程安全问题
- Vector 线程安全,但是效率较低,现在已经不建议使用。
Stack 线程安全,是Vector的一个子类,它实现了一个标准的后进先出的栈。
ArrayList、LinkedList线程不安全。 - Hashtable 线程安全
HashMap 线程不安全 - ConcurrentHashMap 支持高并发、高吞吐量的线程安全HashMap实现。
- HashMap