首页 > 编程知识 正文

java多线程乐观锁实现代码,java多线程锁表

时间:2023-05-03 15:20:34 阅读:40028 作者:3417

从Java 5开始,java.util.concurrent.locks包包含一些锁定实现,因此不需要实现自己的锁定。 但是,我需要知道如何使用这些锁。

很简单的锁

让我们从java同步块开始。

公共类计数器{私有int count=0; 公共intinc ()同步(this ) ) {返回计数;

}

}

}

可以看到inc )方法包含同步(this )代码块。 此代码块确保只有一个线程可以同时执行返回计数。 同步块中的代码可以更复杂,但简单的count操作足以表示线程同步的含义。

以下Counter类使用Lock代替同步实现了相同的目的。

公共类计数器{私有锁=newlock (; 保密计数=0; 公共int Inc (}

lock.lock (; int newCount= count;

lock.unlock (; 返回新计数;

}

}

lock ) )方法锁定lock实例对象,因此对该对象lock ) )调用方法的所有线程都被阻止直到调用该lock对象的unlock ) )方法

这里有一个Lock类的简单实现:

公共类计数器{公共类锁定}私有类booleanislocked=false; 公共同步语音锁定() throwsinterruptedexception (while ) islocked ) )。

等待(;

}

isLocked=true;

}公共同步语音解锁(

isLocked=false;

notify (;

}

}

请注意其中的while (已锁定)循环。 那又叫“自旋锁”。 如果isLocked为true,则调用lock () (的线程为wait ) )调用阻止并等待。 为了防止线程在没有收到通告()调用的情况下从wait ) )返回,线程将重新检查isLocked条件,以确定当前是否可以安全运行。 应该重新等待,而不是认为线程被唤醒后可以安全地继续运行。 如果isLocked为false,则当前线程将退出while(isLocked循环,并将isLocked返回true,以便调用lock[]方法的其他线程可以在lock实例上锁定。

当线程完成lock (和unlock )之间的关键节时,将调用unlock )。 unlock (运行时,isLocked被重新设置为false,lock () ) ) )方法中的wait ) )函数被调用以通知等待的线程之一)唤醒)。

锁定的可重新输入性

Ava同步块可以重新导入。 也就是说,如果一个java线程进入代码中的同步同步块,并获取与同步块使用的同步对象相对应的管道上的锁,则该线程将进入由同一管道对象同步的另一个java代码块以下是一个例子。

公共类保留{公共同步代码生成器}

inner (;

}公共同步inner (() {//do something

}

}

请注意,outer (和inner )都声明为已同步。 这在Java中等效于同步(this )块。 如果线程调用outer (,则在outer ) ()中调用inner ) ),则没有问题。 这是因为这两个方法(代码块)由同一管线对象) (“this”)同步。 如果线程已经具有管线对象的锁定,则可以访问由该管线对象同步的所有代码块。 这就是可以重新进入的事情。 线程可以进入已经拥有的锁同步的代码块中。

前面列出的锁定实现是不可重新输入的。 如果重写Reentrant类,则线程调用outer () (时,inner ) )方法的lock.lock ) )将阻止该类。

公共类保留2 {

锁定锁定=newlock (; publicouter () }

lock.lock (;

inner (;

lock.unlock (;

}公共同步

chronizedinner(){

lock.lock();//do something

lock.unlock();

}

}

调用 outer()的线程首先会锁住 Lock 实例,然后继续调用 inner()。inner()方法中该线程将再一次尝试锁住 Lock 实例,结果该动作会失败(也就是说该线程会被阻塞),因为这个 Lock 实例已经在 outer()方法中被锁住了。

两次 lock()之间没有调用 unlock(),第二次调用 lock 就会阻塞,看过 lock()实现后,会发现原因很明显:

public classLock{boolean isLocked = false;public synchronized voidlock()throwsInterruptedException{while(isLocked){

wait();

}

isLocked= true;

}

...

}

一个线程是否被允许退出 lock()方法是由 while 循环(自旋锁)中的条件决定的。当前的判断条件是只有当 isLocked 为 false 时 lock 操作才被允许,而没有考虑是哪个线程锁住了它。

为了让这个 Lock 类具有可重入性,我们需要对它做一点小的改动:

public classLock{boolean isLocked = false;

Thread lockedBy= null;int lockedCount = 0;public synchronized voidlock()throwsInterruptedException{

Thread callingThread=Thread.currentThread();while(isLocked && lockedBy !=callingThread){

wait();

}

isLocked= true;

lockedCount++;

lockedBy=callingThread;

}public synchronized voidunlock(){if(Thread.curentThread() ==

this.lockedBy){

lockedCount--;if(lockedCount == 0){

isLocked= false;

notify();

}

}

}

...

}

注意到现在的 while 循环(自旋锁)也考虑到了已锁住该 Lock 实例的线程。如果当前的锁对象没有被加锁(isLocked = false),或者当前调用线程已经对该 Lock 实例加了锁,那么 while 循环就不会被执行,调用 lock()的线程就可以退出该方法(译者注:“被允许退出该方法”在当前语义下就是指不会调用 wait()而导致阻塞)。

除此之外,我们需要记录同一个线程重复对一个锁对象加锁的次数。否则,一次 unblock()调用就会解除整个锁,即使当前锁已经被加锁过多次。在 unlock()调用没有达到对应 lock()调用的次数之前,我们不希望锁被解除。

现在这个 Lock 类就是可重入的了。

锁的公平性

Java 的 synchronized 块并不保证尝试进入它们的线程的顺序。因此,如果多个线程不断竞争访问相同的 synchronized 同步块,就存在一种风险,其中一个或多个线程永远也得不到访问权 —— 也就是说访问权总是分配给了其它线程。这种情况被称作线程饥饿。为了避免这种问题,锁需要实现公平性。本文所展现的锁在内部是用 synchronized 同步块实现的,因此它们也不保证公平性。

在 finally 语句中调用 unlock()

如果用 Lock 来保护临界区,并且临界区有可能会抛出异常,那么在 finally 语句中调用 unlock()就显得非常重要了。这样可以保证这个锁对象可以被解锁以便其它线程能继续对其加锁。以下是一个示例:

lock.lock();try{//do critical section code,//which may throw exception

} finally{

lock.unlock();

}

这个简单的结构可以保证当临界区抛出异常时 Lock 对象可以被解锁。如果不是在 finally 语句中调用的 unlock(),当临界区抛出异常时,Lock 对象将永远停留在被锁住的状态,这会导致其它所有在该 Lock 对象上调用 lock()的线程一直阻塞。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。