JUC
实现方式:
- 1.继承Thread
- 2.实现runnable接口
- 3.通过Callable和FutureTask创建线程
- 4.线程池
线程安全问题
1>.同步代码块
2>.同步方法:使用synchronized修饰的方法,就叫做同步方法。
同步锁是谁?对于非static方法,同步锁就是this。对于static方法,同步锁就是当前方法所在类的字节码对象。
锁机制
点击查看代码
java.util.concurrent.locks.Lock;
Lock lock = new ReentrantLock();
lock.lock();
lock.unlock();
volatile关键字
1. 轻量级锁,修饰变量,对volatile修饰的变量,在线程间都是可见的
2. 一个线程修改了这个变量的值,那么其他线程中都可以读取到这个修改后的值
3. synchronized除了线程间互斥以外,还有一个非常大的作用,就是保证可见性
在 Java 程序中怎么保证多线程的运行安全?
1>.使用安全类,比如java.util.concurrent下的类,比如原子类AtomicInteger CAS
2>.使用自动锁,synchronized
3>.使用手动锁Lock
4>.使用 volatile
volatile 是变量修饰符;volatile 不会造成线程的阻塞;
volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。
但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块
内存模型中的可见性、原子性和有序性
轻量级锁,修饰变量,对volatile修饰的变量,在线程间都是可见的.
一个线程修改了这个变量的值,那么其他线程中都可以读取到这个修改后的值.
synchronized除了线程间互斥以外,还有一个非常大的作用,就是保证可见性.
可重入锁
可重复可递归调用的锁,在外层用锁之后,内层依然可以使用.并且不会发生死锁
ReentrantLock和synchronized都是可重入锁
线程池
corePoolSize:核心线程数量,会一直存在,除非allowCoreThreadTimeOut设置为true
maximumPoolSize:线程池允许的最大线程池数量
keepAliveTime:线程数量超过corePoolSize,空闲线程的最大超时时间
unit:超时时间的单位
workQueue:工作队列,保存未执行的Runnable 任务
threadFactory:创建线程的工厂类
handler:当线程已满,工作队列也满了的时候,会被调用。被用来实现各种拒绝策略。
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这A样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors
FixedThreadPool
SingleThreadPool:允许的请求队列长度为 Integer.MAX VALUE,可能会堆积大量的请求,从而导致 OOM。
CachedThreadPool:允许的创建线程数量为Integer.MAX VALUE,可能会创建大量的线程,从而导致 OOM。
拒绝策略
AbortPolicy:默认的策略,直接抛出RejectedExecutionException 异常。
CallerRunsPolicy:既不会抛出异常,也不会终止任务,⽽是将任务返回给调用者。
DiscardOldestPolicy:抛弃队列中等待最久的任务。
DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。
接口幂等性
乐观锁
CAS 实现:Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。
Unsafe,是CAS的核心类,Unsafe类中的所有方法都是native修饰的,
也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应的任务,这个保证了AtomicInteger的原子性,CAS是底层思想。
版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会 +1。
当线程 A 要更新数据时,在读取数据的同时也会读取 version 值,在提交更新时,
若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。
缺点如下:
a.循环时间长且开销很大
b.只能保证一个共享变量的原子操作
c.引出来ABA问题 ABA问题的解决(AtomicStampedReference 类似于时间戳)
悲观锁
适用于写多读少的情况(多写场景) synchronized 和 Lock 就是悲观锁思想的实现
如何抉择?
- 响应效率:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。
- 乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。
- 冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率。冲突频率大,选择乐观锁会需要多次重试才能成功,代价比较大。
- 重试代价:如果重试代价大,建议采用悲观锁。悲观锁依赖数据库锁,效率低。更新失败的概率比较低。
- 乐观锁如果有人在你之前更新了,你的更新应当是被拒绝的,可以让用户从新操作。悲观锁则会等待前一个更新完成。这也是区别。
synchronized和Volatile和ThreadLocal区别是什么
synchronized
1.标记的变量可以被编译器优化
2.可能会造成线程的阻塞
3.而synchronized则可以保证变量的修改可见性和原子性
4.可以使用在变量、方法和类级别的
volatile
1.仅能实现变量的修改可见性,不能保证原子性
2.仅能使用在变量级别
3.不会造成线程的阻塞
4.标记的变量不会被编译器优化
ThreadLocal
允许我们创建只能被同一个线程读写的变量。ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量