程序、进程、线程
程序
概念:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态代码
进程
概念:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有其自身的产生、存在、消亡的过程。——生命周期
说明:进程作为资源分配单位,系统在运行时会为每个进程分配不同的内存区域
线程
概念:进程可以进一步细分为线程,是程序内部的一条执行路径
说明:线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器,线程切换的开销小
内存结构:
进程可以细化为多个线程
每个线程拥有自己独立的:栈、程序计数器
多个线程,共享同一个进程中的机构:方法区和堆
线程的生命周期
多线程
优势
- 线程安全问题:对共享数据的操作可能得到不同的结果
- 如++i操作,先去i再加1再写回,并发线程可能会同时写回造成数据错误
- if-then-do 竞争条件使得多个线程同时进入条件进行数据的重复添加
- 活跃性问题:加锁解锁可能导致线程死锁
- 不同的线程分别占用对方需要的共享资源不放弃,都在等对方释放自己需要的共享资源,就形成了死锁
- 出现死锁后,不会出现异常、提示,只是所有线程都在阻塞状态,无法继续
- 预防死锁的原则:所有的线程都按照相同的顺序获得资源的锁
-
Thread
Java中只有这么⼀种东⻄代表线程
start⽅法才能并发执⾏!
每多开⼀个线程,就多⼀个执⾏流
⽅法栈(局部变量)是线程私有的
静态变量/类变量是被所有线程共享的解决线程安全问题:
在java中通过同步机制,解决线程安全问题
同步解决了线程安全问题,操作同步代码时,其他线程等待,相当于一个单线程的过程,效率低实现线程安全的基本手段
1.不可变类
2.同步块、同步方法
同步代码块
private static final Object lock =new Object();
synchronized(一个对象){
//操作共享数据的代码
}
同步方法 ``` public synchronized void threadTest(){ //操作共享数据 } //等价于 public void threadTest(){ synchronized(this){ //操作共享数据 }
```
实例的synchronnized⽅法把该实例当成锁
Static synchronized⽅法 把Class对象当成锁
Collections.synchronized
将集合中的方法用synchronized方法包起来,使这个方法成为线程安全的,但调用不同的方法还是会出问题,就需要使用同步方法的方式
JUC包
- AtomicInteger
ConcurrentHashMap
任何使⽤HashMap有线程安全问题的地⽅ 都⽆脑地使⽤ConcurrentHashMap替换即可。
ReentrantLock 可重入锁
在不同的地方加锁解锁
ThreadLocal
Java 里一种特殊变量
是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。
它是一个线程级别变量,每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞态条件被彻底消除了,在并发模式下是绝对安全的变量。 可以通过 ThreadLocal
线程池
为什么要使用线程池?
果要执行的任务很多,每个任务都需要一个线程的话,那么频繁的创建、销毁线程会比较耗性能。有了线程池就不要创建更多的线程来完成任务,因为线程可以重用,另外,如果无限制的创建大量的线程,大量的线程会占用内存资源并且可能会导致Out of Memory。
线程池类ThreadPoolExecutor的参数
1.corePoolSize:核心线程数
- 核心线程会一直存活,即使没有任务需要执行
- 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
设置allowCoreThreadTimeout=true,核心线程会超时关闭,默认是false
2.queueCapacity:阻塞队列workQueue的大小
-
3.maxPoolSize:最大线程数
当前线程数达到corePoolSize后,如果继续有任务被提交到线程池,会将任务缓存到工作队列中。如果队列也已满,则会去创建一个新线程来出来这个处理。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
4.keepAliveTime 空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
5.unit 空闲线程存活时间单位
6.workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:
ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
- LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
- SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
- PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
应用场景
- CPU密集型 不适合使用,多线程带来的提升有限
- IO密集型
- 网络IO
- 文件IO