java并发之----synchronized与ReenTrantLock

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

synchronized

synchronized关键字最主要几种使用方式:
(1)同步一个代码块:
只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。

public void func() {
    synchronized (this) {
        // ...
    }
}

对于以下代码, 执行了两个线程,由于调用的是同一个对象的同步代码块,因此这两个线程会进行同步,当一个线程进入同步语句块时,另一个线程就必须等待。

public class SynchronizedExample implements Runnable {
	@Override
	public void run() {
		synchronized (this) {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        }
	}	
	public static void main(String[] args){
		SynchronizedExample e1 = new SynchronizedExample();
		Thread t1 = new Thread(e1);
		Thread t2 = new Thread(e1);
		t1.start();
		t2.start();
	}	
}

在这里插入图片描述
对于以下代码,两个线程调用了不同对象的同步代码块,因此这两个线程就不需要同步。从输出结果可以看出,两个线程交叉执行(运行结果不一定与截图完全一样)。

public class SynchronizedExample implements Runnable {
	@Override
	public void run() {
		synchronized (this) {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        }
	}	
	public static void main(String[] args){
		SynchronizedExample e1 = new SynchronizedExample();
		SynchronizedExample e2 = new SynchronizedExample();
		Thread t1 = new Thread(e1);
		Thread t2 = new Thread(e2);
		t1.start();
		t2.start();
	}	
}

在这里插入图片描述
(2)同步一个方法:
它和同步代码块一样,作用于同一个对象。

public synchronized void func () {
    // ...
}

(3)同步一个类:
作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步

public void func() {
    synchronized (SynchronizedExample.class) {
        // ...
    }
}
public class SynchronizedExample implements Runnable {
	@Override
	public void run() {
		synchronized (SynchronizedExample.class) {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        }
	}
	public static void main(String[] args){
		SynchronizedExample e1 = new SynchronizedExample();
		SynchronizedExample e2 = new SynchronizedExample();
		Thread t1 = new Thread(e1);
		Thread t2 = new Thread(e2);
		t1.start();
		t2.start();
	}
}

在这里插入图片描述
(4)同步一个静态方法
相当于给当前类对象加锁,作用于整个类

public synchronized static void fun() {
    // ...
}

ReenTrantLock

在java多线程中,可以使用synchronized来实现线程之间同步互斥,但在jdk1.5中新增加了ReenTrantLock类也能达到同样效果,并且在扩展功能上也更加强大。

public class MyService {
	private Lock lock = new ReentrantLock();
	public void testMethod(){
		lock.lock();
		for(int i=1; i<4; i++){
			System.out.println("ThreadName="+Thread.currentThread().getName()+" "+i);
		}
		lock.unlock();
	}
}

public class TestReentrantLock extends Thread{
	private MyService service;
	
	public TestReentrantLock(MyService service) {
		this.service = service;
	}
	
	@Override
	public void run(){
		service.testMethod();
	}
	
	public static void main(String[] args){
		MyService service = new MyService();
		TestReentrantLock thread1 = new TestReentrantLock(service);
		TestReentrantLock thread2 = new TestReentrantLock(service);
		TestReentrantLock thread3 = new TestReentrantLock(service);
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

在这里插入图片描述
当前线程依次打印1,2,3,线程之间打印的顺序是随机的

新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,所以synchronized 与 ReentrantLock 大致相同。这里介绍一下ReenTrantLock的一些高级功能,也就是synchronized 不能实现的功能
(1)等待可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
ReentrantLock 可中断,而 synchronized 不行。

(2)公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。所谓的公平锁就是先等待的线程先获得锁。ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。 ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。

(3)锁绑定多个条件
synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。
看这个例子:使用Condition实现等待/通知

public class MyService {
	private Lock lock = new ReentrantLock();
	public Condition condition = lock.newCondition();
	public void await(){
		try{
			lock.lock();
			System.out.println("await时间为"+System.currentTimeMillis());
			condition.await();
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public void signal(){
		try{
			lock.lock();
			System.out.println("signal时间为"+System.currentTimeMillis());
			condition.signal();
		}finally{
			lock.unlock();
		}
	}
}
public class TestCondition extends Thread{
	private MyService service;
	public TestCondition(MyService service){
		this.service = service;
	}
	@Override
	public void run(){
		service.await();
	}
	
	public static void main(String[] args) throws InterruptedException{
		MyService service = new MyService();
		TestCondition thread = new TestCondition(service);
		thread.start();
		Thread.sleep(3000);
		service.signal();
	}
}

在这里插入图片描述
Object类中的wait()方法相当于Condition类中的await()方法
wait(long timeout)方法相当于await(long time,TimeUnit unit)
notify()方法相当于signal()方法
notifyAll()方法相当于signalAll()方法

Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition接口默认提供的。
而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。

注意

除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

synchronized与ReenTrantLock的异同

(1)synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。
(2)ReentrantLock 可中断,而 synchronized 不行。
(3)synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。
(4)一个 ReentrantLock 可以同时绑定多个 Condition 对象。synchronized 则不行
(5)synchronized 和ReentrantLock 都是可重入锁

猜你喜欢

转载自blog.csdn.net/Felix_ar/article/details/83904812