首页 > 编程语言 >2、【java线程及线程池系列】synchronized、ReentrantLock和ReentrantReadWriteLock介绍及示例

2、【java线程及线程池系列】synchronized、ReentrantLock和ReentrantReadWriteLock介绍及示例

时间:2023-07-31 16:32:14浏览次数:32  
标签:opertion java 示例 线程 user new public pool


java线程及线程池 系列文章

1、【java线程及线程池系列】java线程及线程池概念详解

2、【java线程及线程池系列】synchronized、ReentrantLock和ReentrantReadWriteLock介绍及示例

3、【java线程及线程池系列】线程池ThreadPoolExecutor的类结构、使用方式示例、线程池数量配置原则和线程池使用注意事项



文章目录

  • java线程及线程池 系列文章
  • 一、结论
  • 二、synchronized关键字
  • 1、实例方法
  • 2、静态方法
  • 3、实例方法中的同步块
  • 4、静态方法中的同步块
  • 三、锁
  • 1、ReentrantLock、ReentrantReadWriteLock简介
  • 1)、ReentrantLock
  • 2)、ReentrantReadWriteLock
  • 2、简单的示例
  • 1)、ReentrantLock实现示例
  • 2)、ReentrantReadWriteLock实现示例
  • 四、Condition



本文简单的介绍了synchronized、ReentrantLock和ReentrantReadWriteLock介绍及示例。

一、结论

线程间的同步有三种方式,即synchronized、reentrantlock和reentrantreadwritedlock。其中synchroized是关键字,reentrantlock是接口lock的实现,reentrantreadwritedlock是readwritedlock的实现。
1、reentrantlock和synchroiized实现的功能是一样的,但性能更好。同时也添加了类似锁投票、定时锁等候和可中断等候的一些特性
2、和ReentrantLock定义的互斥锁不同的是,ReentrantReadWriteLock定义了两把锁,即读锁和写锁。读锁可以共享,即同一个资源可以让多个线程获取读锁。这个和ReentrantLock(或者sychronized)相比大大提高了读的性能

二、synchronized关键字

对于同步,在具体的Java代码中需要完成一下两个操作:
1、把竞争访问的资源标识为private;
2、同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。
当然这不是唯一控制并发安全的途径。
synchronized关键字使用说明
synchronized只能标记非抽象的方法,不能标识成员变量。

有四种不同的同步块:

1、实例方法

/**
	 * 所有的对象都拥有该同步方法,如果没有发生并行操作的时候,各自操作自己的,如果有并行操作的时候一个一个的执行
	 * Java实例方法同步是同步在拥有该方法的对象上。 这样,每个实例其方法同步都同步在不同的对象上,即该方法所属的实例。
	 * 只有一个线程能够在实例方法同步块中运行。 如果有多个实例存在,那么一个线程一次可以在一个实例同步块中执行操作。 一个实例一个线程。
	 * 同步方法是同步在该方法的拥有者实例(对象)上。每个实例都有自己的同步方法,仅有一个实例能运行同步方法。 
	 * A synchronized instance method in Java is synchronized on the instance (object) owning
	 * the method. Thus, each instance has its synchronized methods synchronized
	 * on a different object: the owning instance. Only one thread can execute
	 * inside a synchronized instance method. If more than one instance exist,
	 * then one thread at a time can execute inside a synchronized instance
	 * method per instance. One thread per instance.
	 * 
	 * @param value
	 */
	public synchronized void add(int value) {
		this.count += value;
	}

2、静态方法

/**
	 * 所有的对象只拥有一个同步方法,执行的时候如果有并行就一个一个执行。不允许同时被多个线程访问。 Synchronized static
	 * methods are synchronized on the class object of the class the
	 * synchronized static method belongs to. Since only one class object exists
	 * in the Java VM per class, only one thread can execute inside a static
	 * synchronized method in the same class.
	 * 
	 * If the static synchronized methods are located in different classes, then
	 * one thread can execute inside the static synchronized methods of each
	 * class. One thread per class regardless of which static synchronized
	 * method it calls.
	 * 
	 * @param value
	 */
	public static synchronized void add2(int value) {
		count2 += value;
	}

