synchronized、Lock和ReadWriteLock快速认识

前言: 这里只是我自己对于synchronized、Lock和ReadWriteLock的一个简单认识,想要学习一样东西,先有一个大概的认识,以后再慢慢深入学习相关的知识。所以,这里就只是一个代码的展示和一些个人的理解。

线程安全和同步

为了提高CPU的利用效率,引入了多线程。但是为了线程的安全问题,又回到了同步(单线程一定是同步的)。

这里使用一个简单的示例代码,来展示synchronized、Lock和ReadWriteLock的作用。首先提供一个简单的模型类:

抽象投票类

说明:一个抽象的模型类,用于投票观察者获取票数。

package learn;

public abstract class Voter {
	public int vote;                        // 票数
	public abstract int getVote();          // 获取票数
	public abstract void setVote(int vote); // 设置票数
}

投票观察者类

说明:投票观察者类,多个线程同时观察同一个投票类,模拟多线程的同时读操作。

package learn;

public class VoteObserver implements Runnable {
	
	private Voter voter;
	
	public VoteObserver(String name, Voter voter) {
		Thread.currentThread().setName(name);
		this.voter = voter;
	}
	
	@Override
	public void run() {
		System.out.println("我是投票观察者:" + Thread.currentThread().getName() + " 现在有多少张票:" + voter.getVote());
	}
	
}

不考虑线程同步的投票类

说明:没有考虑同步措施的投票类,这里我并不会真的使用多线程去修改投票数,我这里是模拟同时去读取票数,所以getVote()方法会有一个耗时操作。

package learn;

public class UnsafeVoter extends Voter {
	
	private int vote;

	@Override
	public int getVote() {
		try {
			Thread.sleep(1000);    // 模拟耗时操作
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return vote;
	}

	@Override
	public void setVote(int vote) {
		this.vote = vote;
	}
}

同步测试类

说明:启动了一个具有十个固定线程的线程池,然后添加任务进行执行,然后对线程任务完成进行计时(这里不考虑主线程的耗时)。

package learn;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 同步测试
 * */
public class SyncTest {
	
	public static void main(String[] args) {
		unThreadSafe();
	}
	
	// 非线程安全方式,多个线程并发执行,速度最快,但是有线程安全问题
	static void unThreadSafe() {
		ExecutorService service = Executors.newFixedThreadPool(10);
		Voter voter = new UnsafeVoter();
		voter.setVote(100);
		
		// 计时器
		long start = System.currentTimeMillis();
		
		for (int i = 0; i < 10; i++) {
			service.submit(new VoteObserver("observer_"+i, voter));
		}
		
		service.shutdown();  // 关闭线程池
		
		while (!service.isTerminated()) {}  // 利用CPU空转计时,效率不高,但是简单易用。
		System.out.println("总耗时:" + (System.currentTimeMillis()-start) + " ms");
	}
}

测试结果

说明:由于没有进行同步操作,所以多个线程是并发获取票数的,即几乎是同时完成了任务,总耗时接近1000ms。因此使用多线程以后,多线程的代码变成了并行执行。
在这里插入图片描述

使用synchronized关键字的线程安全投票类

说明:使用synchronized进行同步操作,保证线程安全。上面的操作速度虽然快,但是无法保证线程安全,所以需要使用同步来保证,这里使用Java的synchronized关键字来完成。

package learn;

public class SafeVoterSync extends Voter {

	private int vote;
	
