Java并发编程之ReentrantLock学习


Lock和synchronized区别

1、synchronized是java提供的内置关键字,一旦使用线程就被锁住,直到线程执行完成或者处于wait状态下才会释放;如果线程处于阻塞了就会其他线程就会一直等待,如sleep;
2、Lock是一个接口,在JDK1.5提供,属于JUC下面常用的同步处理接口。在执行同步是都需要通过方法获取锁,执行完成之后必须释放锁;在获取锁时,通过tryLock方法可以判断锁是否被占用,从而避免线程因锁被占用而阻塞。

用synchronized实现简单的加锁和解锁

	private boolean isLock = false;
	
	private Thread currentThread = null;
	
	public synchronized void lock() throws InterruptedException{
		while (isLock) {
			wait();
		}
		isLock = true;
		currentThread = Thread.currentThread();
	}
	
	public synchronized void unlock() throws InterruptedException{
		if (this.currentThread != Thread.currentThread()) {
			throw new InterruptedException("当前线程没有加锁");
		} else {
			notify();
			isLock = false;
			currentThread = null;
		}
	}

	public void myLockTest() throws InterruptedException{
		lock();
		System.out.println(Thread.currentThread().getName() + " 获得了锁 ");
		try {
			for (int i = 0; i < 10; i++) {
				Thread.sleep(200);
				System.out.println("执行第 "+i+"次");
			}
		} finally {
			System.out.println(Thread.currentThread().getName() + " 释放了锁 ");
			unlock();
		}
	}

	public static void main(String[] args) {
		
		final MyLock lock = new MyLock();
		
		ExecutorService service = Executors.newFixedThreadPool(2);
		service.submit(new Runnable() {
			public void run() {
				try {
					lock.myLockTest();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
		service.submit(new Runnable() {
			public void run() {
				try {
					lock.myLockTest();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
		service.shutdown();
	}

执行结果
pool-1-thread-1 获得了锁 
执行第 0次
执行第 1次
执行第 2次
pool-1-thread-1 释放了锁 
pool-1-thread-2 获得了锁 
执行第 0次
执行第 1次
执行第 2次
pool-1-thread-2 释放了锁 


当然这只是一个实例,这种方式远没有lock接口直接和方便。

ReentrantLock

ReentrantLock是Lock的实现接口,用法比较简单;synchronized和ReentrantLock都是可重入锁,可重入锁获取是基于线程,而不是基于方法调用的分配。如下:
public void lock1(){
		locks.lock();
		System.out.println(Thread.currentThread().getName() + " 获得了锁 ");
		try {
			lock2();
		} finally {
			locks.unlock();
			System.out.println(Thread.currentThread().getName() + " 释放了锁 ");
		}
	}
	
	public void lock2(){
		locks.lock();
		System.out.println(Thread.currentThread().getName() + " 获得了锁 ");
		try {
			for (int i = 0; i < 3; i++) {
				System.out.println("执行第 "+i+"次");
			}
		} finally {
			locks.unlock();
			System.out.println(Thread.currentThread().getName() + " 释放了锁 ");
		}
	}


当调用lock1时,锁被获取,再调用lock2时,由于具备可重入性,不会出现等待或者死锁问题。

ReentrantLock 是一种独占锁,具有可重入性,独占锁是一种互斥锁,即当前线程占用锁之后,其他线程只能等待锁释放之后才能继续占有。它分为公平锁和非公平锁,公平锁是通过AQS 类中一个CLH对列(FIFO)来保证实现公平性,非公平锁是直接得到一个可获取状态的锁,不管是不是在队列头还是队列中。

ReentrantLock 关键UML图:




Lock:接口类,包括各种lock、unlock、ttyLock方法;
Sync:ReentrantLock中内部类,继承了AQS类;
NonfairSync:内部类,非公平锁实现,继承了Sync类;
FairSync:内部类,公平锁实现,继承Sync类。

NonfairSync 类解读:

lock方法实现:
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

直接通过CAS方法获取锁,成功则设置线程获取,失败则通过acquire方法获取,acquire是AQS类中的方法,后续会讲解。1表示的是锁的状态state。对于独占锁而言,如果所处于可获取状态,其状态为0,当锁初次被线程获取时状态变成1。

而tryAcquire方法是直接调用父类Sync中的nonfairTryAcquire方法,具体实现了非公平锁的获取

        /**
         * 非公平锁实现
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

非公平锁获取,先获取当前状态,如果为0表示处于可获取状态,通过CAS方法直接获取锁;如果当前线程就是锁的用户,则状态值增加。

FairSync类:
lock方法:
        final void lock() {
            acquire(1);
        }

调用AQS类中acquire方法,与非公平锁一致,tryAcquire是重写的AQS类中的tryAcquire方法。

读写锁
ReentrantReadWriteLock就是对ReadWriteLock进行实现的,即读写互斥,准确的是说,一旦发现写锁,那么当前锁就会被独占,而如果只有读锁。这可以多个线程同时操作。

其实现和ReetrantLock差不多,使用示例:

	private ReentrantReadWriteLock readLock = new ReentrantReadWriteLock();
	
	
	public void read(){
		readLock.readLock().lock();
		if (!readLock.isWriteLocked()) {
			System.out.println(Thread.currentThread().getName()+"当前为读锁----");
		}
		try {
			System.out.println(Thread.currentThread().getName()+"开始读操作.");
			for (int i=0; i<5; i++) {
				System.out.println(Thread.currentThread().getName()+"进行读操作...");
			}
			System.out.println(Thread.currentThread().getName()+"读操作完毕.");
		} finally {
			System.out.println(Thread.currentThread().getName()+"释放读锁-----");
			readLock.readLock().unlock();
		}
	}
	
	public void write(){
		readLock.writeLock().lock();
		if (readLock.isWriteLocked()) {
			System.out.println(Thread.currentThread().getName()+"当前为写锁----");
		}
		try {
			System.out.println(Thread.currentThread().getName()+"开始写操作.");
			for (int i=0; i<5; i++) {
				System.out.println(Thread.currentThread().getName()+"进行写操作...");
			}
			System.out.println(Thread.currentThread().getName()+"写操作完毕.");
		} finally {
			System.out.println(Thread.currentThread().getName()+"释放写锁-----");
			readLock.writeLock().unlock();
		}
	}
	
	class ReadThread implements Runnable{
		@Override
		public void run() {
			read();
		}
	}
	
	class writeThread implements Runnable{
		@Override
		public void run() {
			write();
		}
	}
	
	
	public static void main(String[] args) {
		
		ExecutorService service = Executors.newCachedThreadPool();
		LockTest lockTest = new LockTest();
		
		service.submit(lockTest.new ReadThread());
		service.submit(lockTest.new ReadThread());
		service.submit(lockTest.new writeThread());
		service.submit(lockTest.new writeThread());
		
		service.shutdown();
	}


执行结果:
pool-1-thread-1当前为读锁----
pool-1-thread-1开始读操作.
pool-1-thread-1进行读操作...
pool-1-thread-2当前为读锁----
pool-1-thread-1进行读操作...
pool-1-thread-2开始读操作.
pool-1-thread-1进行读操作...
pool-1-thread-2进行读操作...
pool-1-thread-1进行读操作...
pool-1-thread-2进行读操作...
pool-1-thread-2进行读操作...
pool-1-thread-1进行读操作...
pool-1-thread-2进行读操作...
pool-1-thread-1读操作完毕.
pool-1-thread-2进行读操作...
pool-1-thread-1释放读锁-----
pool-1-thread-2读操作完毕.
pool-1-thread-2释放读锁-----
pool-1-thread-2当前为写锁----
pool-1-thread-2开始写操作.
pool-1-thread-2进行写操作...
pool-1-thread-2进行写操作...
pool-1-thread-2进行写操作...
pool-1-thread-2进行写操作...
pool-1-thread-2进行写操作...
pool-1-thread-2写操作完毕.
pool-1-thread-2释放写锁-----
pool-1-thread-1当前为写锁----
pool-1-thread-1开始写操作.
pool-1-thread-1进行写操作...
pool-1-thread-1进行写操作...
pool-1-thread-1进行写操作...
pool-1-thread-1进行写操作...
pool-1-thread-1进行写操作...
pool-1-thread-1写操作完毕.
pool-1-thread-1释放写锁-----


可以看出,只有读锁时,可以多个线程共享,但是一点有写锁,那么就被独占了。

从读锁源码看到,读锁是采用的共享锁;而写锁是用的独占锁;
  public static class ReadLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -5992448646407690164L;
        private final Sync sync;

        /**
         */
        protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

        /**
         * 共享锁
         */
        public void lock() {
            sync.acquireShared(1);
        }
    //略
  }

写锁:
 public static class WriteLock implements Lock, java.io.Serializable {
        private static final long serialVersionUID = -4992448646407690164L;
        private final Sync sync;

        /**
         */
        protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }

        /**
         * 独占锁..
         */
        public void lock() {
            sync.acquire(1);
        }
  //略
}

猜你喜欢

转载自dongensi.iteye.com/blog/2312443