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的学习小站!