	@Override
	synchronized public int getVote() {
		try {
			Thread.sleep(1000);    // 模拟耗时操作
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return vote;
	}

	@Override
	public void setVote(int vote) {
		this.vote = vote;
	}

}

使用synchronized关键字的测试方法

// 采用synchronized关键字进行同步的线程安全方式,多个线程同步执行,效率非常低下!
static void threadSafeSynchronized() {
	ExecutorService service = Executors.newFixedThreadPool(10);
	Voter voter = new SafeVoterSync();
	voter.setVote(100);
	
	// 计时器
	long start = System.currentTimeMillis();
	
	for (int i = 0; i < 10; i++) {
		service.submit(new VoteObserver("observer_"+i, voter));
	}
	
	service.shutdown();  // 关闭线程池
	
	while (!service.isTerminated()) {}  // 利用CPU空转计时,效率不高,但是简单易用。
	System.out.println("总耗时:" + (System.currentTimeMillis()-start) + " ms");
}

测试结果

说明:实际观察的话,可以发现每条记录是一次间隔接近1000ms的时间打印的,实际总耗时接近10*1000ms,因此同步以后,多线程的代码变成了串行执行。
在这里插入图片描述

使用Lock类的线程安全投票类

package learn;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SafeVoterLock extends Voter {
	
	private static Lock lock = new ReentrantLock();

	private int vote;
	
	@Override
	public int getVote() {
		lock.lock();
		try {
			Thread.sleep(1000);    // 模拟耗时操作
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
		return vote;
	}

	@Override
	public void setVote(int vote) {
		this.vote = vote;
	}
}

使用Lock类的测试方法

// 采用Lock关键字进行同步的线程安全方式,多个线程同步执行,效率非常低下!
static void threadSafeLock() {
	ExecutorService service = Executors.newFixedThreadPool(10);
	Voter voter = new SafeVoterLock();
	voter.setVote(100);
	
	// 计时器
	long start = System.currentTimeMillis();
	
	for (int i = 0; i < 10; i++) {
		service.submit(new VoteObserver("observer_"+i, voter));
	}
	
	service.shutdown();  // 关闭线程池
	
	while (!service.isTerminated()) {}  // 利用CPU空转计时,效率不高,但是简单易用。
	System.out.println("总耗时:" + (System.currentTimeMillis()-start) + " ms");
}

测试结果

说明:效果和synchronized是差不多的,听说java对synchronized进行了优化,现在性能和Lock也差不多了,但是我对这个的认识还比较浅显。
在这里插入图片描述

使用ReadWriteLock的线程安全投票类

说明:前面我们看到了,我们只是获取投票数这个读取的过程,如果加了多线程同步以后,代码的效率下降的非常严重,这个和日常生活也有点相似,任何事情一旦进入了排队等待,就感觉是一种煎熬——排队买票、排队领书、排队买奶茶等等。
但是,如果不会对状态进行修改,那就没必要进行同步。

package learn;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class SafeVoterRWLock extends Voter {

	/**
	 * 这里可以切换读写锁来测试,只使用读锁,我们就回到了那个并发执行的春天了,
	 * 使用写锁的话就和前面的同步没有区别了。所以,这也引出了它的应用场景,
	 * 即读多于写的场景下,效率很高,最坏情况下,基本同普通的Lock和synchronized一样。
	 * */
	private static ReadWriteLock rwLock = new ReentrantReadWriteLock();
	private static Lock readLock = rwLock.readLock();    // 读锁
	private static Lock writeLock = rwLock.writeLock();  // 写锁 
	
	
	private int vote;
	
	@Override
	public int getVote() {
		readLock.lock();  // 加锁放在try块外部
		try {
			Thread.sleep(1000);    // 模拟耗时操作
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			readLock.unlock();        // 注意上下两个都要修改为一样的,即加锁必须释放,否则就死锁了!
		}
		return vote;
	}

	@Override
	public void setVote(int vote) {
		this.vote = vote;
	}

}

使用ReadWriteLock的测试方法

// 采用ReadWriteLock关键字进行同步的线程安全方式,多个读线程并发执行,效率非常高!
static void threadSafeRWLock() {
	ExecutorService service = Executors.newFixedThreadPool(10);
	Voter voter = new SafeVoterRWLock();
	voter.setVote(100);
	
	// 计时器
	long start = System.currentTimeMillis();
	
	for (int i = 0; i < 10; i++) {
		service.submit(new VoteObserver("observer_"+i, voter));
	}
	
	service.shutdown();  // 关闭线程池
	
	while (!service.isTerminated()) {}  // 利用CPU空转计时,效率不高,但是简单易用。 注意,不能使用isShutDown方法。
	System.out.println("总耗时:" + (System.currentTimeMillis()-start) + " ms");
}

/**
 * synchronized代码块中的代码才是耗时的,其它代码都是并行执行的!
 * 
 * 读写锁:
 * 读读共享
 * 读写互斥
 * 写读互斥
 * 写写互斥
 * */

测试结果
多个读锁之间是共享的,即会并发执行。但是读锁和写锁、写锁和写锁还是需要互斥进行的,即会串行执行(这里只有读锁的并发执行)。
在这里插入图片描述

说明

这里只是对synchronized、Lock和ReadWirteLock的进行一个使用,先掌握一个简单的用法,可能这里的场景不是很合适。但是,这反正也只是学习的第一步,先慢慢来吧。先写一个例子,记录一下自己学习的过程,多线程确实是一个难点,需要多想、多练、多看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值