本篇文章旨在对多线程的知识进行补充总结。
1.wait和sleep的区别
wait()是Object类的方法,是线程同步的手段之一。sleep是thread类的方法。区别:
1.wait()必须在synchronized同步块或方法里调用;
2.wait()会释放synchronized锁上的对象锁,sleep不会;
3.wait()形成的阻塞,只能在同一个对象锁的synchronized作用域中调用notify()/notifyAll()来唤醒,而sleep()只能等待定时醒来或interrupt打断;
2.线程池
使用Executors创造线程池时:
线程数不满corePoolSize时总是创建新线程来执行任务;
线程数高于corePoolSize且低于maxPoolSize时,如使用SynchronousQueue队列,将检查是否有空闲线程,如有则使用,如没有则创建新线程来执行任务;如使用BlockingQueue队列,则新任务进入队列等待(这意味着maxPoolSize失效,线程池大小不可能超过corePoolSize了);
线程数达到最大线程数时,如使用SynchronousQueue队列,则触发拒绝策略;如使用BlockingQueue队列,则尝试将任务入队,如队列已满,则触发拒绝策略;
SynchronousQueue是一种特殊的零长度阻塞队列
3.synchronized锁住的是什么
每个java对象在内存中都有一个对应的锁监视器monitor,用来储存锁标记。同一时刻只能有一个线程拥有这个锁标记(其实就是0和1),这个锁也成为内部锁。
当多个线程执行到synchronized的代码块,只有拥有指定对象monitor锁的线程才能进入临界区,其他线程将阻塞。
当synchronized是单独的方法块时,锁住的是括号内的对象;当修饰普通方法时,锁住的是这个方法的拥有者(即实现类的一个实例对象);当修饰静态方法或类时,锁住的就是这个类对象。
4.Lock和synchronized的区别
Lock有ReentrantLock实现,线程在获取Lock锁的时候不会阻塞锁,而是通过不停循环(自旋)重试,直到持有该Lock锁的线程释放。synchronized会阻塞。
ReentrantLock可重入,持有Lock锁的线程还可以继续加锁。synchronized不可以。
Lock可以搭配Condition创建多个不同的条件变量,灵活多样地控制线程的睡眠和唤醒。synchronized不可以。
ReentrantLock支持公平和非公平锁(获取锁时是否插队)。synchronized都是非公平锁。
5.线程安全容器总结
ConcurrentHashMap:在HashMap的基础上再加一层segment,每个segment上各应用一把锁。一定程度上减少了锁的竞争,也允许一定数量并发读写。
CopyOnWriteArrayList:适用读多写少场景,读操作不加锁,写操作复制出一个新的队列然后在新集合上修改,然后新集合替换老集合。
ConcurrentSkipListMap:并发的Treemap;
ConcurrentLinkedQueue:基于链表的非阻塞队列;
BlockingQueue:阻塞并发队列接口,提供可阻塞的put()和get()方法。调用put()时,如果队列满则阻塞当前线程,直到队列有空间;调用take()时,如果队列为空则阻塞线程,直到队列不空。
系列文章:
java多线程解说【肆】_锁实现:wait()/notify()
java多线程解说【伍】_锁实现:ReentrantLock的实现
java多线程解说【柒】_锁实现:Lock/Condition的例子
java多线程解说【捌】_锁实现:读写锁ReentrantReadWriterLock
java多线程解说【玖】_锁实现:LockSupport工具类
java多线程解说【拾伍】_并发工具类:CountDownLatch