您的位置:首页 > 职场人生

码农小汪-锁 LOCK

2016-04-02 15:26 531 查看

在java中有两种方法实现锁机制,一种是线程同步机制:synchronized,而另一种是比synchronized更加强大和领过的Lock。Lock确保当一个线程位于代码的临界区时,另一个线程不进入临界区,相对于synchronized,Lock接口及其实现类提供了更加强大、灵活的锁机制。

自Java 5开始,java.util.concurrent.locks包中包含了一些锁的实现,因此你不用去实现自己的锁了。但是你仍然需要去了解怎样使用这些锁,且了解这些实现背后的理论也是很有用处的

一个简单的锁

public class Test{
private int count = 0;

public int inc(){
synchronized(this){
return ++count;
}
}
}


同一个对象,在多个线程中,只有一个线程更新,得到同步的机会哦。

我们自己写一个类似的锁机制,替换掉系统给我们实现的吧

package ThredDemo;

public class Lock {
private boolean isLock=false;
public synchronized void Lock() throws InterruptedException{
while(isLock){
wait();
}
isLock=true;
}
public synchronized void unlock(){
isLock=false;
notify();
}
}


当isLocked为true时,调用lock()的线程在wait()调用上阻塞等待。为防止该线程没有收到notify()调用也从wait()中返回(也称作虚假唤醒),这个线程会重新去检查isLocked条件以决定当前是否可以安全地继续执行还是需要重新保持等待,而不是认为线程被唤醒了就可以安全地继续执行了。如果isLocked为false,当前线程会退出while(isLocked)循环,并将isLocked设回true,让其它正在调用lock()方法的线程能够在Lock实例上加锁。

当线程完成了临界区(位于lock()和unlock()之间)中的代码,就会调用unlock()。执行unlock()会重新将isLocked设置为false,并且通知(唤醒)其中一个(若有的话)在lock()方法中调用了wait()函数而处于等待状态的线程。

下面是我们的测试例子:

package ThredDemo;
/**
*
* @author JetWang
*
*/
public class ThreadLock {
private int count = 0;
Lock l = new Lock();

public void test() {
// TODO Auto-generated method stub
try {
l.Lock();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (int i = 0; i < 5; i++) {
count++;
System.out.println(Thread.currentThread().getName() + "count:"
+ count);
}
l.unlock();
}

public static void main(String[] args) {
ThreadLock testLock = new ThreadLock();
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
testLock.test();
}
}).start();
}
}
}


Thread-0count:1

Thread-0count:2

Thread-0count:3

Thread-0count:4

Thread-0count:5

Thread-1count:6

Thread-1count:7

Thread-1count:8

Thread-1count:9

Thread-1count:10

Thread-2count:11

Thread-2count:12

Thread-2count:13

Thread-2count:14

Thread-2count:15

这里为了保证,操作的是同一个对象,所以。我们的Lock放在属性中,而不是在Test中生成的,不然每一个线程中都有一个lock对象,在我们自己写的Lock中就不会同步,以为没有唯一的区分,不同的lock对象,同步方法块肯定就是不行的啦!好好的理解,不懂的看哈上面篇

锁的公平性:

公平性的对立面是饥饿。那么什么是“饥饿”呢?如果一个线程因为其他线程在一直抢占着CPU而得不到CPU运行时间,那么我们就称该线程被“饥饿致死”。而解决饥饿的方案则被称之为“公平性”——所有线程均可以公平地获得CPU运行机会。

导致线程饥饿主要有如下几个原因:操作系统说的就是调度的策略问题

高优先级线程吞噬所有的低优先级线程的CPU时间。我们可以为每个线程单独设置其优先级,从1到10。优先级越高的线程获得CPU的时间越多。对大多数应用来说,我们最好是不要改变其优先级值。

线程被永久堵塞在一个等待进入同步块的状态。java的同步代码区是导致线程饥饿的重要因素。java的同步代码块并不会保证进入它的线程的先后顺序。这就意味着理论上存在一个或者多个线程在试图进入同步代码区时永远被堵塞着,因为其他线程总是不断优于他获得访问权,导致它一直得到不到CPU运行机会被“饥饿致死”。

线程在等待一个本身也处于永久等待完成的对象。如果多个线程处在wait()方法执行上,而对其调用notify()不会保证哪一个线程会获得唤醒,任何线程都有可能处于继续等待的状态。因此存在这样一个风险:一个等待线程从来得不到唤醒,因为其他等待线程总是能被获得唤醒。

为了解决线程“饥饿”的问题,我们可以使用锁实现公平性。本文所展现的锁在内部是用synchronized同步块实现的,因此它们也不保证公平性。

锁的可重入性

Java中的synchronized同步块是可重入的。这意味着如果一个java线程进入了代码中的synchronized同步块,并因此获得了该同步块使用的同步对象对应的管程上的锁,那么这个线程可以进入由同一个管程对象所同步的另一个java代码块。获得了对象锁之后,我们可以再次获得相同的涩

Reentrant 再进去的,凹角的

网络 可重入; 可重入的; 重入

public class Reentrant{
public synchronized outer(){
inner();
}

public synchronized inner(){
//do something
}
}


java多线程的可重入性的实现是通过每个锁关联一个请求计算和一个占有它的线程,当计数为0时,认为该锁是没有被占有的,那么任何线程都可以获得该锁的占有权。当某一个线程请求成功后,JVM会记录该锁的持有线程 并且将计数设置为1,如果这时其他线程请求该锁时则必须等待。当该线程再次请求请求获得锁时,计数会+1;当占有线程退出同步代码块时,计数就会-1,直到为0时,释放该锁。这时其他线程才有机会获得该锁的占有权。

public class Father {
public synchronized void method(){
//do something
}
}
public class Child extends Father{
public synchronized void method(){
//do something
super.method();
}
}


如果所是不可重入的,上面的代码就会死锁,因为调用child的method(),首先会获取父类Father的内置锁然后获取Child的内置锁,当调用父类的方法时,需要再次后去父类的内置锁,如果不可重入,可能会陷入死锁。

??怎么修改刚刚我们的那个锁呢

第一要记住锁住的哪个?锁的次数?

下面的这个我们如果不修改上面的哪个Lock函数肯定不行的,为啥呢?我们的那个while循环体,没有考虑可重入性的问题。所以需要修改啦!

两次lock()之间没有调用unlock(),第二次调用lock就会阻塞,看过lock()实现后,

public class Reentrant2{
Lock lock = new Lock();

public outer(){
lock.lock();
inner();
lock.unlock();
}

public synchronized inner(){
lock.lock();
//do something
lock.unlock();
}
}


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

可重入锁LOCK

package ThredDemo;
/**
* 可重入的锁
* @author JetWang
*
*/
public class Lock {
private boolean isLock=false;
private Thread isLocakBy=null;//哪个线程锁住了
private int count=0;//数量是什么?
public synchronized void Lock() throws InterruptedException{
Thread thread=Thread.currentThread();
while(isLock&&thread==isLocakBy){//满足这两个条件才阻塞
wait();
}
isLock=true;
}
public synchronized void unlock(){
if(Thread.currentThread()==isLocakBy){
count--;
}
if(count==0){
isLock=false;
notify();
}
}
}


在finally语句中调用unlock()

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

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


java锁以及实现类图:

java.util.concurrent.locks提供了非常灵活锁机制,为锁定和等待条件提供一个框架的接口和类,它不同于内置同步和监视器,该框架允许更灵活地使用锁定和条件。它的类结构图如下:



慢慢的了解这些东西
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: