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()方法,此时就进入死亡状态,该线程结束生命周期。
为什么用线程池
1.创建/销毁线程伴随着系统开销,过于频繁的创建/销毁线程,会很大程度上影响处理效率
例如:
记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3
如果T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!
正好,线程池缓存线程,可用已有的闲置线程来执行新任务,避免了T1+T3带来的系统开销
2.线程并发数量过多,抢占系统资源从而导致阻塞
我们知道线程能共享系统资源,如果同时执行的线程过多,就有可能导致系统资源不足而产生阻塞的情况
运用线程池能有效的控制线程最大并发数,避免以上的问题
3.对线程进行一些简单的管理
比如:延时执行、定时循环执行的策略等
运用线程池都能进行很好的实现
线程池的工作原理
多线程并发之乐观锁和悲观锁
悲观锁 ,为了防止自己拿到数据后别人会来修改,你就把数据加上锁,直到自己处理完,才会把数据释放给别人。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。