java线程的6种状态:
1、初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
2、运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3、阻塞(BLOCKED):表示线程阻塞于锁。
4、等待(WAITING):join,进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5、超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
6、终止(TERMINATED):表示该线程已经执行完毕。
1、并行和并发的区别
并行:同一时刻,多条指令在多个处理器上同时执行。无论从微观还是从宏观来看,二者都是一起执行
并发:同一时刻,只能有一条指令执行,多个指令被快速的轮换执行。宏观上具有同时执行的效果,但在微观上并不是同时执行,只是把时间分成若干段,使多个指令快速交替的执行
2、线程的几种状态:
新建状态:新创建一个线程对象
就绪状态:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
运行状态:就绪状态的线程获取了CPU,执行程序代码。
阻塞状态:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒,
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期
3、future原理
【转】FutureTask实现原理_男儿当自强-CSDN博客_futuretask 原理
4、对象的组成部分 Java并发编程 Synchronized及其实现原理 - 明耀 - 博客园
对象头、实例变量、填充
对象头包括:
mark word
类型指针 指向它的元数据,虚拟机通过这个指针来确定对象是哪个类的实例
数组长度
5、AQS Java中的AQS_每天进步一点点-CSDN博客_aqs java
AQS即Abstract Queued Synchronizer(抽象队列同步器),用来实现各种锁,各种同步组件,它包含了state变量、加锁线程、等待队列等并发中的核心组件。我们常用的比如ReentrantLock,CountDownLatch等等基础类库都是基于AQS实现的。
通过构造一个基于阻塞的CLH队列容纳所有阻塞线程,而对该队列的所有操作通过CAS操作,但对已经获得锁的线程而言,Reentranlock实现了偏向锁
CLH(Craig Landin and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配
6、lock和synchronized
Reentranlock底层就是AQS,默认使用非公平锁,首先CAS抢占锁,如果失败就调用LockSupport.park将当前线程阻塞,将其加入CLH队列;当持有锁的线程调用unlock,会将CLH队列头结点的下一个节点线程唤醒,调用LockSupport.unpark,唤醒线程从阻塞处继续循环,重新抢占锁,此时有可能被新线程抢占,导致重新进入阻塞状态,这就是非公平锁。
lock存储结构(同AQS):一个int类型状态值(用于锁状态变更);双向链表
synchronized Java并发编程 Synchronized及其实现原理 - 明耀 - 博客园
利用对象头和monitor来实现
底层也是一个基于CAS操作的等待队列,把等待队列分为ContentionList和EntryList,目的是为了降低线程的出列速度
锁升级:
无锁->偏向锁->轻量级锁->重量级锁
锁降级:
当jvm进入安全点(SafePoint)的时候,会检查是否有闲置的monitor,然后试图降级
每日一面 - 什么是 Safepoint? - 简书SafePoint 每日一面 - 什么是 Safepoint? - 简书
JVM(三)----垃圾收集算法及Safe Point介绍 - 简书
Safepoint 可以理解成是在代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,主动去检查,线程是否需要暂停,是否要做某些操作,如回收内存,锁降级等
synchronized同步方法和synchronized(this)使用的是同一把锁,锁对象。作用范围是同一个实例对象的方法或者代码块,作用对象同一个实例对象
synchronized同步静态方法和synchronized(class)使用的是同一把锁,锁类。作用范围是整个静态方法或者代码块,作用对象是这个类的所有对象
多线程访问synchronized(class)和synchronized(this),不会阻塞,因为锁的不是同一个对象,不是同一代码块,一个锁实例对象的代码块,一个锁类对象的代码块
区别:详解synchronized与Lock的区别与使用_brickworkers的博客-CSDN博客_synchronized与lock的区别
底层机制都是基于队列和一个状态值
都是可重入锁,可重入锁可以一定程度避免死锁:1.同一线程支持重入 2.父子类支持重入【内置锁重入】子类重写父类synchronized方法,父类方法锁住的对象是谁?_weixin_33670713的博客-CSDN博客
synchronized实现了自旋锁,并针对不同的系统和硬件体系进行了优化,而Lock则完全依靠系统阻塞挂起等待线程;Lock比synchronized更适合在应用层扩展,可以继承AbstractQueuedSynchronizer定义各种实现,比如实现读写锁(ReadWriteLock),公平或不公平锁;同时,Lock对应的Condition也比wait/notify要方便的多、灵活的多
synchronized | Lock |
Java关键字 | 类 |
无法获取锁状态 | 可以获取tryLock() |
自动释放锁 | 手动释放锁unlock() |
等待不可中断 | 可中断等待tryLock(long time, TimeUnit unit); lockInterruptibly(); |
非公平 | 可公平、非公平 |
锁代码块生成指令monitorenter、monitorexit; 锁方法通过判断运行时常量池ACC_SYNCHRONIZED标识 | AQS |
7、wait/notify、await/signal、LockSupport.park/unpark、sleep
8、ConcurrentHashMap如何保证线程安全? ConcurrentHashMap是如何实现线程安全的_|-| [- |_ |_ ()-CSDN博客_concurrenthashmap怎么实现线程安全
【67期】谈谈ConcurrentHashMap是如何保证线程安全的? - 知乎
java8实现锁分离,锁住一个node,锁住node之前是基于volatile和cas的无锁操作且线程安全
java8之前业务是锁分离,只不过锁住的是存储多个node的segment
get操作不会加锁,利用volatile保证每个node的可见性,且它的put,扩容,remove操作都设计的比较巧妙。相比hashTable,Collections.synchronizedMap()用一个大锁锁住整个hash列表性能要好很多
初始化线程安全:
多个线程同时进行put操作,在初始化数组时使用了乐观锁CAS操作来决定到底是哪个线程有资格进行初始化,其他线程均只能等待。
用到的并发技巧:
volatile变量(sizeCtl):它是一个标记位,用来告诉其他线程这个坑位有没有人在,其线程间的可见性由volatile保证。
CAS操作:CAS操作保证了设置sizeCtl标记位的原子性,保证了只有一个线程能设置成功
put操作线程安全:
由于其减小了锁的粒度,若Hash完美不冲突的情况下,可同时支持n个线程同时put操作,n为Node数组大小,在默认大小16下,可以支持最大同时16个线程无竞争同时操作且线程安全。当hash冲突严重时,Node链表越来越长,将导致严重的锁竞争,此时会进行扩容,将Node进行再散列,下面会介绍扩容的线程安全性。总结一下用到的并发技巧:
减小锁粒度:将Node链表的头节点作为锁,若在默认大小16情况下,将有16把锁,大大减小了锁竞争(上下文切换),就像开头所说,将串行的部分最大化缩小,在理想情况下线程的put操作都为并行操作。同时直接锁住头节点,保证了线程安全
Unsafe的getObjectVolatile方法:此方法确保获取到的值为最新
扩容操作线程安全:
ConcurrentHashMap运用各类CAS操作,将扩容操作的并发性能实现最大化,在扩容过程中,就算有线程调用get查询方法,也可以安全的查询数据,若有线程进行put操作,还会协助扩容,利用sizeCtl标记位和各种volatile变量进行CAS操作达到多线程之间的通信、协助,在迁移过程中只锁一个Node节点,即保证了线程安全,又提高了并发性能。
get操作线程安全:
对于get操作,其实没有线程安全的问题,只有可见性的问题,只需要确保get的数据是线程之间可见的即可
统计容器大小线程安全:
分两步:
利用CAS递增baseCount值来感知是否存在线程竞争,若竞争不大直接CAS递增baseCount值即可,性能与直接baseCount++差别不大
若存在线程竞争,则初始化计数桶,若此时初始化计数桶的过程中也存在竞争,多个线程同时初始化计数桶,则没有抢到初始化资格的线程直接尝试CAS递增baseCount值的方式完成计数,最大化利用了线程的并行。此时使用计数桶计数,分而治之的方式来计数,此时两个计数桶最大可提供两个线程同时计数,同时使用CAS操作来感知线程竞争,若两个桶情况下CAS操作还是频繁失败(失败3次),则直接扩容计数桶,变为4个计数桶,支持最大同时4个线程并发计数,以此类推…同时使用位运算和随机数的方式"负载均衡"一样的将线程计数请求接近均匀的落在各个计数桶中
9、volatile实现原理
volatile 变量使用条件丶Java教程网-IT开发者们的技术天堂
lock指令和内存屏障:禁止指令重排序,并非线程安全
典型的使用场景是作为标记使用,使用条件:
对变量的写操作不依赖于当前值;
该变量没有和别的volatile修饰的变量一起使用,一起在同一个式子中操作
10、线程
创建线程的4中方法:集成Thread类,实现Runnable、Callable接口,线程池
线程池特点:线程复用、控制最大并发数、管理线程、降低重复创建、销毁的消耗
11、CountDownLatch和CyclicBarrier
CyclicBarrier和CountDownLatch区别_泡泡鱼的专栏-CSDN博客_cyclicbarrier和countdownlatch区别
12、threadLocal内存泄漏
对ThreadLocal的一点理解_从不吹牛逼-CSDN博客
13、如何实现一个线程安全的单例,前提是不能加锁