3、实例方法中的同步块

/**
	 * 该代码在执行时和同步方法一样。功能相同。 使用了“this”,即为调用add方法的实例本身。
	 * 
	 * @param value
	 */
	public void add3(int value) {

		synchronized (this) {
			this.count += value;
		}
	}

4、静态方法中的同步块

/**
	 * 和静态同步方法一样,
	 * @param value
	 */
	public static void log2(int value) {
		synchronized (ThreadSyn.class) {
			count2 += value;
		}
	}

三、锁

在Java5中,专门提供了锁对象,利用锁可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制,这些内容主要集中在java.util.concurrent.locks包下面,里面有三个重要的接口Condition、Lock、ReadWriteLock。

2、【java线程及线程池系列】synchronized、ReentrantLock和ReentrantReadWriteLock介绍及示例_并发控制

1、ReentrantLock、ReentrantReadWriteLock简介

  • 可重入锁 :可重入锁的概念是自己可以再次获取自己的内部锁。举个例子,比如一条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的(如果不可重入的锁的话,此刻会造成死锁)。可重入锁是一种递归无阻塞的同步机制。
  • 读写锁 :读写锁拆成读锁和写锁来理解。读锁可以共享,多个线程可以同时拥有读锁,但是写锁却只能只有一个线程拥有,而且获取写锁的时候其他线程都已经释放了读锁,而且该线程获取写锁之后,其他线程不能再获取读锁。写锁是排他锁,读锁是共享锁。
  • 公平和非公平 :公平表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO顺序。而非公平就是一种获取锁的抢占机制,和公平相对就是先来不一定先得,这个方式可能造成某些线程饥饿(一直拿不到锁)。
  • ReentrantLock 和 ReentrantReadWriteLock是拥有者两个不同类继承结构的体系,两者并无关联。

1)、ReentrantLock

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。

Lock lock = new ReentrantLock();
lock.lock();
try { 
  // update object state
}
finally {
  lock.unlock(); 
}

可以看到 Lock 和 synchronized 有一点明显的区别 —— lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!而使用synchronized,JVM 将确保锁会获得自动释放。

2)、ReentrantReadWriteLock

类ReentrantReadWriteLock实现了ReadWirteLock接口。和ReentrantLock定义的互斥锁不同的是,ReentrantReadWriteLock定义了两把锁即读锁和写锁。读锁可以共享,即同一个资源可以让多个线程获取读锁。这个和ReentrantLock(或者sychronized)相比大大提高了读的性能。在需要对资源进行写入的时候在会加写锁达到互斥的目的。

2、简单的示例

不同用户可以进行不同的操作(存取款)

1)、ReentrantLock实现示例

public class LockTest {

	public static void main(String[] args) {
		// 创建一个线程池
		ExecutorService pool = Executors.newCachedThreadPool();
		// 创建一些并发访问用户,一个信用卡,存的存,取的取
		User u1 = new User("zhangsan");
		Opertion opertion = new Opertion(u1, "取款", -4000);
		pool.execute(opertion);
		opertion = new Opertion(u1, "取款", -4000);
		pool.execute(opertion);
		opertion = new Opertion(u1, "取款", -1000);
		pool.execute(opertion);
		opertion = new Opertion(u1, "存款", 4000);
		pool.execute(opertion);
		opertion = new Opertion(u1, "存款", 4000);
		pool.execute(opertion);
		opertion = new Opertion(u1, "取款", -4000);
		pool.execute(opertion);

		User u2 = new User("李四");
		opertion = new Opertion(u2, "存款", 4000);
		pool.execute(opertion);
		opertion = new Opertion(u2, "存款", 4000);
		pool.execute(opertion);
		// 关闭线程池
		pool.shutdown();
	}
}

class Opertion implements Runnable {
	private User user;
	private String opertion;
	private int cash;
	Lock lock = new ReentrantLock();

	public void run() {
		try {
			// 获取锁
			lock.lock();
			// 执行现金业务
			System.out.println(user.getAccount() + " 当前余额:" + user.getBalance());
			user.setBalance(this.getCash() + user.getBalance());
			System.out.println(user.getAccount() + " 当前余额:" + user.getBalance() + ",本次操作[" + this.getOpertion() + ",金额"
					+ this.getCash() + "]");
		} finally {
			// 释放锁,否则别的线程没有机会执行了
			lock.unlock();
		}
	}

