Java 锁 重入锁 读写锁

ReentrantLock锁
java5.0之前,我们对于共享对象访问的机制只有synchronized关键字和volatile变量,现在提供了ReentrantLock
该锁比synchronized更多的灵活处置,比如,synchronized没法去解决死锁,不能中断正在等待获取锁的线程.
ReentrantLock锁
1.可重入
单线程可以重复进入,但必须重复退出。
2.可中断
可以中断正在等待的线程
3可限时
超时不能获得锁,就返回false,不会永久等待构成死锁
4.可轮训
tryLock() ,只有在获取到锁时,才返回true.
可轮训和可限时可以规避死锁
5.公平锁
public ReentrantLock(boolean fair)
public static ReentrantLock fairLock = new ReentrantLock(true);
设置为true时,按照先来先得的原则,后来的被放进等待队列中.

那么怎么选择synchronized和ReentrantLock了
从上面可以看出,ReentrantLock有很多的特性,那么如果想选择可中断,可限时,可轮询等就可以选择ReentrantLock.
锁的书写方式:
class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() { 
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

2. 可重入代码如下:
public class TestReentrantLock implements Runnable {
	private ReentrantLock lock = new ReentrantLock();
	private int a;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 1000; i++) {
			try {
				// 加锁
				lock.lock();
				a++;
			} catch (Exception e) {
				// TODO: handle exception
			} finally {
				// 释放锁
				lock.unlock();
			}
		}

	}

	public static void main(String[] args) throws InterruptedException {
		TestReentrantLock tr = new TestReentrantLock();
		Thread t1 = new Thread(tr);
		Thread t2 = new Thread(tr);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(tr.a);
	}
}

3.可限时
如果在规定的时间内范围内,没有获取到该锁,等返回false,等待.
/**
 * 一段时间内,尝试获取锁
 * @author Administrator
 *
 */
public class TimeReentrantLock implements Runnable{
	private ReentrantLock lock = new ReentrantLock();

	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			//设置在5秒内获取锁
			if(lock.tryLock(5, TimeUnit.SECONDS)){
				System.out.println(Thread.currentThread().getName()+"获取锁成功");
				Thread.sleep(6000);
			}else{
				System.out.println(Thread.currentThread().getName()+"获取锁失败");
			}

		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			if(lock.isHeldByCurrentThread()){
				lock.unlock();	
			}
		}
	}
	
	public static void main(String[] args) {
		TimeReentrantLock tr = new TimeReentrantLock();
		Thread t1 = new Thread(tr,"t1");
		Thread t2 = new Thread(tr,"t2");
		t1.start();
		t2.start();
	}
}

结果:
t1获取锁成功
t2获取锁失败
Exception in thread "t2" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)
at java.util.concurrent.locks.ReentrantLock.unlock(Unknown Source)
at com.study.displaylock.TimeReentrantLock.run(TimeReentrantLock.java:25)
at java.lang.Thread.run(Unknown Source)

ReentrantLock源码分析
前面我们看到,ReentrantLock有两种锁,一种是公平锁,一种是非公平锁。
首先,来学习不公平锁
主要是下面三种技术来实现:
1.cas 2.等待队列 3.park

不公锁调用如下:

 final void lock() {
            if (compareAndSetState(0, 1))//使用cas尝试设置值,如果设置成功,则表明现在没有现成得到锁,那么获取成功
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);//再次尝试
        }
 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&   //再次尝试获取锁,成功则不再做如下操作
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//否则添加到等待队列中
            selfInterrupt();
    }

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {//cas成功,则直接放入队列
                pred.next = node;
                return node;
            }
        }
        enq(node);.//不成功,强制放入队列
        return node;
    }
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {//再次尝试加锁
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&//如果等待状态singal的就直接返回,singal表示这个线程可以unpack,如果是cancelled,则忽略这些线程
                    parkAndCheckInterrupt())//否则,pack这些线程
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

释放锁
public void unlock() {
        sync.release(1);
    }

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//unpark 释放这些线程
            return true;
        }
        return false;
    }

读写锁:
ReentrantLock不管是读取还是写入都是互斥的,这对于大量的并发读取操作来说,性能就不会太高,ReentrantReadWriteLock读写锁,可以允许多个线程来读,只有一个线程来写。
它是读读不互斥,读写互斥,写写互斥。
/**
 * 读写锁
 * 读写互斥   写写互斥 读读不互斥
 *
 */
public class TestReentrantRWLock {
	
	private  ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
	private ReadLock rl = rw.readLock();
	private WriteLock wl = rw.writeLock();
	
	private int i=5;
	public void get(){
		try {
			rl.lock();
			Thread.sleep(2000);
			System.out.println("i的值是"+i);
			rl.unlock();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void add(){
		
		try {
			wl.lock();
			i++;
			System.out.println("i++后的值是:"+i);
			Thread.sleep(2000);
			wl.unlock();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public static void main(String[] args) {
		final TestReentrantRWLock trl = new TestReentrantRWLock();
	  
	  Thread t1 = new Thread(new Runnable() {
		
		@Override
		public void run() {
			// TODO Auto-generated method stub
			trl.get();
		}
	});
	  Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				trl.get();
			}
		});
	  Thread t3 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				trl.add();
			}
		});
	  //t1.start();
	  t3.start();
	  t2.start();
	}
}

如上所述:如果开启t1,t2线程,那么将不会阻塞,基本是同时运行的,如果开启t2,t3线程,就会发生阻塞。




猜你喜欢

转载自blog.csdn.net/strong_yu/article/details/72496419