Skip to main content

Java_线程池

线程的基本概念

Java中的线程生命周期

1.新建

用new语句创建的线程对象处于新建状态,此时它和其他java对象一样,仅被分配了内存。

2.等待

当线程在new之后,并且在调用start方法前,线程处于等待状态。

3.就绪

当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态。处于这个状态的线程位于Java虚拟机的可运行池中,等待cpu的使用权。

4.运行状态

处于这个状态的线程占用CPU,执行程序代码。在并发运行环境中,如果计算机只有一个CPU,那么任何时刻只会有一个线程处于这个状态。

只有处于就绪状态的线程才有机会转到运行状态。

5.阻塞状态

阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU,直到线程重新进入就绪状态,它才会有机会获得运行状态。

阻塞状态分为三种:

1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。

2、同步阻塞:运行的线程在获取对象同步锁时,若该同步锁被别的线程占用,则JVM会把线程放入锁池中。

3、其他阻塞:运行的线程执行Sleep()方法,或者发出I/O请求时,JVM会把线程设为阻塞状态。当Sleep()状态超时、或者I/O处理完毕时,线程重新转入就绪状态。

6.死亡状态

当线程执行完run()方法中的代码,或者遇到了未捕获的异常,就会退出run()方法,此时就进入死亡状态,该线程结束生命周期。

1664003521933

为什么用线程池

1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率

例如:

记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3

如果T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!

正好,线程池缓存线程,可用已有的闲置线程来执行新任务,避免了T1+T3带来的系统开销

2.线程并发数量过多,抢占系统资源从而导致阻塞

我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况

运用线程池能有效的控制线程最大并发数,避免以上的问题

3.对线程进行一些简单的管理

比如:延时执行、定时循环执行的策略等

运用线程池都能进行很好的实现

线程池的工作原理

1664004265162

多线程并发之乐观锁和悲观锁

悲观锁 ,为了防止自己拿到数据后别人会来修改,你就把数据加上锁,直到自己处理完,才会把数据释放给别人。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

悲观锁的实现方式是加锁,加锁既可以是对代码块加锁(如Java的synchronized关键字),也可以是对数据加锁(如MySQL中的排它锁)。

乐观锁 ,在拿到数据后,不会立即进行数据锁定,只有等到数据需要更新时,才会判断数据是否和那到时一致。

乐观锁的实现机制 1、版本号机制,数据有相应的版本来进行确保一致性。

例子:比如A和B同时从数据库获得了数据Money100,这个数据100对应了一个数据版本1,此时A要扣50(100-50),B要扣60(100-60)。B最先提交给了数据库,在提交的时候确认了当前数据库的版本号依旧为1,于是就写入了数据(100-60)剩下40,同时更新了数据版本2。此时A操作完了,也准备写入数据库,本来(100-50)是可以操作成功的,但是A在写入时发现,数据版本变成了2,于是从新从数据库拿到了剩余的数据40,然后再扣款就失败了。

2、CAS算法(compare and swap),无锁编程,在线程没有阻塞的情况下实现变量的同步。

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。其基本原理等同于版本号机制,旧的预期值等于数据版本,如果在进行原子操作时,发现旧的预期值和当前的内存值V不相等,则取消操作,否则把B值赋值给内存V。