Java并发
线程的六种状态
线程池的核心参数
线程池可以看做是线程的集合。在没有任务时线程处于空闲状态,当请求到来:线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务执行(而不是销毁)。这样就实现了线程的重用。如果请求到来时,核心线程被占用,任务便会进入workQueue中等待。若workQueue也满,则会创建救急线程来执行,执行完任务后,救急线程不会保留在线程池中,有一定的生存时间,取决于keepAliveTime与unit。若还有时间,会从workQueue中依次加载任务。
- corePoolSize核心线程数目:最多保留的线程数。
- maximumPoolSize最大线程数目:核心线程+救急线程。
- keepAliveTime生存时间:针对救急线程。
- unit时间单位:针对救急线程。
- workQueue :阻塞队列。
- threadFactory线程工厂:可以为线程创建时起个好名字。
- handler拒绝策略:当核心线程、救急线程、阻塞队列都满是,
submit一个任务,则会触发拒绝策略。- 默认为
AbortPolicy,会抛出异常。 CallerRunsPolicy,由提交任务的线程完成任务(如main线程)。DiscardPolicy,丢弃,不运行,也不报错。DiscardOldestPolicy,抛弃队列中最先加入的任务,新任务进入队列。
- 默认为
代码演示
private static void testWaiting() { |
t2.start()启动线程,t2线程变为runnable状态,线程进入代码块- t2线程执行
wait()方法后,锁释放,主线程进入sychronized代码块 - 主线程调用
notify()方法,打印t2线程状态,为blocked,因为锁被主线程占有 - 主线程执行完
sychronized代码块,t2线程继续运行,状态变为runnable
sleep vs wait
- 共同点:
wait()、wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态。 - 方法归属不同:
sleep(long)是Thread的静态方法wait()、wait(long)都是Object的成员方法,每个对象都有
- 醒来时机不同:
- 执行
sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来 wait(long)和wait()还可以被notify唤醒,wait()如果不唤醒就一直等下去- 它们都可以被线程对象的
interrupt()打断唤醒,被打断的线程抛出异常
- 执行
- 锁特性不同:
wait()方法的调用必须先获取wait对象的锁,而sleep()无此限制wait()方法执行后会释放对象锁,允许其他线程获得该对象锁(我放弃,但你们还可以用)- 而
sleep()如果在synchronized代码块中执行,并不会释放对象锁(我放弃,你们也用不了)
lock vs synchronized
- 语法层面
synchronized是关键字,源码在jvm中,用c++语言实现Lock是接口,源码由jdk提供,用java语言实现- 使用
synchronized时,退出同步代码块锁会自动释放,而使用Lock时,需要手动调用unlock方法释放锁
- 功能层面
- 二者均属于悲观锁,都具备基本的互斥、同步、锁重入功能
- 互斥:多个线程争抢一把锁,只有一个线程成功,其余锁进入阻塞状态
- 同步:多线程同步运行,当某一线程需要另一线程的计算结果,那么它会等待那个线程运行完再运行下去
- 锁重入:可以给同一对象加多道锁(加多道,解锁时也要解多道)
Lock提供了许多synchronized不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量(因为synchronized用c++实现,较为底层)- 获取等待状态:互斥、同步时,可以知道哪些线程处于阻塞状态、等待状态
- 公平锁:多线程争抢锁失败后,后续再获得锁时,若先来先得为公平锁,可插队为非公平锁
Lock可以公平锁也可以非公平锁,而synchronized为非公平锁- 非公平锁吞吐量高,插队效率更高
- 可打断、可超时:锁被线程占用,其他线程的等待过程可以被打断,或者设置超时时间,
synchronized则一直等下去 - 多条件变量:
Lock有多个等待队列(await()方法使线程进入waiting queue,signal()方法唤醒,synchronized只有一个
Lock有适合不同场景的实现,如ReentrantLock(可重入锁),ReentrantReadWriteLock(针对读多写少),synchronized只有一层实现,即用c++语言在jvm底层实现
- 二者均属于悲观锁,都具备基本的互斥、同步、锁重入功能
- 性能层面
- 在没有竞争时,
synchronized做了很多优化,如偏向锁、轻量级锁,性能不赖 - 在竞争激烈时,
Lock的实现通常会提供更好的性能
- 在没有竞争时,
volatile(修饰符)能否保证线程安全
-
线程安全要考虑三个方面:可见性、有序性、原子性,
volatile可以保证可见性和有序性。-
**可见性:**一个线程对共享变量修改,另一个线程能看到最新的结果。
-
**有序性:**一个线程内代码安编写顺序执行。假如你编写顺序是123,但cpu对你优化,变为321执行,这对单线程没问题,对多线程可能会有问题。
-
**原子性:**一个线程内多行代码以一个整体运行,期间不能有其他线程的代码插队。
static volatile int balance = 10;
public static void subtract(){
balance -= 5;
}
public static void add(){
balance += 5;
}调用两个线程执行加减操作,执行
javap -p - v ( + *.class字节码文件),查看反编译内容。t1
0:getstatic
3:iconst_5
4:iadd
5:putstatic
t2
0:getstatic
3:iconst_5
4:isub
5:putstatic可以看出
balance += 5一条代码,对应底层多行操作,若两个线程执行各自的多行代码时发生交错,则可能导致最后结果出错。所以要保证原子性,可用synchronized、lock锁、CAS。CAS原理:比较并交换,如果我们读到的值v和调用weakCompareAndSetInt中快照获取的内存中的值一致的话,那么我们就将变更后的值进行写入到主内存中的操作进行下去;如果预期值与实际的值不一致(即已经有其他的线程捷足先登,提前改变了这个值,那么我们再取主内存中最新的值,进行循环的比较与交换操作)。
-
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Abacteria的学习小站!
