线程通信
wait,notify
这2个方法,是Object类的方法,必须要先加锁才可以使用。顺序使用有要求
park,unpark
这2个方法,对顺序没有要求,但是不能加锁使用在同步代码块里面。LockSupport类的静态方法。
ps,这个方法的通信条件不要使用if来进行判断,可能出现伪唤醒。使用while来代替。
线程池数量判断规则
1.计算类任务
cpu的1-2倍,比如cpu8核,那么16个线程
2.io类任务
因为有io阻塞,要大一点。最佳线程数量 = ((线程等待时间+线程CPU时间)/ 线程CPU时间)* CPU个数
终极规则:
看看你的cpu使用情况>80%是比较合理的,不断调整
线程加锁
synchronized关键字加锁
偏向锁 -轻量级锁-重量级锁,这是java虚拟机做的优化。
第一个线程第一次加锁(通过偏向锁标识位),那么就会采用偏向锁的机制,修改对象头的标识位,将第一个线程的线程id加进去。
当第二个线程访问这个对象时,判断是否偏向锁,是则第一步,不是则判断是否有线程持有锁,若是没有持有锁,那么cas操作尝试加锁,切将锁升级为轻量级锁,cas操作是,将加锁标识位修改,然后把markword里面的信息,存放到线程栈的私有信息里面去,将其替代为加锁线程的地址。若是有锁,那么自旋多次cas后,还是没有成功,则将其升级为重量级锁。
总量级锁通过monitor来实现,每个对象都有一个monitor。
monitor里面有entrylist,里面是争抢锁的。owner则是锁的持有者,waitset则是owner调用wait后进入的集合。通过notify进入到entrylist里面。
lock api
ReentrantLock:独享锁,可重入锁,支持公平和不公平
ReadWriteLock:读写锁,其包含2把锁,创建这个读写锁对象后,我们可以通过这个对象获取到2把锁,一个读锁,一个写锁,读锁可以多个线程同时持有,写锁,一个线程持有,在加了读锁后,不能加写锁。(可以用这个特点去做锁降级)。
aqs,本质上就是通过cas,链表,park,unpark来处理的。
Semaphore,信号量,创建对象时,指定一个信号量。维护一个原子变量,有2个方法,一个是
acquire:获取一个许可,没有就等待
release:释放一个许可
场景:代码限流,维护一个资源只被限定的线程数量访问
CountDownLatch:到计数器,创建对象时,指定一个计数器。维护一个原子变量,有2个方法,一个是
await():方法等待计数器变为0,在此之前,线程进入等待状态。
countdown():方法使得计数器建一。
场景:统计线程执行情况。看看哪些线程没有执行完。
使用多个线程来计算数据,实现并发处理。
多个线程相互通信。
CyclicBarrier:线程栅栏。创建对象时,指定一个栅栏线程大小。
。维护一个原子变量,同时可以加一个Runnable接口,作为满足条件后的执行的方法
核心方法是
await():线程调用await方法后,判断当前调用await方法的线程数目是否达到了创建对象所指定的数量,没有达到进入等待状态,达到了,执行后续的方法。
和前面countdownlatch的区别是可以多次执行。每满足一次数量后,就会执行一次调用await方法线程的后续代码和Runnable接口的方法,可以多次
场景:拼多多拼团购物。
批量插入数据。