并发编程三要素
原子性
原子,即一个不可再分割的颗粒。在Java中原子性指的是一个或多个操作要么全部执行成功,要么全部执行失败。
有序性
程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)
可见性
当多个线程访问同一个变量时,如果其中一个线程对其作了修改,其他线程能立即获取到最新的值。
线程的五大状态
创建状态
就绪状态
处于就绪状态的线程调用start并不一定马上就会执行 run 方法,还需要等待CPU的调度。
运行状态
阻塞状态
死亡状态
悲观锁与乐观锁
悲观锁
每次操作都会加锁,会造成线程阻塞。
乐观锁
每次操作不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止,不会造成线程阻塞。
线程之间的协作
wait/notify/notifyAll
这一组是 Object 类的方法,需要注意的是:这三个方法都必须在同步的范围内调用。
sleep/yield/join
这一组是 Thread 类的方法。sleep 让当前线程暂停指定时间,只是让出CPU的使用权,并不释放锁。
yield 暂停当前线程的执行,也就是当前CPU的使用权,让其他线程有机会执行,不能指定时间。会让当前线程从运行状态转变为就绪状态,此方法在生产环境中很少会使用到,官方在其注释中也有相关的说明。
join 等待调用 join 方法的线程执行结束,才执行后面的代码,使用场景:当父线程需要等待子线程执行结束才执行后面内容或者需要某个子线程的执行结果会用到 join 方法,其调用一定要在 start 方法之后(看源码可知)
valitate 关键字
如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。 valitate是轻量级的synchronized,不会引起线程上下文的切换和调度,执行开销更小。
它的作用是:
内存可见性
多线程操作的时候,一个线程修改了一个变量的值 ,其他线程能立即看到修改后的值。
防止重排序
即程序的执行顺序按照代码的顺序执行(处理器为了提高代码的执行效率可能会对代码进行重排序)
- 并不能保证操作的原子性
synchronized 关键字
会让没有得到锁的资源进入Block状态,争夺到资源之后又转为Running状态,这个过程涉及到操作系统用户模式和内核模式的切换,代价比较高。
CAS (Compare And Swap,即比较替换,是实现并发应用到的一种技术)
AtomicBoolean,AtomicInteger,AtomicLong以及 Lock 相关类等底层就是用 CAS实现的,在一定程度上性能比 synchronized 更高。
操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。
为什么会有CAS ?
如果只是用 synchronized 来保证同步会存在以下问题:
synchronized 是一种悲观锁,在使用上会造成一定的性能问题。在多线程竞争下,加锁、释放锁会导致比较多的线程上下文切换和调度延时,引起性能问题。一个线程持有锁会导致其它所有需要此锁的线程挂起。
实现原理
Java不能直接的访问操作系统底层,是通过native方法(JNI)来访问。CAS底层通过Unsafe类实现原子性操作。
CAS只能保证一个共享变量的原子操作
Future
在并发编程我们一般使用Runable去执行异步任务,然而这样做我们是不能拿到异步任务的返回值的,但是使用Future 就可以。使用Future很简单,只需把Runable换成FutureTask即可。使用上比较简单,这里不多做介绍。
线程池
** 如果我们使用线程的时候就去创建一个线程,虽然简单,但是存在很大的问题。如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。线程池通过复用可以大大减少线程频繁创建与销毁带来的性能上的损耗。
Java中线程池的实现类 ThreadPoolExecutor,其构造函数的每一个参数的含义在注释上已经写得很清楚了,这里几个关键参数可以再简单说一下
corePoolSize :核心线程数即一直保留在线程池中的线程数量,即使处于闲置状态也不会被销毁。要设置 allowCoreThreadTimeOut 为 true,才会被销毁。
maximumPoolSize:线程池中允许存在的最大线程数
keepAliveTime :非核心线程允许的最大闲置时间,超过这个时间就会本地销毁。
workQueue:用来存放任务的队列。
SynchronousQueue:这个队列会让新添加的任务立即得到执行,如果线程池中所有的线程都在执行,那么就会去创建一个新的线程去执行这个任务。当使用这个队列的时候,maximumPoolSizes一般都会设置一个最大值。 Integer.MAX_VALUE
LinkedBlockingQueue:这个队列是一个无界队列。怎么理解呢,就是有多少任务来我们就会执行多少任务,如果线程池中的线程小于corePoolSize ,我们就会创建一个新的线程去执行这个任务,如果线程池中的线程数等于corePoolSize,就会将任务放入队列中等待,由于队列大小没有限制所以也被称为无界队列。当使用这个队列的时候 maximumPoolSizes 不生效(线程池中线程的数量不会超过corePoolSize),所以一般都会设置为0。
ArrayBlockingQueue:这个队列是一个有界队列。可以设置队列的最大容量。当线程池中线程数大于或者等于 maximumPoolSizes 的时候,就会把任务放到这个队列中,当当前队列中的任务大于队列的最大容量就会丢弃掉该任务交由 RejectedExecutionHandler 处理。**