	public Opertion() {
	}

	public Opertion(User user, String opertion, int cash) {
		this.user = user;
		this.opertion = opertion;
		this.cash = cash;
	}

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	public String getOpertion() {
		return opertion;
	}

	public void setOpertion(String opertion) {
		this.opertion = opertion;
	}

	public int getCash() {
		return cash;
	}

	public void setCash(int cash) {
		this.cash = cash;
	}
}

@Data
class User {
	private String account; // 用户名
	private int balance; // 账户余额
	private int cash; // 操作的金额
	User() {
	}
	User(String account) {
		this.account = account;
	}

}

2)、ReentrantReadWriteLock实现示例

public class LockTest {
	public static void main(String[] args) {
		// 创建一个线程池
		ExecutorService pool = Executors.newCachedThreadPool();
		User u1 = new User("zhangsan");
		Opertion opertion = new Opertion(u1, 1, -4000);
		pool.execute(opertion);
		opertion = new Opertion(u1, 1, -4000);
		pool.execute(opertion);
		opertion = new Opertion(u1, 1, -1000);
		pool.execute(opertion);
		opertion = new Opertion(u1, 2, 4000);
		pool.execute(opertion);
		
		opertion = new Opertion(u1, 0);
		pool.execute(opertion);
		
		opertion = new Opertion(u1, 2, 4000);
		pool.execute(opertion);
		
		opertion = new Opertion(u1, 0);
		pool.execute(opertion);
		
		opertion = new Opertion(u1, 1, -4000);
		pool.execute(opertion);

		User u2 = new User("李四");
		opertion = new Opertion(u2, 2, 4000);
		pool.execute(opertion);
		opertion = new Opertion(u2, 0);
		pool.execute(opertion);
		opertion = new Opertion(u2, 2, 4000);
		pool.execute(opertion);
		// 关闭线程池
		pool.shutdown();

		System.out.println(u1.getAccount() + " 余额:" + u1.getBalance());
		System.out.println(u2.getAccount() + " 余额:" + u2.getBalance());
	}
}

class Opertion implements Runnable {
	private User user;
	private int opertion;
	private int cash;
	private boolean isRead;
	ReadWriteLock rwl = new ReentrantReadWriteLock();
	private ReadLock readLock = (ReadLock) rwl.readLock();
	private WriteLock writeLock = (WriteLock) rwl.writeLock();

	public boolean isRead() {
		return isRead;
	}
	public void setRead(boolean isRead) {
		this.isRead = isRead;
	}
	public void run() {
		String action = "查询";
		if (opertion == 0) {
			setRead(true);
		} else if (opertion == 1) {
			action = "取款";
		} else if (opertion == 2) {
			action = "存款";
		}
		if (isRead()) {
			try {
				readLock.lock();
				System.out.println(user.getAccount() + " 当前余额:" + user.getBalance() + " 本次操作:" + action);
			} finally {
				readLock.unlock();
			}
		} else {
			try {
				// 获取锁
				writeLock.lock();
				// 执行现金业务
				System.out.println(user.getAccount() + " 当前余额:" + user.getBalance());
				user.setBalance(this.getCash() + user.getBalance());
				System.out.println(user.getAccount() + " 当前余额:" + user.getBalance() + ",本次操作[" + action + ",金额"
						+ this.getCash() + "]");
			} finally {
				writeLock.unlock();
			}
		}
	}
	public Opertion() {
	}
	public Opertion(User user, int opertion, int cash) {
		this.user = user;
		this.opertion = opertion;
		this.cash = cash;
	}
	public Opertion(User user, int opertion) {
		this.user = user;
		this.opertion = opertion;
	}
	public User getUser() {
		return user;
	}
	public void setUser(User user) {
		this.user = user;
	}
	public int getOpertion() {
		return opertion;
	}
	public void setOpertion(int opertion) {
		this.opertion = opertion;
	}
	public int getCash() {
		return cash;
	}
	public void setCash(int cash) {
		this.cash = cash;
	}
}
@Data
class User {
	private String account; // 用户名
	private int balance; // 账户余额
	private int cash; // 操作的金额
	User() {
	}
	User(String account) {
		this.account = account;
	}
}

