多线程
2020-07-29 22:18:58 0 举报
AI智能生成
多线程和锁
作者其他创作
大纲/内容
线程
线程安全
包含原子性和可见性两个方面
volatile,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的
内存模型
在当前内存模型下,线程可以把变量保存到本地内存(比如机器的寄存器)中,这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
要解决这个问题,就需要把变量声明为volatile,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取
为什么使用多线程
充分利用计算机处理性能,提高程序运行的效率
任务处理异步化,快速返回客户端程序响应
如何实现多线程
继承Thread类,重写run()方法
实现Runnable接口,重写run()方法
实现Callable接口,重写call方法(有返回值)
使用线程池(有返回值)
synchronized关键字
使用方式
修饰实例方法
作用与当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法
作用于当前对象加锁,进入同步代码前要获得当前类对象的锁
修饰代码块
指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁
单例模式
懒汉式
饿汉式
双重校验锁
枚举
线程池
为什么使用线程池
降低资源消耗
通过重复利用已创建的线程降低线程创建和销毁造成的消耗
提高响应速度
当任务到达时,任务可以不需要的等到线程创建就能立即执行
提高线程的可管理性
线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
应用场景
需要大量的线程来完成任务,且完成任务的时间较短
对性能要求苛刻的应用
接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用
创建方式
Thread PoolExecutor构造方法(需要设置具体的参数)
通过Executors工具类来实现(不推荐)
Fixed Thread Pool
返回一个固定线程数量的现称此
可能堆积大量请求,从而导致OOM
SingleThreadExecutor
返回一个只有一个线程的线程池
可能堆积大量请求,从而导致OOM
CachedThreadPool
返回一个可根据实际情况调整数量的线程池
可能会创建大量线程,从而导致OOM
ScheduleThreadPool
可能会创建大量线程,从而导致OOM
锁
乐观锁与悲观锁
乐观锁
乐观锁对应于生活中乐观的人总是想着往好的方向发展
实现
版本号机制或时间戳
在数据表中加version字段,表示数据被修改的次数,当数据被修改时,version值会加以。当线程A需要更新数据时,在读取数据的同时也会读取verision值,在提交更新时,若刚才读到的version值为当前数据库中的version值相等时才哥更新,否则重试更新操作,直到更新成功
CAS(compareAndSet)
在不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步。
缺点
ABA问题
问题:如果一个变量V初次读取的时候时A,并且在准备赋值的时候检查它仍为A,并不能说明它的值没被其他线程修改过,因为在这段时间它的值可能被修改为其他值,然后又改回A。
解决:JDK1.5之后,AtomicStampedReference类提供了compareAndSet方法就是检查当前引用是否等于引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置给新的更新值
循环时间长开销大
问题:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
解决:如果JVM能支持处理器提供的pause指令那么效率会有一定提升
只能保证一个共享变量的原子操作
问题:CAS只对单个共享变量有效
解决:JDK1.5之后,把多个变量放在一个对象里来进行CAS操作,所以我们就可以使用锁货利用AtomicReference类把多个共享变量合成一个共享变量来操作
CAS与synchronized
CAS用于写比较少的情况下,synchronized用于写比较多的情况下
悲观锁
悲观锁对应于生活中悲观的人总是想着事情往最坏的方向发展
每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
实现
synchronized
ReentrantLock
独享锁与共享锁
独享锁
该锁一次只能被一个线程所持有
实现:互斥锁
ReentrantLock
共享锁
该锁可被多个线程所持有
实现:读写锁
ReadWriteLock
可重入锁与非可重入锁
可重入锁(递归锁)
指同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提是锁对象得是同一个对象),不会因为之前已经获取过锁还没有释放而阻塞
ReentrantLock和synchronized都是可重入锁
性能区别
在synchronized优化之前,性能比ReentrantLock差很多,但自从synchronized引入了偏向锁、轻量级锁(自旋锁)后,两者性能差不多
功能区别
ReentrantLock是需要手动声明与释放锁,所以为了避免忘记手工释放锁造成死锁,最好在finnally中声明释放锁
ReentrantLock可以指定是公平锁还是非公平锁,synchronized只能是非公平锁
ReentrantLock可以分组唤醒需要唤醒的线程
提供中断等待锁的线程的机制
来源区别
synchronized依赖于JVM
ReentrantLock依赖于API
需要lock()和unlock()方法配合try/finally语句块来完成
可重入锁的特点就是可以避免死锁
子主题
公平锁与非公平锁
公平锁
多个线程按照申请锁的是顺序来获取锁
非公平锁
顺序不一致,可能造成优先级反转或者饥饿现象
偏向锁/轻量级锁/重量级锁
这三种锁是指锁的状态,针对优化synchronized引入
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取所。降低获取锁的代价
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低
自旋锁
在Java中,自旋锁是指舱室获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是会消耗CPU
区别
synchronized与lock区别
lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现的
通过lock可以知道有没有成功获取锁,而synchronized却无法办到
synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用lock时需要在finally块中释放锁;
lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
lock可以提高多个线程进行操作的效率(读写锁)
在性能上,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时,此时lock的性能要远远优于synchronized。
synchronized与volatile区别
volatile关键字解决的是变量和多个线程之间的可见性,而synchronized关键字解决的是访问共享资源的同步性
volatile只能用于修饰变量,而synchronized可以修饰方法,以及代码块。(volatile是线程同步的轻量级实现,所以volatile性能要比synchronized要好,随着jdk新版本的发布,synchronized关键字在执行上得到很大的提升,在开发中使用synchronized关键字的比率还是比较大)
多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
volatile能保证变量在多个线程之间的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据同步
Runnable接口与Callable接口的区别
Runnable接口不会返回结果但是Callable接口可以返回结果
执行execute()方法和submit()方法的区别
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否
submit()方法用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成
Atomic
指一个操作是不可中断的
JUC包中的原子类
基本类型(使用原子的方式更新基本类型)
AtomicInteger
整型原子类
使用AtomicInteger之后,不用对increment()方法加锁也可以保证线程安全
AtomicLong
长整型原子类
AtomicBoolean
布尔型原子类
数组类型(使用原子的方式更新数组里的某个元素)
AtomicIntegerArray
整型数组原子类
AtomicLongArray
长整型数组原子类
AtomicReferencArray
引用类型数组原子类
引用类型
AtomicReference
引用类型原子类
AtomicStampedReference
原子更新引用类型里的字段原子类
AtomicMarkableReference
原子更新带有标记位的引用类型
对象的属性修改类型
AtomicIntegerFieldUpdater
原子更新整型字段的更新器
AtomicLongFieldUpdater
原子更新长整型字段的更新器
AtomicStampedReference
原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用CAS进行原子更行时可能出现的ABA问题
AtomicInteger原理
主要利用CAS+volatile和native方法来保证原子操作,从而避免synchronized的高开销,执行效率大为提升
AQS(AbstractQueuedSynchronizer)
是一个用来构建锁和同步器的框架,使用AQS能简单高效地构造出应用广泛的大量的同步器,比如我们提到的ReentranrLock,Semaphore,其他诸如ReentrantReadWriteLock。SynchronousQueue,Futuretask等等皆是
原理
AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并
且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被
唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队
列中。
且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被
唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队
列中。
资源共享方式
Excluslve(独占)
只有一个线程能执行,如ReentrantLock
Share(共享)
多个线程可同时执行,如Semaphore,ReadWriteLock
0 条评论
下一页