1 synchronized
1.1 同步方法获取的锁都是对象锁,而不是把一段代码和方法当作锁(等同于在方法中添加同步代码块synchronized(this){})
那个线程先执行带有synchronized的方法,那个线程就拥有该方法所属对象的锁,其他线程只能等待。
前提:多个线程访问同一个对象
public synchronized void setAndShowAge(String username) throws } public void setAndShowAge(String username) throws InterruptedException { synchronized (this) { } }
1.2 A线程持有object对象的Lock锁,B线程可以以异步方式调用object对象中的非synchronized类型的方法,但如果要调用object对象的同步方法则需要等待。
1.3 当一个线程持有object对象锁后,再次请求此对象锁时可以再次得到该对象锁(锁重入机制,也支持父子类继承的环境中)
1.4 当一个线程执行出现异常时,会释放持有的锁。
1.5 在静态方法上添加synchronized,表示对当前的.java文件对应的class类加锁
静态同步方法和非静态同步方法持有不同的锁,前者是类锁,后者是对象锁。
2 ReentrantLock
2.1 ReentrantLock持有的锁是对象锁,但与Synchronized持有的对象锁不是同一个,需要在finally中进行手动unlock
2.2 ReentrantLock与Synchronized的区别
2.2.1 Synchronized是基于JVM层面实现的,而ReentrantLock是基于JDK层面实现的
2.2.2 ReentrantLock在性能方面更全面,包括时间锁等待,可中断锁等待,而且一个锁可以有多个Condition,可以实现多路通知
2.2.3 ReentrantLock性能比Synchronized好
2.2.4 ReentrantLock提供了可轮询的锁请求,可以有效避免死锁的现象
private static void reentrantLockDeadLock() { Lock lock_a=new ReentrantLock(); Lock lock_b=new ReentrantLock(); new Thread(()->{ //尝试得到锁,如果尝试次数超过了指定的值还没有得到锁,就会抛出错误,该线程释放锁
//tryLock()只探测锁是否,并没有lock()的功能,要获取锁,还得调用lock()方法 /** * 源码中这样写道: * else if (current == getExclusiveOwnerThread()) { * int nextc = c + acquires; * if (nextc < 0) // overflow * throw new Error("Maximum lock count exceeded"); * setState(nextc); * return true; * } */ if (lock_a.tryLock()) { try {
lock_a.lock(); System.out.println("线程"+Thread.currentThread().getName()+"获取到a锁,正在等待b锁"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } if (lock_b.tryLock()) { try {
lock_b.lock(); System.out.println("线程"+Thread.currentThread().getName()+"获取到b锁"); } finally { lock_b.unlock(); } } }finally { lock_a.unlock(); } } }).start(); new Thread(()->{ if (lock_b.tryLock()) { try {
lock_b.lock(); System.out.println("线程"+Thread.currentThread().getName()+"获取到b锁,正在等待a锁"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } if (lock_a.tryLock()) { try {
lock_a.lock(); System.out.println("线程"+Thread.currentThread().getName()+"获取到a锁"); } finally { lock_a.unlock(); } } }finally { lock_b.unlock(); } } }).start(); }
2.3 是否要使用ReentrantLock代替Synchronized(不可以)
2.3.1 Synchronized代码块或者方法结束时可以自动释放锁,可以有效避免因忘记手动调用ReentrantLock的unlock()方法产生的死锁
2.3.2 当JVM调用Synchronized管理和释放锁是会打印相应的堆栈信息,对于调试和异常处理非常有用;而Lock是普通的类,JVM不知道是那个线程调用Lock对象。
2.4 什么时候使用ReentrantLock代替Synchronized
只有在Synchronized没有的特性情况下才考虑使用ReentrantLock
2.5 ReentrantLock公平锁和非公平锁
ReentrantLock构造方法中可以传递参数,默认为false,表示非公平锁,否则为公平锁
公平锁:哪个线程先启动,那个线程就优先获得锁
非公平锁:采用抢占机制,各个线程随机获得锁
3 读锁(乐观锁)和写锁(排他锁)
多个线程可以同时进行读操作,但同一时刻只允许一个写操作;且读和写也是排斥的
4 BlockingQueue 阻塞队列
4.1 插入方法
add() 如果有足够的空间返回true,否则抛出异常
offer() 如果有足够的空间返回true,否则返回false
put() 如果有足够的空间就插入值,没有返回值,否则将等待直到有了足够的空间
4.2 移除方法
take() 移除头部的元素,如果没有一直等待
poll(long timeout, TimeUnit unit) 移除头部元素,等待指定的时间如果还没有就返回null
remove(Object o) 移除o.equals(e)的元素,存在返回true,不存在返回false
drainTo(Collection<? super E> c) 可以一次读取指定的个数到集合中,可以提高效率,不需要多次分批加锁和解锁
/** * * blockQueue * * @Description 基于阻塞队列实现的生产者和消费者模式 * @return void * @see * @since */ private static void blockQueue() { //阻塞队列 BlockingDeque<Integer> blockingDeque=new LinkedBlockingDeque<>(5); //模拟三个生产者同时生产 for(int i=0;i<3;i++){ Thread producter=new Thread(()->{ while (true) { int product=new Random().nextInt(10); try { //waiting if necessary for space to become available. blockingDeque.put(product); } catch (Exception e) { e.printStackTrace(); } System.out.println("生产者"+Thread.currentThread().getName()+"生产了"+product+",size="+blockingDeque.size()+","+blockingDeque.toString()); try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } } }); producter.setName("producter"+(i+1)); producter.start(); } //模拟10个消费者同时消费 for(int i=0;i<10;i++){ Thread consumer=new Thread(()->{ while (true) { Integer product; try { //waiting if necessary until an element becomes available product = blockingDeque.take(); //List<Integer> products=new ArrayList<>(); //可以一次读取队列中的多个元素,但不会阻塞 //blockingDeque.drainTo(products,3); System.out.println("消费者"+Thread.currentThread().getName()+"消费了"+product); Thread.sleep(3000); } catch (Exception e) { e.printStackTrace(); } } }); consumer.setName("consumer"+(i+1)); consumer.start(); } }
4.3 ArrayBlockingQueue和LinkedBlockingQueue的区别
4.3.1 ArrayBlockingQueue中的锁是没有分离的,即生产者和消费者用的是同一个锁
LinkedBlockingQueue中的锁是分离的,即生产者用的是putLock,消费者用的是takeLock
4.3.2 ArrayBlockingQueue基于数组,直接将枚举对象插入或移除,不产生额外的对象实例
LinkedBlockingQueeu基于链表,需要将枚举转换为Node<E>进行插入和删除,会产生额外的Node对象,会有gc影响
4.3.3 ArrayBlockingQueue是有界的,必须指定大小
LinkedBlockingQueeu是无界的,可以不指定大小,默认为Integer.MAX_VALUE
4.4 SynchronousQueue是一种没有缓冲区的阻塞队列,每一个插入操作必须等待一个线程对应的移除操作
5 线程组
5.1 线程组的作用:批量管理线程或线程组对象,有效地对线程或线程组进行组织
5.2 线程必须启动后才能归属到指定的线程组中
5.3 如果线程没有指定线程组,会自动归属到当前线程所属的线程组中
5.4 根线程组就是系统线程组system
6 中断机制
6.1 中断机制是一种协作机制,只是设置中断标志,发出中断请求,并不会真正的终止线程,什么时候中断需要被中断的线程自己处理。
6.2 interrupt() 仅仅设置中断标识,具体有JVM虚拟机实现
interrupted() 测试当前线程是否已经中断,是否清除中断标识符由参数决定,静态方法,获取的是当前线程的中断状态
isInterrupted() 测试当前线程是否已经中断,不清楚标识符,实例方法,测试的是调用该方法的线程锁表示的线程
public static boolean interrupted() { return currentThread().isInterrupted(true);
}
Thread thread=new Thread(); thread.start(); Thread.currentThread().interrupt(); System.out.println("interrupted:"+thread.interrupted()); //判断的是当前主线程的状态:interrupted:true System.out.println("isInterrupted:"+thread.isInterrupted()); //判断thread子线程的状态:isInterrupted:false
7 Semaphore
并发控制器,可以同时提供的信号量(同时运行的线程,如果一个线程只需要一个信号量)
每个线程运行前需要从semaphore中请求信号量,运行结束后进行释放
如果semaphore中暂时足够的信号量,该线程进入等待状态
private static void semaphoreUse() { //定义并发管理器,同时最大运行5个信号量 final Semaphore semaphore=new Semaphore(5); //同时启动了十个线程,但只能同时运行5个 for(int i=0;i<10;i++){ new Thread(()->{ try { try { //可以一下请求多个信号量 //semaphore.acquire(2); semaphore.acquire(); } catch (Exception e) { e.printStackTrace(); } System.out.println("线程"+Thread.currentThread().getName()+"获取到了信号"); try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } System.out.println("线程"+Thread.currentThread().getName()+"释放了信号"); } finally { //可以一下释放多个信号量 //semaphore.release(2); semaphore.release(); } }).start(); } }
8 Exchanger两个线程间的数据交换
private static void exchangerUse() { //用于两个线程间的数据交换 final Exchanger<String> exchanger=new Exchanger<>(); for(int i=0;i<2;i++){ new Thread(()->{ String threadName=Thread.currentThread().getName(); System.out.println("线程"+threadName+",原数据为:"+threadName); try { //线程会在这里进行阻塞,直到两个线程都运行到这里,线程会被唤醒,执行执行以下代码 threadName=exchanger.exchange(threadName); } catch (Exception e) { e.printStackTrace(); } System.out.println("线程"+Thread.currentThread().getName()+",交换后数据为:"+threadName); }).start(); } }