synchronized和ReentrantLock的区别小结

1.相比synchronized,ReentrantLock(重入锁)增加了一些高级功能

等待可中断——对于synchronized,如果一个线程正在等待锁,那么结果只有两种情况,要么获得这把锁继续执行 ,要么就保持等待。而使用ReentrantLock,如果一个线程正在等待锁,那么它依然可以收到通知,被告知无需再等待,可以停止工作了。

1)lockInterruptiby()的使用

线程t1一直占用者锁 不释放,此时t2一直在等待着锁,通过lockInterruptibly()方法可以通知线程t2停止等待,而使用synchronized方式,只能继续等待。注意:这里说的是一个线程正在等待锁即没有获得锁的情况,而不是说获得锁后执行出行异常,这两者不一样,因为synchronized同步的代码出现异常,也会释放锁,例如在synchronized同步代码中进入sleep(),此时通过中断,也会释放锁。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;

public class ReentrantLock4 {
		
	public static void main(String[] args) {
		ReentrantLock lock = new ReentrantLock();
		
		
		Thread t1 = new Thread(()->{
			try {
				lock.lock();
				System.out.println("t1 start");
				TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
				System.out.println("t1 end");
			} catch (InterruptedException e) {
				System.out.println("interrupted!");
			} finally {
				lock.unlock();
			}
		});
		t1.start();
		
		Thread t2 = new Thread(()->{
			try {
				lock.lockInterruptibly(); //可以对interrupt()方法做出响应
				System.out.println("t2 start");
				TimeUnit.SECONDS.sleep(5);
				System.out.println("t2 end");
			} catch (InterruptedException e) {
				System.out.println("interrupted!");    //在这里处理退出中断后的后续动作
			} finally {
				if(lock.isHeldByCurrentThread())
				{
					lock.unlock();
				}
			}
		});
		t2.start();
		
		try {
			TimeUnit.SECONDS.sleep(1);		
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.interrupt();      //打断线程2的等待
		
	}
}
t1 start
interrupted!

2)使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行,可以根据tryLock的返回值来判定是否锁定

也可以指定tryLock的时间,犹如tryLock(time)抛出异常,所以要注意unlock的处理,必须方法finally中。

代码示例:

class MyService {

	public ReentrantLock lock = new ReentrantLock();

	public void waitMethod() {
		if (lock.tryLock()) {			//如果获取到了锁,打印"获得锁",否则打印"没有获得锁"
			System.out.println(Thread.currentThread().getName() + "获得锁");
		} else {
			System.out.println(Thread.currentThread().getName() + "没有获得锁");
		}
	}
}

public class HighConcurrency {

	public static void main(String[] args) throws InterruptedException {
		final MyService service = new MyService();

		Runnable runnableRef = new Runnable() {
			@Override
			public void run() {
				service.waitMethod();
			}
		};

		Thread threadA = new Thread(runnableRef);
		threadA.setName("A");
		threadA.start();
		Thread threadB = new Thread(runnableRef);
		threadB.setName("B");
		threadB.start();
	}
}
B没有获得锁
A获得锁

tryLock(time)示例:

class MyService {

	public ReentrantLock lock = new ReentrantLock();

	public void waitMethod() {
		try {
			if (lock.tryLock(3, TimeUnit.SECONDS)) {	//等待3s后,如果没有获取锁,则执行else里语句
				System.out.println("      " + Thread.currentThread().getName()
						+ "获得锁的时间:" + System.currentTimeMillis());
				Thread.sleep(10000);
			} else {
				System.out.println("      " + Thread.currentThread().getName()
						+ "没有获得锁");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			if (lock.isHeldByCurrentThread()) {
				lock.unlock();
			}
		}
	}
}

public class HighConcurrency {

	public static void main(String[] args) throws InterruptedException {
		final MyService service = new MyService();

		Runnable runnableRef = new Runnable() {
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName()
						+ "调用waitMethod时间:" + System.currentTimeMillis());
				service.waitMethod();
			}
		};

