线程的6个状态:
1.初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
2.运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3. 阻塞(BLOCKED):表示线程阻塞于锁。
4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
6. 终止(TERMINATED):表示该线程已经执行完毕。
Synchronized 和 Lock区别
Synchronized 是java内置的关键字;Lock是类
Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
Synchronized 会自动释放锁,Lock必须要手动释放锁,不释放会死锁
Synchronized 如果线程1(获得锁,阻塞)线程2(等待),lock不一定会等待下去
Synchronized 可重入锁,不可以中断,是非公平的,Lock 可重入锁,可判断锁,非公平(可以自己设置)
Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码
注意:Synchronized锁的是调用者对象或class,集群环境下synchronized会失效。
生产者和消费者问题
Synchronized版本:
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{for(int i=0;i<10;i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}},"B").start();
}
}
class Data{
//数字 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
while(number!=0){ //如果这里使用if会存在虚假唤醒
//等待操作
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
while(number==0){ //如果这里使用if会存在虚假唤醒
//等待操作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程 我-1完毕了
this.notifyAll();
}
}
JUC版本的生产者和消费者问题
juc版通过Condition还可以精准的通知和唤醒的指定的线程!
/**
* A 执行完 调用B
* B 执行完 调用C
* C 执行完 调用A
*/
public class Test {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printA();
}
},"A").start();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printB();
}
},"B").start();
new Thread(()->{
for(int i=0;i<10;i++){
data3.printC();
}
},"C").start();
}
}
class Data3{
//资源类
private Lock lock=new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; //1A 2B 3C
public void printA(){
lock.lock();
try {
//业务 判断 -> 执行 -> 通知
while(number!=1){
//等待
condition1.await();
}
//操作
System.out.println(Thread.currentThread().getName()+",AAAAA");
//唤醒指定的线程
number=2;
condition2.signal(); // 唤醒2
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
//业务 判断 -> 执行 -> 通知
while (number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+",BBBBB");
//唤醒3
number=3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
//业务 判断 -> 执行 -> 通知
while(number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+",CCCCC");
//唤醒1
number=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
常用辅助类:
这里只介绍最常用的三种,另外两种可以 点击查看
Semaphore是经典的并发工具。
CountDownLatch是一个非常简单但非常常见的实用程序,用于阻塞直到给定数量的信号、事件或条件成立。
ACyclicBarrier是可重置的多路同步点,在某些并行编程风格中很有用。
APhaser提供了一种更灵活的屏障形式,可用于控制多个线程之间的分阶段计算。
AnExchanger允许两个线程在一个集合点交换对象,并且在多个管道设计中很有用。
CountDownLatch
减法计数器: 等待计数器为0,就唤醒,再继续向下运行
public static void main(String[] args) throws InterruptedException {
//总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" Go out");
countDownLatch.countDown(); //每个线程都数量-1
},String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零 然后向下执行
System.out.println("close door");
}
CyclickBarrier
加法计数器
public static void main(String[] args) {
//主线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙~");
});
for (int i = 1; i <= 7; i++) {
//子线程
int finalI = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 收集了第 {"+ finalI+"} 颗龙珠");
try {
cyclicBarrier.await(); //加法计数 等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
Semaphore
作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!
semaphore.acquire()得到资源,如果资源已经使用完了,就等待资源释放后再进行使用!
semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!
代码案例(抢车位:3个车位 6辆车):
public static void main(String[] args) {
//停车位为3个
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
int finalI = i;
new Thread(() -> {
try {
semaphore.acquire(); //得到
//抢到车位
System.out.println(Thread.currentThread().getName() + " 抢到了车位{" + finalI + "}");
TimeUnit.SECONDS.sleep(2); //停车2s
System.out.println(Thread.currentThread().getName() + " 离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();//释放
}
}, String.valueOf(i)).start();
}
}
读写锁:
官方文档 点击查看
先对于不加锁的情况:
如果我们做一个我们自己的cache缓存。分别有写入操作、读取操作;
我们采用五个线程去写入,使用十个线程去读取。
我们来看一下这个的效果,如果我们不加锁的情况!
public class Test {
public static void main(String[] args) {
MyCacheeadWriteLock mycache = new MyCacheeadWriteLock();
//开启5个线程 写入数据
for (int i = 1; i <=5 ; i++) {
int finalI = i;
new Thread(()->{
mycache.put(String.valueOf(finalI),String.valueOf(finalI));
}).start();
}
//开启10个线程去读取数据
for (int i = 1; i <=10 ; i++) {
int finalI = i;
new Thread(()->{
String o = mycache.get(String.valueOf(finalI));
}).start();
}
}
}
class MyCacheeadWriteLock{
private volatile Map<String,String> map=new HashMap<>();
//使用读写锁
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//普通锁
private Lock lock=new ReentrantLock();
public void put(String key,String value){
//写入
System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
}
public String get(String key){
//得到
System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
String o = map.get(key);
System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
return o;
}
}
运行结果:
使用读写锁运行结果:
public class Test {
public static void main(String[] args) {
MyCacheeadWriteLock mycache = new MyCacheeadWriteLock();
//开启5个线程 写入数据
for (int i = 1; i <=5 ; i++) {
int finalI = i;
new Thread(()->{
mycache.put(String.valueOf(finalI),String.valueOf(finalI));
}).start();
}
//开启10个线程去读取数据
for (int i = 1; i <=10 ; i++) {
int finalI = i;
new Thread(()->{
String o = mycache.get(String.valueOf(finalI));
}).start();
}
}
}
class MyCacheeadWriteLock{
private volatile Map<String,String> map=new HashMap<>();
//使用读写锁
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//普通锁
private Lock lock=new ReentrantLock();
public void put(String key,String value){
//加锁
readWriteLock.writeLock().lock();
try {
//写入
//业务流程
System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock(); //解锁
}
}
public String get(String key){
//加锁
String o="";
readWriteLock.readLock().lock();
try {
//得到
System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
o = map.get(key);
System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
return o;
}
}
运行结果如下:
线程池
线程池:三大方法、7大参数、4种拒绝策略
线程池的好处
1.降低资源消耗
通过重复利用已创建的线程 从而降低线程的创建和销毁造成的消耗
2.提高响应速度
当任务到达时,任务可以不需要等到线程创建就能立即执行
3.方便管理
线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以统一的分配,调优和监控
线程池创建
Executors类中提供(三大方法)
阿里巴巴开发手册明确规定:不推荐使用原因Executors类创建线程
newSingleThreadExecutor(),newFixedThreadPool(),newCachedThreadPool()可用来创建线程池。而这三个方法皆调用了ThreadPoolExecutor类中的构造方法
ThreadPoolExecutor类
七大参数
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
int maximumPoolSize,//线程池的最大线程数
long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue, //等待提交到线程池的任务队列
ThreadFactory threadFactory, //创建线程的方式,一般默认
RejectedExecutionHandler handler //拒绝策略) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
拒绝策略
AbortPolicy: 默认的拒绝策略,如果maximumPoolSize+workQuery.size()已经饱和,就丢掉超额的部分,并抛出RejectedExcutionException异常
DiscardPolicy: 如果饱和就丢掉超额的部分,但不抛出异常
DIscardOldestPolicy: 如果队列已经饱和就删除最早进入队列的任务,将新任务追加到队尾
CallerRunPolicy: 将任务分给调用线程来执行
代码示例:
public static void main(String[] args) throws InterruptedException, IOException {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, //线程池的核心线程数量
3,//线程池的最大线程数
5, //当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit.SECONDS, //时间单位
new LinkedBlockingDeque<>(3), //等待提交到线程池的任务队列
Executors.defaultThreadFactory(), //创建线程的方式,一般默认
new ThreadPoolExecutor.AbortPolicy());//拒绝策略
try {
for (int i = 1; i <= 10; i++) {
poolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程用完 程序结束 关闭线程池
poolExecutor.shutdown();
}
}
核心线程数为2个,队列长度为3个,当来了第6个人的时候,则判断是否达到最大线程数。如果到达最大线程数则按照拒绝策略执行
最大线程如何定义?
1.cpu密集型: 电脑的核数是几核就选择几(maximumPollSize = Runtime.getRuntime().availableProcessors(););
2.IO密集型 maximumPollSize > 判断程序中十分占IO的线程
Volatile
1.保证可见性
// 如果不加volatile 程序会死循环
// 加了volatile是可以保证可见性的
private volatile static Integer number = 0;
public static void main(String[] args) {
//main线程
//子线程1
new Thread(()->{
while (number==0){
System.out.println("num=====>"+number);
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//子线程2
new Thread(()->{
while (number==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
number=1;
System.out.println(number);
}
2.不保证原子性
原子性:不可分割;
线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。
原子性操作类:java.util.concurrent.atomic
3.避免指令重排
什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的
源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
int x=1; //1
int y=2; //2
x=x+5; //3
y=x*x; //4
//我们期望的执行顺序是 1->2->3->4 可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的
玩转单例模式
饿汉式
/**
* 饿汉式单例
*/
public class Hungry {
/**
* 可能会浪费空间
*/
private byte[] data1=new byte[1024*1024];
private byte[] data2=new byte[1024*1024];
private byte[] data3=new byte[1024*1024];
private byte[] data4=new byte[1024*1024];
private Hungry(){
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
DCL懒汉式
//懒汉式单例模式
public class LazyMan {
private static boolean key = false;
private LazyMan(){
synchronized (LazyMan.class){
if (key==false){
key=true;
}
else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
System.out.println(Thread.currentThread().getName()+" ok");
}
private volatile static LazyMan lazyMan;
//双重检测锁模式 简称DCL懒汉式
public static LazyMan getInstance(){
//需要加锁
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 就有可能出现指令重排问题
* 比如执行的顺序是1 3 2 等
* 我们就可以添加volatile保证指令重排问题
*/
}
}
}
return lazyMan;
}
//单线程下 是ok的
//但是如果是并发的
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//Java中有反射
// LazyMan instance = LazyMan.getInstance();
Field key = LazyMan.class.getDeclaredField("key");
key.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //无视了私有的构造器
LazyMan lazyMan1 = declaredConstructor.newInstance();
key.set(lazyMan1,false);
LazyMan instance = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(lazyMan1);
System.out.println(instance == lazyMan1);
}
}
因为反射的存在,所以单例模式不安全。可以使用枚举,我们就可以防止反射破坏了
//enum 是什么? enum本身就是一个Class 类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
//java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>()
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
CAS
什么是cas?
//CAS : compareAndSet 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//boolean compareAndSet(int expect, int update)
//期望值、更新值
//如果实际值 和 我的期望值相同,那么就更新
//如果实际值 和 我的期望值不同,那么就不更新
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
//因为期望值是2020 实际值却变成了2021 所以会修改失败
//CAS 是CPU的并发原语
atomicInteger.getAndIncrement(); //++操作
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
概括:
CAS(Compare And Swap)比较并替换,是线程并发运行时用到的一种技术,比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
缺点:
循环会耗时;
一次性只能保证一个共享变量的原子性;
它会存在ABA问题
ABA问题?
描述:
线程1:期望值是1,要变成2;
线程2:两个操作:
1、期望值是1,变成3
2、期望是3,变成1
所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;
怎么解决ABA问题?
juc版乐观锁
public static void main(String[] args) {
//integer使用了对象缓存机制,超出了-128~127重新创建对象
//AtomicStampedReferencer如果泛型是一个包装类,注意对象引用问题
// AtomicStampedReference<Object> objectAtomicStampedReference = new AtomicStampedReference<>(2020,2021);
//每次被动过版本号加一
//正常业务操作这里比较的是一个个对象
AtomicStampedReference<Integer> objectAtomicStampedReference = new AtomicStampedReference<>(1,1);
//乐观锁的原理相同
new Thread(()->{
int stamp = objectAtomicStampedReference.getStamp();//获得版本号
System.out.println("a1->"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
objectAtomicStampedReference.compareAndSet(1,2,objectAtomicStampedReference.getStamp(), objectAtomicStampedReference.getStamp()+1);
System.out.println("a2->"+objectAtomicStampedReference.getStamp());
objectAtomicStampedReference.compareAndSet(2,1,objectAtomicStampedReference.getStamp(), objectAtomicStampedReference.getStamp()+1);
System.out.println("a3->"+objectAtomicStampedReference.getStamp());
},"a").start();
new Thread(()->{
int stamp = objectAtomicStampedReference.getStamp();//获得版本号
System.out.println("b1->"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
objectAtomicStampedReference.compareAndSet(2,6,stamp,stamp+1);
System.out.println("b2->"+objectAtomicStampedReference.getStamp());
},"b").start();
}
种锁的理解
公平锁,非公平锁
公平锁:非常公平,不能够插队,必须先来后到
非公平锁:非常不公平,可以插队,可以改变顺序
死锁
死锁测试,怎么排除死锁:
1.使用jps定位进程号
命令:jps -l
2.使用jstack 进程进程号 找到死锁信息
3.杀进程
linux: kill -9 进程id
windows: taskkill /pid 进程id /F