java lock锁的使用,java lock是悲观锁
如何解决写爬虫IP受阻的问题?立即使用。
Lock锁与生产者消费者问题
传统同步锁锁锁同步锁和锁锁的区别传统生产者和消费者问题锁版本条件的生产者和消费者问题实现精确通知和唤醒传统Synchronized锁
实现一个售票的基本例子:
/*
真正的多线程开发,在公司开发,减少耦合。
线程是一个独立的资源类,没有附加的操作。
1.属性和方法
* */公共类SaleTicketDemo1 {
公共静态void main(String[] args) {
//并发,多个线程操作同一个资源类,把资源类扔进线程。
票票=新票();
//Runnable excuse是functional interface函数接口。界面可以是新的。JDK 1.8之后,lamda表达式()-{code}
新线程(()-{
for(int I=0;i60i ){
ticket . sale();
}
},一)。start();
新线程(新Runnable() {
@覆盖
公共无效运行(){
for(int I=0;i60i ){
ticket . sale();
}
}
}, B )。start();
新线程(()-{
for(int I=0;i60i ){
ticket . sale();
}
}, C )。start();
} }//资源类OOPclass票证{
//属性,方法
private int数=50;
//售票的方式
//同步本质:队列,由
公共同步无效销售(){
if(number0){
system . out . println(thread . current thread()。getname()已售(数量-)票,剩余数量);
}
}}注意这里用的是lambda表达式。有关lambda表达式的详细描述,请参见Java Foundation -Lambda表达式。
这是用传统的synchronized来实现并发,synchronized的本质就是队列,锁。就像在食堂排队一样。如果没有排队,那就乱了。一个人的服务完成了,另一个人才能接受服务。
Lock锁
如前所述,JVM提供synchronized关键字实现对变量的同步访问,提供wait和notify实现线程间通信。在jdk1.5之后,JAVA提供了Lock类来实现与synchronized相同的功能,还提供了显示线程间通信的条件。
Lock类是Java类提供的一个函数,丰富的api使得Lock类的同步功能比synchronized同步更强大。在java.util. Concurrent包中,里面有3个接口,Condition,lock(标准锁)。ReadWriteLock锁(读写锁)Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition。
锁定l=.l . lock();try { //访问受此锁保护的资源} finally { l . unlock();}lock()表示加锁,unlock()表示解锁
所有已知实现类:重入锁在JDK官方文件中有说明。
Reentrantreadwritelock。读锁
reentrantreadwritelock . writelock写锁
先说一下ReentrantLock实现类:
ReentrantLock底层源代码构造函数
锁:很公平,你可以先来,再来。但是如果一个3s和3h的过程到了,3h先到了,那么3s等待3h,其实是不利的。
不公平锁:非常不公平,可以插队(默认)
之后我们会详细讲解。
怎么用,用之前加锁,用之后解锁
//锁定三部曲
//1 . new ReentranLock();结构
//2.lock . lock();锁
//3 . finally();开启
公共类SaleTicketDemo2 {
公共静态void main(String[] args) {
//并发,多个线程操作同一个资源类,把资源类扔进线程。
票票=新票();
//Runnable excuse是functional interface函数接口。界面可以是新的。JDK 1.8之后,lamda表达式()-{code}
新线程(()-{ for(int I=0;i60i )ticket.sale()。},一)。start();
新线程(()-{ for(int I=0;i60i )ticket.sale()。}, B )。start();
新线程(()-{ for(int I=0;i60i )ticket.sale()。}, C )。start();
} }//资源类OOP//lock三部曲//1 . new ReentranLock();//2.lock . lock();lock//3 . finally();解锁等级票2{
//属性,方法
private int数=50;
//售票的方式
//同步本质:队列,由
lock lock=new reentrant lock();
公开无效销售(){
lock . lock();
尝试{
//业务代码
if(number0){
system . out . println(thread . current thread()。getname()已售(数量-)票,剩余数量);
}
} catch(异常e) {
//TODO:处理异常
}最后{
lock . unlock();
}
}}Synchronized和lock锁的区别
1.synchronized是内置的java关键字,lock是Java类。
2.synchronized不能判断获取锁的状态,lock可以判断锁是否被获取。
3.synchronized会自动解除锁(A),锁必须手动解除!如果不释放锁,就会导致死锁。
4.同步的线程1(获取锁,阻塞),线程2(等等,傻等。)
Lock.tryLock()尝试获取锁,它不必永远等待。
5.同步重入锁,不可中断,不公平锁。锁,重入锁,可以判断锁,公平不公平可以自己设置(可以自己设置)。
6.synchronized适用于少量代码同步问题,lock lock适用于锁定大量同步代码。
同步锁对象和同步代码块的方法
传统的生产者和消费者问题
传统的生产者和消费者是基于对象类的wait、notify方法和synchronized关键字实现的。
手写生产者和消费者代码在面试中很常见。
经典面试问题:
单一模式排序算法生产者-消费者死锁
消费者问题的同步版本
线程间的通信问题:生产者和消费者等待唤醒并通知唤醒。
线程交替执行A B操作。相同的变量数=0
一号
b编号1
注:加锁的方法中,执行的思路是判断等待+业务+通知
包testConcurrent/*
线程间的通信问题:生产者和消费者等待唤醒并通知唤醒。
线程交替执行A B操作。相同的变量数=0
一号
b编号1
* */公共A类
公共静态void main(String[] args) {
数据data=新数据();
新线程(()-{
for(int I=0;i10i ){
尝试{
data . increment();
} catch(异常e) {
//TODO自动生成的catch块
e . printstacktrace();
}
}
},一)。start();
新线程(()-{
for(int I=0;i10i ){
尝试{
data . decrement();
} catch(异常e) {
//TODO自动生成的catch块
e . printstacktrace();
}
}
}, B )。start();
} }//判断等待服务通知类数据{//数量。班级资源
private int number=0;
//1。多线程的情况下一定要锁。
公共同步void increment()引发InterruptedException{
//确定是否需要等待,如果不需要,则需要为业务运营工作。
如果(数字!=0)当{//等于1时,需要等待,缓冲区只有1个空位置。
//等待操作
this . wait();
}
号码;//开展业务操作
system . out . println(thread . current thread()。getName() =号);
//通知其他线程我1完成了
this . notify();
}
//-1
公共同步void decrement()引发InterruptedException{
if(number==0){
//等待
this . wait();
}
数字-;
system . out . println(thread . current thread()。getName() =号);
//通知其他线程I -1已完成
this . notify();
}}
如图,基本可以实现所需功能,但还是会有问题。如果我此时再添加两个线程,那么
新线程(()-{
for(int I=0;i10i ){
尝试{
data . increment();
} catch(异常e) {
//TODO自动生成的catch块
e . printstacktrace();
}
}
}, C )。start();
新线程(()-{
for(int I=0;i10i ){
尝试{
data . decrement();
} catch(异常e) {
//TODO自动生成的catch块
e . printstacktrace();
}
}
}, D )。start();
这里结果中出现2,输出结果有问题。为什么?
if判断为什么会有问题:
如果只判断一次。因为如果被判定,已经进入了代码的等待线。这时候可能有多个线程在等待,甚至包括生产者和消费者。也许一个制片人在行刑后叫醒了另一个制片人。
我们的官方文件中给出了解释。
公共最终无效等待(长超时)
Throws InterruptedException导致当前线程等待,直到另一个线程调用此对象的notify()方法或notifyAll()方法,或者经过了指定的时间。线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:
同步(对象){
while(条件不成立)
obj.wait(超时);
.//执行适合条件的操作
}注意点:防止虚假唤醒问题。
我们在代码中使用if判断,但是我们应该使用while判断。
包testConcurrent/*
线程之间的通信问题:生产者和消费者问题等待唤醒,通知唤醒
线程交替执行有操作同一个变量数字=0
一号
b编号一
* */公共A类
公共静态void main(String[] args) {
数据数据=新数据();
新线程(()-{
for(int I=0;i10i ){
尝试{
数据。increment();
} catch(异常e) {
//TODO自动生成的捕捉块
e。printstacktrace();
}
}
},一)。start();
新线程(()-{
for(int I=0;i10i ){
尝试{
数据。减量();
} catch(异常e) {
//TODO自动生成的捕捉块
e。printstacktrace();
}
}
}, B ).start();
新线程(()-{
for(int I=0;i10i ){
尝试{
数据。increment();
} catch(异常e) {
//TODO自动生成的捕捉块
e。printstacktrace();
}
}
}, C ).start();
新线程(()-{
for(int I=0;i10i ){
尝试{
数据。减量();
} catch(异常e) {
//TODO自动生成的捕捉块
e。printstacktrace();
}
}
}, D ).start();
}}//判断等待业务通知类别数据{ //数字。资源类
private int number=0;
//1
公共同步无效增量()引发中断的异常{
//判断是否需要等待,如果不需要,就需要干活进行业务操作
而(号!=0){ //等于一的时候,需要等待,缓冲区只有一个空位置
//等待操作
这个。wait();
}
号码;//进行业务操作
系统。出去。println(线程。当前线程().getName() =号);
//通知其他线程,我一完毕了
这个。notify();
}
//-1
公共同步无效减量()引发中断的异常{
while(number==0){
//等待
这个。wait();
}
数字-;
系统。出去。println(线程。当前线程().getName() =号);
//通知其他线程,我-1完毕了
这个。notify();
}}Lock版的生产者和消费者问题
在同步的版本中,我们使用了等待和通知来实现线程之间的同步
在锁中,
此时同步的被锁替换了,那么等待和通知用什么来替换呢?
我们在官方文档java.util.concurrent.locks中,找到锁类,然后在底部找到了条件新条件()
返回一个新情况绑定到该实例锁实例。
在等待条件之前,锁必须由当前线程保持。呼叫Condition.await()将在等待之前将原子释放锁,并在等待返回之前重新获取锁。
然后我们再来了解情况类Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。一个Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。
我们可以看到,使用的时候new一个Condition对象。然后用await替代wait,signal替换notify
代码实现
//判断等待业务通知
类别数据2{//数字。资源类
private int number=0;
lock lock=new reentrant lock();
条件条件=锁定。新条件();
//1
公共无效增量()引发中断的异常{
尝试{
锁定。lock();
//业务代码
而(号!=0){
//等待
条件。await();
}
号码;
系统。出去。println(线程。当前线程().getName() =号);
条件。signal all();//通知
} catch(异常e) {
//TODO:处理异常
}最后{
锁定。unlock();
}
}
//-1
公共无效减量()引发中断的异常{
尝试{
锁定。lock();
//业务代码
而(号!=1){
//等待
条件。await();
}
数字-;
系统。出去。println(线程。当前线程().getName() =号);
条件。signal all();//通知
} catch(异常e) {
//TODO:处理异常
}最后{
锁定。unlock();
}
}}注意:主函数部分于最上面的代码一样。
这时候虽然说是正确的,但是它是一个随机分布的状态,现在我们希望它有序执行,即A执行完了执行B,B执行C,C完了执行D。即精准通知。
Condition实现精准通知唤醒
Condition实现精准的通知和唤醒
我们构造三个线程,要求A执行完了执行B,B执行完了执行C,C执行完了执行D.代码思想:
//加多个监视器,通过监视器来判断唤醒的是哪一个人
//设置多个同步监视器,每个监视器监视一个线程
//实例:生产线,下单-支付-交易-物流
包测试并发导入Java。util。并发。锁。条件;导入Java。util。并发。锁。锁;导入Java。util。并发。锁。重入锁;/*
A执行完调用B,B执行完调用C,C执行完调用A
* */公共C类
公共静态void main(String[] args) {
数据3数据=新数据3();
新线程(()-{
for(int I=0;i10i ){
数据。printa();
}
},一)。start();
新线程(()-{
for(int I=0;i10i ){
数据。printb();
}
}, B ).start();
新线程(()-{
for(int I=0;i10i ){
数据。printc();
}
}, C ).start();
} }类数据3{ //资源类
私有锁Lock=new reentrant Lock();
//加多个监视器,通过监视器来判断唤醒的是哪一个人
//设置多个同步监视器,每个监视器监视一个线程
//实例:生产线,下单-支付-交易-物流
私有条件条件1=锁定。新条件();
私有条件条件2=锁定。新条件();
私有条件条件3=锁定。新条件();
private int number=1;//1A2B3C
public void printA(){
锁定。lock();
尝试{
//业务,判断-执行-通知
而(号!=1){
//等待
条件一。await();
}
系统。出去。println(线程。当前线程().getName()=aaaaaaaa );
//唤醒,唤醒指定的人,B
数字=2;//精准唤醒
条件二。signal();
} catch(异常e) {
//TODO:处理异常
}最后{
锁定。unlock();
}
}
公共void printB(){
锁定。lock();
尝试{
//业务,判断-执行-通知
而(号!=2){
//等待
条件二。await();
}
系统。出去。println(线程。当前线程().getName()=bbbbbb );
//唤醒,唤醒指定的人,C
数字=3;//精准唤醒
条件三。signal();
} catch(异常e) {
//TODO:处理异常
}最后{
锁定。unlock();
}
}
public void printC(){
锁定。lock();
尝试{
//业务,判断-执行-通知
而(号!=3){
//等待
条件三。await();
}
系统。出去。println(线程。当前线程().getName()=cccccc );
//唤醒,唤醒指定的人,一个
数字=1;//精准唤醒
条件一。signal();
} catch(异常e) {
//TODO:处理异常
}最后{
锁定。unlock();
}
}}以上就是爪哇介绍锁锁与生产者消费者问题的详细内容,更多请关注我们其它相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。