		Thread threadA = new Thread(runnableRef);
		threadA.setName("A");
		threadA.start();
		Thread threadB = new Thread(runnableRef);
		threadB.setName("B");
		threadB.start();
	}
}
A调用waitMethod时间:1526127191925
B调用waitMethod时间:1526127191925
      A获得锁的时间:1526127191926
      B没有获得锁
说明A先获取锁,B等待3S后没有获取锁,就进入else语句执行,不再等待。

2.可实现公平锁:公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁,而非公平锁不保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁,synchronized中的锁是非公平的,ReenrantLock默认情况下也是非公平的,但是可以在构造函数中设置为公平锁ReentrantLock(boolean fair) 。

公平锁示例:

public class ReentrantLock5 extends Thread {
		
	private static ReentrantLock lock=new ReentrantLock(true); //参数为true表示为公平锁
    public void run() {
        for(int i=0; i<100; i++) {
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName()+"获得锁");
            }finally{
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) {
        ReentrantLock5 rl=new ReentrantLock5();
        Thread th1=new Thread(rl);
        Thread th2=new Thread(rl);
        th1.start();
        th2.start();
    }
}
输出:
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
Thread-2获得锁
Thread-1获得锁
如果将构造函数中的true去掉,此时成为非公平锁后的打印为:
Thread-1获得锁
Thread-1获得锁
Thread-1获得锁
Thread-1获得锁

3.锁可以绑定多个条件

一个ReentrantLock对象可以同时绑定多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知。

自己对condtion的理解:condition使得调用该方法的线程进入等待队列,但是唤醒的时候唤醒的是对方的等待队列。用一个例子来说明,有一个容器,有多个生产者和多个消费者,希望当容器满的时候,能够只让生产的线程等待,只唤醒消费的线程,同样当容器空的时候,让消费的线程等待,只唤醒生产的线程

代码实现:

public class MyContainer2 {
	final private LinkedList lists = new LinkedList<>();
	final private int MAX = 10; //最多10个元素
	private int count = 0;
	
	private Lock lock = new ReentrantLock();
	private Condition producer = lock.newCondition();
	private Condition consumer = lock.newCondition();
	
	public void put(T t) {
		try {
			lock.lock();
			while(lists.size() == MAX) { 
				producer.await();	//当消费者线程们执行到这里的时候等待
			}
			
			lists.add(t);
			++count;
			System.out.println("里面有个数:"+count);
			consumer.signalAll(); //通知消费者线程进行消费
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public T get() {
		T t = null;
		try {
			lock.lock();
			while(lists.size() == 0) {
				consumer.await();	//当生产者线程执行到这里的等待
			}
			t = lists.removeFirst();
			count --;
			System.out.println("里面有个数:"+count);
			producer.signalAll(); //通知生产者的线程们进行生产
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return t;
	}
	
	public static void main(String[] args) {
		MyContainer2 c = new MyContainer2<>();
		//启动消费者线程
		for(int i=0; i<10; i++) {
			new Thread(()->{
				for(int j=0; j<5; j++) 
					c.get();
					//System.out.println(c.get());
			}, "c" + i).start();
		}
		
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//启动生产者线程
		for(int i=0; i<2; i++) {
			new Thread(()->{
				for(int j=0; j<25; j++) 
					c.put(Thread.currentThread().getName() + " " + j);
			}, "p" + i).start();
		}
	}
}

说明:该代码有一个注意点,在put方法中判断容器是否满的时候使用的是while(lists.size()==MAX),这里能否改成if(lists.size==MAX)呢?这里假设这样一种情形,此时容器已经满了,两个消费者线程都进入await()等待,此时一个消费者线程消费了一个商品,同时唤醒了这两个生产者线程c1和c2,此时被唤醒c1先抢到锁,直接执行wait()后面的语句,生产了一个商品,然后c2抢到锁,此时c2直接从wait()后面开始执行,并不会判断容器是否已经满了,所以导致容器装不下,导致错误。

4.线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态称为阻塞状态。但是阻塞在Lock接口的线程状态却是等待状态,因为Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。(Java并发编程的艺术90页)

https://blog.csdn.net/chenchaofuck1/article/details/51045134

https://blog.csdn.net/zheng548/article/details/54426947

你可能感兴趣的:(Java并发)