四、Condition

实现了java.util.concurrent.locks.Condition接口,条件变量的实例化是通过一个Lock对象上调用newCondition()方法来获取的,这样,条件就和一个锁对象绑定起来了。因此,Java中的条件变量只能和锁配合使用,来控制并发程序访问竞争资源的安全。
条件变量的出现是为了更精细控制线程等待与唤醒,在Java5之前,线程的等待与唤醒依靠的是Object对象的wait()和notify()/notifyAll()方法,这样的处理不够精细。
而在Java5中,一个锁可以有多个条件,每个条件上可以有多个线程等待,通过调用await()方法,可以让线程在该条件下等待。当调用signalAll()方法,又可以唤醒该条件下的等待的线程。有关Condition接口的API可以具体参考JavaAPI文档。
条件变量比较抽象,原因是他不是自然语言中的条件概念,而是程序控制的一种手段。

/**
 * 测试condition的常用功能
 * @author alanchan
 */
public class ConditionTest {
	public static void main(String[] args) {
		// 创建并发访问的账户
		Account myCount = new Account(10000);
		// 创建一个线程池
		ExecutorService pool = Executors.newFixedThreadPool(2);
		Runnable t1 = new SaveThread("张三", myCount, 2000);
		Runnable t2 = new SaveThread("李四", myCount, 3600);
		Runnable t3 = new DrawThread("王五", myCount, 2700);
		Runnable t4 = new SaveThread("老张", myCount, 600);
		Runnable t5 = new DrawThread("老牛", myCount, 1300);
		Runnable t6 = new DrawThread("胖子", myCount, 800);
		// 执行各个线程
		pool.execute(t1);
		pool.execute(t2);
		pool.execute(t3);
		pool.execute(t4);
		pool.execute(t5);
		pool.execute(t6);
		// 关闭线程池
		pool.shutdown();
	}
}

/**
 * 存款线程类
 */
class SaveThread implements Runnable {
	private String name; // 操作人
	private Account myCount; // 账户
	private int x; // 存款金额
	SaveThread(String name, Account myCount, int x) {
		this.name = name;
		this.myCount = myCount;
		this.x = x;
	}
	public void run() {
		myCount.saving(x, name);
	}
}

/**
 * 取款线程类
 */
class DrawThread implements Runnable {
	private String name; // 操作人
	private Account myCount; // 账户
	private int x; // 存款金额
	DrawThread(String name, Account myCount, int x) {
		this.name = name;
		this.myCount = myCount;
		this.x = x;
	}
	public void run() {
		myCount.drawing(x, name);
	}
}

class Account {
	private int cash; // 账户余额
	private Lock lock = new ReentrantLock(); // 账户锁
	private Condition save = lock.newCondition(); // 存款条件
	private Condition draw = lock.newCondition(); // 取款条件
	Account(int cash) {
		this.cash = cash;
	}
	/**
	 * 存款
	 * @param x
	 *            操作金额
	 * @param name
	 *            操作人
	 */
	public void saving(int x, String name) {
		try {
			lock.lock(); // 获取锁
			if (x > 0) {
				cash += x; // 存款
				System.out.println(name + "存款" + x + ",当前余额为" + cash);
			}
			draw.signalAll(); // 唤醒所有等待线程。
		} finally {
			lock.unlock(); // 释放锁
		}
	}
	/**
	 * 取款
	 * @param x
	 *            操作金额
	 * @param name
	 *            操作人
	 */
	public void drawing(int x, String name) {
		lock.lock(); // 获取锁
		try {
			if (cash - x < 0) {
				draw.await(); // 阻塞取款操作
			} else {
				cash -= x; // 取款
				System.out.println(name + "取款" + x + ",当前余额为" + cash);
			}
			save.signalAll(); // 唤醒所有存款操作
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock(); // 释放锁
		}
	}
}

以上,简单的介绍了synchronized、ReentrantLock和ReentrantReadWriteLock介绍及示例。


