Java多线程、并发常见知识点

Java线程的状态

1.新建(New)
2.运行(Runnable)
3.阻塞(Blocked)
4.等待(Waiting)
5.超时等待(Timed_Waiting)
6.终止(Terminated)

Java线程中将 就绪(ready)和运行中(running)合并为 “运行(Runnable)”

创建线程的方式:

1.继承Thread类
2.实现Runnable接口
3.实现Callable接口(支持泛型返回值,执行后获取Future,再调用get方法获取返回值)
4.基于线程池的方式(用Executors创建)

 为什么要使用多线程呢?
 线程可以比作是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。
 多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能,提高 CPU 和 IO 设备的综合利用率。

 线程和进程有什么区别?
 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
 开销:线程开销比进程小,是个包含关系,进程中至少要有1个及以上线程。
 内存方面:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

常用方法

start() : 开始执行线程,拿到时间片后自动调用run方法
run(): 普通的执行方法
sleep(): 静态方法,使线程睡眠,可指定时间,不释放锁
yield(): 线程让步,让出当前执行权进入就绪态,有可能下一轮还会抢到时间片继续运行
join(): 使当前线程等待另一个线程执行完毕后再执行,不释放锁,有点插队的意思
wait(): Object的方法,进入等待队列。依靠唤醒或者timeout时间到自动唤醒,会释放锁
currentThread(): 返回当前线程

sleep与wait区别? 
1.sleep方法属于Thread,wait方法属于Object
2.调用sleep方法不会释放锁;wait会释放锁,使得其他线程可以使用同步控制块或者方法
3.wait只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
4.sleep必须捕获异常,而wait不需要捕获异常

Java锁

乐观锁:
是一种乐观思想,每次去拿数据的时候都认为别人不会修改,所以不会上锁。但在更新的时候回判断一下在此期间是否有别人修改了这个数据,如果一样则更新,不一样则重复执行 读-比较-写的操作;java中的乐观锁基本都是通过CAS(Compare and swap)操作实现的,有个缺点就是会导致ABA问题,解决方式是加版本号。乐观锁适合读多写少的场景。

悲观锁:
是一种悲观思想,每次拿数据都认为别人会修改,认为别人会它竞争,所以每次在读写的时候都会上锁,这样别人想读就会被阻塞,java中的悲观锁典型的就是synchronized,但AQS(Abstract Queue Synchronized)的锁则是先尝试CAS乐观锁去获取锁,如获取不到才会转化为悲观锁,如RetreenLock;

自旋锁:重复执行操作,可减少线程的阻塞,适用于锁的竞争不激烈且锁占用时间非常短的代码块,可提升性能,如果长时间获取不到锁,等于一直在做无用功,例如CAS的失败重复尝试
Synchronized 同步锁,非公平锁,保证原子性、可见性、有序性=
作用于普通方法时,锁住的是对象的实例(this);
作用于静态方法锁住的是类;
作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。

Jdk1.6后的锁升级
分为:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
随着锁的竞争程度,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁,是中锁优化策略;

在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
ReentrantLock和synchronized区别?
1.使用synchronized,线程执行完同步代码块会自动释放锁,而ReentrantLock需要手动释放锁。
2.synchronized是非公平锁,ReentrantLock可以设置为公平锁,构造函数传入ture。
3.ReentrantLock 是 API 代码级别的,synchronized 是 JVM 级别的
4.ReentrantLock上等待获取锁的线程是可中断的,线程可以放弃等待锁。而synchonized会无限期等待下去。
5.ReentrantLock 可以设置超时获取锁。在指定的截止时间之前获取锁,如果截止时间到了还没有获取到锁,则返回。
6.ReentrantLock 的 tryLock() 方法可以尝试非阻塞的获取锁,调用该方法后立刻返回,如果能够获取则返回true,否则返回false
7.ReentrantLock的功能更多
volatile和synchronized的区别是什么?
1.volatile 只能使用在单个变量上;而synchronized可以在类,变量,方法和代码块上。
2.volatile 保证可见性、禁止指令重排;synchronized保证原子性、可见性、有序性。
3.volatile 禁用指令重排序;synchronized不会。
4.volatile 不会造成阻塞;synchronized会。
5.volatile 不保证原子性,所以线程不安全;

为啥synchronized无法禁止指令重排,但可以保证有序性?
加了锁之后,只能有一个线程获得到了锁,获得不到锁的线程就要阻塞。所以同一时间只有一个线程执行,相当于单线程,而单线程的指令重排是没有问题的
什么是CAS?
CAS全称 Compare And Swap,比较与交换,是乐观锁的主要实现方式。CAS 在不使用锁的情况下实现多线程之间的变量同步。ReentrantLock 内部的 AQS 和原子类内部都使用了 CAS。

CAS算法涉及到三个操作数:
需要读写的内存值 V。
进行比较的值 A。
要写入的新值 B。
只有当 V 的值等于 A 时,才会使用原子方式用新值B来更新V的值,否则会继续重试直到成功更新值。

线程死锁

什么是死锁?
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

产生死锁的四个必要条件:互斥、占有且等待、不可剥夺、循环等待
避免死锁的方法,破坏其中一个条件即可(但互斥是不能破坏的):
一次性申请所有资源,破坏“占有且等待”
占有部分资源的线程进一步申请其它资源时,如果申请不到,主动释放它的占有的资源,破坏“不可剥夺”
按顺序申请资源,破坏“循环等待”条件

ThreadLocal

线程本地变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,内部是ThreadLocalMap,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程。
 适用场景:
 线程间数据隔离(每个线程在自己线程里使用自己的局部变量,各线程间的ThreadLocal对象互不影响);
 每个线程需要有自己单独的实例,且需要在多个方法共享实例,即同时满足实例在线程间的隔离与方法间的共享。比如Java web应用中,每个线程有自己单独的 Session 实例,就可以使用ThreadLocal来实现,例如JDBC的数据库连接池;

 ThreadLocal内存泄漏的原因?
每个Thread都有⼀个ThreadLocalMap的内部属性,map的key是ThreaLocal,定义为弱引用,value是强引用类型。GC的时候会⾃动回收key,而value不会,数据量大后,value就有可能越来越多且无法释放,最终导致内存泄漏。
解决⽅法:每次使⽤完ThreadLocal就调⽤它的remove()⽅法,手动将对应的键值对删除,从⽽避免内存泄漏。

线程池

线程池参数
corePoolSize 核心线程数
maximumPoolSize 最大线程数
workQueue 存储等待运行的任务,缓冲队列
keepAliveTime 非核心线程空闲时的存活时间
TimeUnit 时间单位
ThreadFactory 线程的创建工厂
RejectedExecutionHandler 拒绝策略,当队列和线程池都满
    AbortPolicy:默认的策略,直接抛出异常
    DiscardPolicy: 不处理,直接丢弃
    DiscardOldestPolicy: 将等待队列的首任务丢弃并执行当前任务
    CallerRunsPolicy: 由调用线程处理该任务
使用Executors创建线程的弊端:任务很多时可能会导致OOM
4种线程池
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程。适用场景:快速处理大量耗时较短的任务

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

newScheduledThreadPool 创建一个周期线程池,支持定时及周期性任务执行,定时任务等

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
AQS组件

Semaphore(信号量)-允许多个线程同时访问: synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。

CountDownLatch (倒计时器): CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。

CyclicBarrier(循环栅栏): CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。

“Java多线程、并发常见知识点”的3,258个回复