锁的应用锁,可以解决并发资源抢夺问题。示例如下publicclassTest{privatestaticintsum;publicstaticvoidmain(String[]args){for(inti=0;i<100;i++){Threadt=newThread(newCalc());t.开始();}尝试{Thread.sleep(1000);}catch(Exceptione){e.printStackTrace();}System.out.println(sum);}staticclassCalcimplementsRunnable{@Overridepublicvoidrun(){sum++;System.out.println(Thread.currentThread().getName()+":"+sum);}}}启动100个线程累加sum,结果不一定是100,加锁后优化解决publicclassTest{privatestaticvolatileintsum;私有静态ReentrantLock锁;publicstaticvoidmain(String[]args){lock=newReentrantLock();for(inti=0;i<100;i++){Threadt=newThread(newCalc());t.开始();}尝试{线程.睡眠(1000);}catch(Exceptione){e.printStackTrace();}System.out.println(sum);}staticclassCalcimplementsRunnable{@Overridepublicvoidrun(){lock.lock();总和++;锁定。解锁();System.out.println(Thread.currentThread().getName()+":"+sum);}}}题外话:上面的例子还有一个细节,volatile,没有volatile,不影响最终结果,只是线程打印的值重复了。这里验证类volatile的可见性被一个线程修改,其他线程立即可见。锁的条件,条件就是条件,用来共同完成一个工作,需要合作。例子,生成随机数,打印随机数,先生成后打印publicclassTest{privatestaticintrandom;私有静态ReentrantLock锁;私有静态条件条件;publicstaticvoidmain(String[]args)throwsException{lock=newReentrantLock();条件=lock.newCondition();新线程(新打印())。开始();线程.睡眠(1000);新线程(新生成器())。开始();System.out.println("主出口");}staticclassPrintimplementsRunnable{@Overridepublicvoidrun(){lock.lock();try{System.out.println("打印等待。");条件.await();System.out.println("打印开始。");System.out.println("结果为:"+random);}catch(Exceptione){e.printStackTrace();}finally{System.out.println("打印解锁。");锁定。解锁();}}}staticclassGeneratorimplementsRunnable{@Overridepublicvoidrun(){lock.lock();try{System.out.println("发电机启动。");random=(int)(Math.random()*10000);系统。出去。println("信号之前的生成器。");健康)状况。信号();系统。出去。println("信号后的生成器。");}最后{系统。out.println("发电机解锁。");尝试{Thread.sleep(5000);}catch(Exceptione){e.printStackTrace();}锁。解锁();}}}}锁实现LockSupport类介绍LockSupport用于实现锁时挂起和唤醒线程;在多线程竞争中,只有一个线程获取到锁继续运行,其他线程挂掉。锁释放后,队列最前面等待的线程被唤醒,称为唤醒,并不是说马上获取锁,而是想立即快速获取锁。挂起和唤醒之间的联系是LockSupport。公共类测试{privatestaticvolatileintsum;私有静态ReentrantLock锁;静态类Park实现Runnable{@Overridepublicvoidrun(){park();}voidpark(){System.out.println("park");LockSupport.park(这个);System.out.println("出公园");}}publicstaticvoidmain(String[]args){Threadt=newThread(newPark());t.开始();尝试{Thread.sleep(3000);}catch(Exceptione){e.printStackTrace();}LockSupport.unpark(t);}}上面演示了LockSupportpark和unpark的使用,线程在park处挂起,直到被其他线程唤醒,唤醒后会从park中的下一行代码继续执行。上面的例子是由主线程唤醒的。jstack命令查看主线程信息和park线程的线程信息,名称为Thread-0,park()和park(Objectblocker)的主要区别是后者打印阻塞信息更详细,如如上图所示“-停车等待xxxxx”。该阻滞剂是否还有其他用途还有待研究。重点关注点:看JDKLockSupport.park的方法注解,可以知道park方法是响应线程中断的,即线程被中断会被唤醒。线程被中断唤醒后,会将自己在等待锁队列中的Node状态设置为Cancel1,特殊情况会从tail向前遍历。原因:线程释放锁后,调用了unparkSuccessor。尾巴向前移动。因为如果阻塞队列有多个等待线程。在释放锁的瞬间,如果unparkSuccessor和等待线程并发中断,那么从头往下遍历可能无法遍历到有效节点;但是,从尾部遍历肯定可以得到有效节点。为什么从头部遍历可能找不到有效节点,因为AQS的cancelAcquire会将取消节点的next设置为,node.next=node;AQS模板设计模式:AQS提供了aquire和release模板方法供子类调用,模板方法中的tryRequire和tryRelease供子类调用自定义自己的逻辑。ReentrantLock可重入锁的实现涉及主要的类关系图和lock方法调用图。线程抢占锁,释放锁唤醒同步器队列线程,同步器的成员变量head,tail和state变化的详细过程如下,使用3个线程抢锁,开始运行到一个正在运行的进程结束。从上面可以看出,如果线程1释放了锁,线程2还没唤醒,线程4突然抢到了锁。此时线程2醒来后抢不到锁。这种谁先抢到就叫“非公平锁”。可重入锁还支持“公平锁”。公平锁就是等待时间最长的人优先。具体实现可以看源码。默认的可重入锁是“非公平锁”。从效率的角度来看,“非公平锁”效率更高。ReentrantLock的Condition实现了condition条件队列。线程只有先获取到锁,才能进入并执行condition.await。await动作将线程放入条件队列,然后释放锁,进入park让出CPU。condition.signal,将condition队列的线程放入同步队列,然后释放锁,unpark同步队列上的线程。Condition不能单独使用,必须和lock一起使用。实际上,condition的出现是为了满足多线程协调合作的需要。例如,在生产者-消费者模式下,消费者线程的输入是生产者线程的输出,因此两个线程作为Conditional运行,需要协调。如果两个线程是相互独立的,那么就没有必要使用锁和条件。那为什么条件一定要配合锁,因为生产者和消费者不能同时运行,所以需要锁,只有获得锁的一方才能运行。生产者获得锁后,生产一些东西,然后释放锁。让消费者消费。这种场景可以用lock锁,但是consumer消费后,如何让producer继续存活,就需要通知,condition就是这样产生的。下面是消费者和生产者的一个交互过程。需要说明的是,序号并不是严格意义上的运行顺序。比如4释放锁后,5和6并列,不确定谁先到。
