1.摘要
本问旨在通过例子来带领大家分析Java并发编程中线程不安全行为,并相应给出对应的解决方案。
(1) 概念:。
这里有必要额外解释下进程,。
通常来说一个应用程序占用一个进程,当然一个服务也可以开启多个进程。
。
(2) 状态:
线程五种状态:分别是新建、就绪、运行、阻塞、死亡。
1.创建线程让线程来到新建状态。
2.调用start()方法,此时线程来到就绪状态。
3.CPU时间片轮训调度执行到该线程,此时线程处于运行状态。
4.线程执行sleep/ wait /join 方法,来到阻塞状态。
5.线程内run方法执行结束,线程凋亡。
(3) 分类:
线程分为。用户线程则是用户创建普通线程,JVM启动从Main函数开始执行,这个主线程也是一个就是一个用户线程,其中JVM运行中还同时开启很多守护线程,比如低优先级的GC线程。
两者的区别:
。JVM在执行过程中,并不会因为守护线程结束而结束运行,相反,当用户线程/主线程执行结束,JVM则会直接退出,此时如果还有后台守护线程仍然在运行中,则直接一并结束运行。
(4) 线程创建几种方式:
1) 类继承Thread 类,重写run方法
2) 类实现runnable 接口,重写run方法
3) 类实现 callabled 接口,重写call方法,并结合FutureTask 获取返回值。【】
4) 使用Executors 构建线程池。前面三种都是创建单个线程的方式。
(5) 多线程并发编程的意义:
随着互联网数据和访问请求流量的日益增加,单核CPU /单线程处理任务的效率已经无法满足人们都系统响应的要求,且伴随着多核CPU时代的到来,多线程并发处理任务,能同时利用多个CPU同时并发处理海量的系统请求,极大的提升系统性能。
(1) 原生【使用final修饰的类】
(2) 使用 来存储对象,ThreadLocal 对象属于线程私有,每个对象只会存储对象的一个副本,线程在操作该对象时,只会操作当前线程内部的对象,从而做到数据隔离,线程安全。
(3) volatile/synchronized 关键字
volatile: 只能解决有序性和可见性。不能保证原子性。通常用来解决多线程情况下,每次需要获取对象最新数据。
有序性:
Java内存模型允许编译器和处理器对指令进行重排需从而获取更优越的性能。但是这个只是会对不存在依赖性的指令发生重排序。单线程下不会出现问题,多线程情况下有可能就是因为执行重排而导致执行异常。因此对于多线程情况下,某些变量写操作不依赖当前变量的值,且需要获取最新数据时,可以使用volatile 关键字进行声明,避免指令重排,且维持可见性。
可见性:线程在获取当前对象的值总是从主内存获取最新的数据,而不是,可以有效解决数据未实时同步更新的情况
缓存扩展补充:
为了解决主内存与CPU 之间运行速度之间差,或者说为了进一步提升CPU处理数据的速度,通常会在主内存和CPU之间使用多级缓存用以存储数据,CPU执行时,直接从缓存中取存数据,然后缓存在将数据同步更新至主内存中去。
L1、 L2、 L3 每一级缓存存储数据空间更大,同时缓存速度依次下降。由于CPU执行不是以变量来加载执行的,而是以Cache行为单位与主内存进行数据交换的,一般是2的幂次数字节,因此当多个变量存在一个Cache行中,多线程同时修改一个Cache行里面的多个变量时,由于,根据,相比将多个变量存在不同Cache行进行操作,性能会有所下降,这就是所谓的。
如何避免伪共享?JDK8之前都是通过的方式来避免,就是说。
JDK8提供了一个注解来解决伪共享问题。. 可作用于类和对象上,默认只能用于Java核心类,用户类路径下需要手动添加JVM参数开启。
【开启注解】
【填充宽度默认128可设置】
(4) 原子类/CAS
JDK内置AtomicLong、AtomicInteger、LongAdder、LongAccumulator 等
CAS 操作通过CPU原语实现,是一种硬件实现线程安全方式,其中可以使用Unsafe类来给对象定制化CAS操作,谨慎使用Unsafe类,这个类可以直接操作内存。【Unsafe类无法直接实例化(),但是可以借助反射技术来实例化】
(5) 锁
- synchronized关键字实现同步
JVM 内置锁,底层依赖于互斥锁MuteLock,线程获取锁,会相应进入MonitorEnter,退出则进入MoniterExit ,内置使用state维护锁状态。 - 各种显示锁如 ReentrantLock、ReentrantReadWriteLock、JDK8新增StampedLock等。底层都是基于AQS实现。
下面通过ReentrantLock来认识AQS。AQS是实现同步器的抽象组件JUC包所有锁的底层就是用的AQS。
ReentrantLock有个Sync类直接继承AQS。内部四个关键对象分别是
head 头指针,tail尾指针,exclusiveOwnerThread= threadName 当前独占锁所属线程。state 表示获取锁次数。其中state从0变成1 时候,表示当前有线程获取锁,如果此时当前线程继续获取锁,则state则会执行state=state+1 操作。【该锁是可重入独占锁,允许获取锁线程重复获取锁】
整个流程图如下:
这个是默认非公平锁实现。
下面着重分析入队列方法和 方法
第一次压入队列执行逻辑图: ,并且设置tail指针指向当前Node对象。
公平锁和非公平锁最大的区别就在tryAcquire()方法的实现。为例子进行说明
这个第一个节点Node属性waiter设置为null。
条件队列和同步队列:
AQS 内部采用,用于阻塞排队获取锁,同时也提供了基于,用以条件控制等待获取锁。下面结合图进行介绍。
其中条件队列可以有多个,当锁Contion对象调用await 方法,会将当前线程释放锁,追加至条件队列。当锁Condition对象调用singnal或者singalAll(唤醒所有线程)方法唤醒线程,将条件队列线程追加至同步队列尾部。
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/jjc/26812.html