Java实现线程同步方法及原理详解
作者:main(0) 发布时间:2021-07-29 21:28:13
标签:Java,线程,同步
一、概述
无论是什么语言,在多线程编程中,常常会遇到多个线同时操作程某个变量(读/写),如果读/写不同步,则会造成不符合预期的结果。
例如:线程A和线程B并发运行,都操作变量X,若线程A对变量X进行赋上一个新值,线程B仍然使用变量X之前的值,很明显线程B使用的X不是我们想要的值了。
Java提供了三种机制,解决上述问题,实现线程同步:
同步代码块
synchronized(锁对象){
// 这里添加受保护的数据操作
}
同步方法
静态同步方法:synchronized修饰的静态方法,它的同步锁是当前方法所在类的字节码对象
public static synchronized void staticMethod(){
}
非静态同步方法:synchronized修饰的非静态方法,它的同步锁即为this
public synchronize void method(){
}
锁机制
// 以可重入锁举例
Lock lock = new ReentrantLock(/*fail*/);
// fail:
// true表示使用公平锁,即线程等待拿到锁的时间越久,越容易拿到锁
// false表示使用非公平锁,线程拿到锁全靠运气。。。cpu时间片轮到哪个线程,哪个线程就能获取锁
lock.lock();
// 这里添加受保护的数据操作
lock.unlock();
个人理解:其实无论哪种机制实现线程同步,本质上都是加锁->操作数据->解锁的过程。同步代码块是针对{}中,同步方法是针对整个方法。其ReentrantLock类提供的lock和unlock和C++的std::mutex提供lock和unlock类似
二、测试用例
同步代码块测试类
package base.synchronize;
public class SynchronizeBlock implements Runnable {
private int num = 100;
@Override
public void run() {
while (num > 1) {
synchronized (this) {
// 同步代码块,只有拿到锁,才有cpu执行权
System.out.println("Thread ID:" + Thread.currentThread().getId() + "---num:" + num);
num--;
}
}
System.out.println("Thread ID:" + Thread.currentThread().getId() + " exit");
}
}
同步方法测试类
package base.synchronize;
public class SynchronizeMethod implements Runnable {
private int num = 100;
public static int staticNum = 100;
boolean useStaticMethod;
public SynchronizeMethod(boolean useStaticMethodToTest) {
this.useStaticMethod = useStaticMethodToTest;
}
// 对于非静态方法,同步锁对象即this
public synchronized void method() {
System.out.println("Thread ID:" + Thread.currentThread().getId() + "---num:" + num);
num--;
}
// 对于静态方法,同步锁对象是当前方法所在类的字节码对象
public synchronized static void staticMethod() {
System.out.println("Static Method Thread ID:" + Thread.currentThread().getId() + "---num:" + staticNum);
staticNum--;
}
@Override
public void run() {
if (useStaticMethod) { // 测试静态同步方法
while (staticNum > 1) {
staticMethod();
}
}else{ // 测试非静态同步方法
while (num > 1){
method();
}
}
System.out.println("Thread ID:" + Thread.currentThread().getId() + " exit");
}
}
ReentrantLock测试类
package base.synchronize;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SynchronizeLock implements Runnable {
private Lock lock = null;
private int num = 100;
public SynchronizeLock(boolean fair){
lock = new ReentrantLock(fair); // 可重入锁
}
@Override
public void run() {
while (num > 1) {
try {
lock.lock();
System.out.println("Thread ID:" + Thread.currentThread().getId() + "---num:" + num);
num--;
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
System.out.println("Thread ID:" + Thread.currentThread().getId() + " exit");
}
}
测试三种机制的Demo
package base.synchronize;
public class Demo {
public static void main(String[] args) {
synchronizeBlockTest(); // 同步代码块
synchronizeMethodTest(); // 同步非静态方法
synchronizeStaticMethodTest(); // 同步静态方法
synchronizeLockTest(); // 可重入锁机制
}
public static void synchronizeBlockTest(){
Runnable run = new SynchronizeBlock();
for(int i = 0; i < 3; i++){
new Thread(run).start();
}
}
public static void synchronizeMethodTest(){
Runnable run = new SynchronizeMethod(false);
for(int i = 0; i < 3; i++){
new Thread(run).start();
}
}
public static void synchronizeStaticMethodTest() {
Runnable run = new SynchronizeMethod(true);
for(int i = 0; i < 3; i++){
new Thread(run).start();
}
}
public static void synchronizeLockTest(){
Runnable run = new SynchronizeLock(false); // true:使用公平锁 false:使用非公平锁
for(int i = 0; i < 3; i++){
new Thread(run).start();
}
}
}
无论哪种机制,都得到预期的效果,打印100-0
来源:https://www.cnblogs.com/main404/p/13020726.html


猜你喜欢
- 使用Convert接口实现类型转换器在Spring3中引入了一个Converter接口,它支持从一个Object转为另一个Object。除了
- 从今天开始,本专栏持续更新Android简易实战类博客文章。和以往专栏不同,此专栏只有实例。每个实例尽量按照知识点对应相应一章节的内容去写,
- 本文实例为大家分享了Unity实现切割图集工具的具体代码,供大家参考,具体内容如下操作步骤先将脚本拖入Editor1.选中要切割的图片,te
- 用法1 为原始类型扩展方法先说一下,this 后面跟的类型,就是要拓展方法的类型。注意要写在静态类中的静态方法,不然有些情况下访问/// &
- 装箱是将值类型转换为 object 类型或由此值类型实现的任何接口类型的一个过程。 当 CLR 对值类型进行装箱时,会将该值包装到 Syst
- 笔记在微服务中,若想要使用远程调用,需要引入spring-cloud-starter-openfeign(在使用注册中心的环境下)<d
- 本程序通过JFrame实时显示本机摄像头图像,并将图像存储到一个缓冲区,当用户用鼠标点击JFrame中任何区域时,显示抓取图像的简单动画,同
- 前言apollo配置经常使用的方式是@value,比较便捷,如果只出现在一个类中还行,但是如果多个类中并不是很方便,特别是如果出现配置值变化
- 不安全的集合在单线程应用中,通常采取new ArrayList(),指定一个List集合,用于存放可重复的数据。但在多线程下,往往会出现意想
- 获取非公平锁(基于JDK1.7.0_40)非公平锁和公平锁在获取锁的方法上,流程是一样的;它们的区别主要表现在“尝试获取锁的机制不同”。简单
- HttpServletResponse.sendRedirect与RequestDispatcher.forward方法都可以实
- java有两种类型的classload,一种是user-defined的,一种是jvm内置的bootstrap class loader,所
- 文章来源:aspcn 作者:孙雯服务器Sockets列表9.2是一个服务器应用程序的一部分.列表9.2 一个简单的服务器程序 /** &n
- 本文实例讲述了C#使用listView增删操作的方法。分享给大家供大家参考。具体分析如下:应用场景: C#中使用listView控件,实现动
- 我这里 shiro 并没有集成 springMVC,直接使用 ini 配置文件。shiro.ini[main]# Objects and t
- 简述最近做的公司项目,图片比较多,不想给其存储到自己服务器上,就买了阿里云的OSS服务器来哦进行存储,其实集成第三方平台,一般没什么难度,当
- Unity IPostBuildPlayerScriptDLLsUnity IPostBuildPlayerScriptDLLs是Unity
- Android有三个基础组件Activity,Service和BroadcastReceiver,他们都是依赖Intent来启动。本文介绍的
- 本文实例为大家分享了java利用数组随机抽取幸运观众的具体代码,供大家参考,具体内容如下思想:首先将所有观众姓名生成数组,然后获取数组元素的
- 引言你在服务端的安全管理使用了 Spring Security,用户登录成功之后,Spring Security 帮你把用户信息保存在 Se