内存模型
在当前内存模型下,线程可以把变量保存到本地内存(比如机器的寄存器)中,这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
要解决这个问题,就需要把变量声明为volatile,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取
如何实现多线程
继承Thread类,重写run()方法
实现Runnable接口,重写run()方法
实现Callable接口,重写call方法(有返回值)
使用线程池(有返回值)
线程池
为什么使用线程池
降低资源消耗
通过重复利用已创建的线程降低线程创建和销毁造成的消耗
提高响应速度
当任务到达时,任务可以不需要的等到线程创建就能立即执行
提高线程的可管理性
线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
应用场景
需要大量的线程来完成任务,且完成任务的时间较短
对性能要求苛刻的应用
接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用
创建方式
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和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()方法加锁也可以保证线程安全
数组类型(使用原子的方式更新数组里的某个元素)
AtomicIntegerArray
整型数组原子类
AtomicReferencArray
引用类型数组原子类
引用类型
AtomicStampedReference
原子更新引用类型里的字段原子类
AtomicMarkableReference
原子更新带有标记位的引用类型
对象的属性修改类型
AtomicIntegerFieldUpdater
原子更新整型字段的更新器
AtomicLongFieldUpdater
原子更新长整型字段的更新器
AtomicStampedReference
原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用CAS进行原子更行时可能出现的ABA问题
AtomicInteger原理
主要利用CAS+volatile和native方法来保证原子操作,从而避免synchronized的高开销,执行效率大为提升
AQS(AbstractQueuedSynchronizer)
是一个用来构建锁和同步器的框架,使用AQS能简单高效地构造出应用广泛的大量的同步器,比如我们提到的ReentranrLock,Semaphore,其他诸如ReentrantReadWriteLock。SynchronousQueue,Futuretask等等皆是
原理
AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并<br>且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被<br>唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队<br>列中。
资源共享方式
Excluslve(独占)
只有一个线程能执行,如ReentrantLock
Share(共享)
多个线程可同时执行,如Semaphore,ReadWriteLock