标签:opertion,java,示例,线程,user,new,public,pool
From: https://blog.51cto.com/alanchan2win/6909715

相关文章

  • 2、【java程序运行监控byteman】使用示例(运行中方法耗时监控、javaagent监控、jvm监控
    文章目录一、统计方法耗时(程序运行中)1、创建示例类2、建立监控1)、获取运行程序的进程号2)、建立监控3、编制脚本4、检测脚本5、提交脚本6、验证7、卸载脚本二、其他示例1、javaagent示例(程序未运行)1)、创建示例类2)、编译及测试3)、编制脚本4)、运行脚本5)、测试2、监控jvm的类1)、创建......
  • javascript实现浏览器端大文件分块上传
    ​ 以ASP.NETCoreWebAPI 作后端 API ,用 Vue 构建前端页面,用 Axios 从前端访问后端 API,包括文件的上传和下载。 准备文件上传的API #region 文件上传  可以带参数        [HttpPost("upload")]        publicJsonResultuploadProject(I......
  • C#.NET 国密SM4对称加解密 与JAVA互通 ver:20230731
    C#.NET国密SM4对称加解密与JAVA互通ver:20230731 .NET环境:.NET6控制台程序(.netcore)。JAVA环境:JAVA8,带maven的JAVA控制台程序。 简要解析:1:加密的KEY、明文等输入参数都需要string转byte[],要约定好编码,如:UTF8。2:加密后的输出参数:byte[],在传输时需要转为stri......
  • linux 8 基于线程池和epoll监听实现聊天服务器
    1.立项功能1.聊天总人数显示2.账号密码注册功能-保留名字-永久保留id->保留id功能取消3.总聊天室-进入前可输入名字顺序id4.聊天室聊天5.单对单聊天6.id=cfd串联起来4.服务器代码#include"threadpoolsimple.h"//初始化结构体#include<stdio.h>ThreadPool*thrPool=......
  • JDK 版本异常导致 flutter doctor --android-licenses 出错 (class file version 61.0
    flutterdoctor--android-licensesError:AJNIerrorhasoccurred,pleasecheckyourinstallationandtryagainExceptioninthread"main"java.lang.UnsupportedClassVersionError:com/android/sdklib/tool/sdkmanager/SdkManagerClihasbeencompil......
  • java生成时间戳
    如何用Java生成时间戳概述在Java开发中,时间戳(Timestamp)是一个用于表示某个特定时间的数值,通常是自1970年1月1日午夜(UTC/GMT的午夜)以来的毫秒数。时间戳在很多场景下非常有用,例如记录日志、生成唯一的标识符等。本文将引导刚入行的开发者学习如何使用Java生成时间戳。步骤下面是......
  • java生成订单序号
    Java生成订单序号在电子商务、餐饮、物流等行业中,生成订单序号是一个常见的需求。订单序号通常用于唯一标识每个订单,方便系统进行订单管理和跟踪。在Java中,我们可以使用不同的方法来生成订单序号。本文将介绍几种常见的生成订单序号的方法,并提供相应的代码示例。1.基于时间戳的......
  • java生成16位数字
    如何使用Java生成16位数字作为一名经验丰富的开发者,我将教会你如何使用Java生成16位数字。下面是整个过程的步骤:步骤描述1导入相关的包2创建一个Random对象3生成一个16位的随机数4将随机数转换为字符串现在,让我们一步步来实现这些步骤。步骤1:导入相......
  • java设置字符串颜色
    如何实现Java设置字符串颜色概述本文将向刚入行的小白开发者介绍如何在Java中设置字符串颜色。我们将使用Java的控制台输出来展示不同颜色的字符串。首先,我们将介绍整个实现的流程,然后逐步讲解每个步骤所需的代码和注释。实现流程步骤描述1.导入必要的类和包2.创......
  • java删除对象的某个属性
    Java删除对象的某个属性在Java编程中,我们经常需要对对象进行操作和修改。有时候,我们需要删除对象的某个属性,以便满足特定的需求。本文将介绍如何在Java中删除对象的某个属性,并提供相应的代码示例。删除对象属性的常见场景在实际的开发中,有一些常见的场景需要删除对象的某个属性,......