欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java并发编程之ReentrantLock学习

程序员文章站 2022-07-15 13:55:49
...

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图:

Java并发编程之ReentrantLock学习
            
    
    博客分类: 并发编程java基础 javalock锁读写锁源码 


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);
        }
  //略
}
  • Java并发编程之ReentrantLock学习
            
    
    博客分类: 并发编程java基础 javalock锁读写锁源码 
  • 大小: 53.9 KB