我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Java多线程之线程同步

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Java多线程之线程同步

volatile

先看个例子


class Test {
		// 定义一个全局变量
    private boolean isRun = true;
 
	  // 从主线程调用发起
    public void process() {
        test();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stop();
    }
		// 启动一个子线程循环读取isRun
    private void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (isRun) {
									// 疑问,如果我这里有一些打印的语句或者线程睡眠的语句,子线程在
									// 主线程将isRun改为false的时候,就会跳出死循环,反之,如果循环体
									// 内是空的,就算在主线程改了isRun的值,也无法及时跳出循环,why?
									// 当然,如果将isRun变量使用volatile修饰就没有此问题
                }
            }
        }).start();
    }
 
    private void stop() {
        isRun = false;
    }
}

有一点是一定的,就是子线程访问isRun的时候会拷贝一份放到自己的线程(工作内存)里,这样在读写的时候可能就不会和外面isRun的值实时是匹配上的。所以就会出现意想不到的问题。

所以我们使用volatile修饰,这样当有多线程同时访问一个变量时,都会自动同步一下。显然这样会带来一定的性能损失,但是如果确实需要还是要这么做的。

但是,有一个问题来了,使用volatile一定能就可解决多线程同步的问题了吗?那我们看下面这个例子:


class TestSynchronize {
 
		// 使用volatile修饰的变量
    private volatile int x = 0;
 
    private void add() {
        x++;
    }
 
    public void test() {
				// 启动第一个线程,进行100万次自加
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0; i< 1_000_000; i++) {
                    add();
                }
                System.out.println("第一个线程x=" + x);
            }
        }).start();
				// 启动第二个线程,进行100万次自加
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0; i< 1_000_000; i++) {
                    add();
                }
                System.out.println("第二个线程x=" + x);
            }
        }).start();
    }
}

我们希望的结果是,最后一个执行完的线程应该是在2_000_000,但是只要你实际测下就发现并不是这样,因为volatile只能保证可见性,但是只要涉及多线程我们一定还听说过原子性这个概念。什么是可见性:

可见性:对于多个线程都在访问的变量,当有个线程在修改的时候,它会保证会将修改的值更新到内存中,而不是只在工作线程中修改,这样当别的线程访问的时候也会去内存中取最新的值,这样就能保证访问到的值是最新的。

那什么又是原子性呢:

原子性:就是一个操作或者多个操作要么都执行,要么都不执行,不会存在执行一半会被打断。

在Java中,对基本数据类型变量的读取和赋值操作是原子性的。但是上述代码中的x++;显然不是原子操作,可以拆解为:


int temp = x + 1;
x = temp;

那么这就为多线程操作带来不确定性,

1、开始x初始值为0,

2、当线程A调用add()函数时,执行到temp=x+1;这一行时被中断了,

3、此时切换到线程B的add()函数,线程B完整执行完两行代码后,x = 1了,

4、这个时候线程B又完整的执行了一遍add方法,那么x=2了,

5、此时发生了线程切换,切换到A执行,A接着上次的执行的语句,temp = 1了,接下来执行x = temp;语句将1赋值给了x。

可是本来x都被B线程加到2了,这下又回去了,经历A和B线程一共三次add()操作,结果x的值只是1。

这就解释了上面那段代码中,两个线程分别加了100万次后,结果最后一个执行完的线程打印的却并不是200万。原因就是add()里面的操作并不是原子性的,而volatile只能保证可见性,不能保证原子性

当然,仅针对上面的按理我们可以将int x = 0;换一种类型声明,比如使用AtomicInteger x = new AtomicInteger(0);然后将x++改成x.incrementAndGet();这样也能保证原子性,确保多线程操作后数据是符合期望的。

除了针对基本数据类型的,还有对引用操作原子化的,AtomicReference<V>

synchronized

当synchronized修饰一个方法时,那么同一时间只有一个线程可以访问此方法,如果有多个方法都被synchronized修饰的话,当一个线程访问了其中一个方法,别的线程就无法访问其他被synchronized修饰的方法。

相当于有一个监视器,当一个线程访问某个方法,其他线程想访问别的方法时,需要和同一个监视器做确认,这么做看起来不太合理,其实也是合理的,比如有两方法都可能对同一个变量做操作,两个线程能同时访问两个方法,这样数据还是会发生错乱。

当然,我们就有两个方法支持同步访问的场景的,只要我们自己确认两个方法不会存在数据上的错乱,我们可以为每个方法指定自己的监视器,在默认情况下是当前类的对象(this)。

我们分别为setName();和其他两个方法指定了不同的monitor(监视器),这样当线程A访问上面两个方法的时候,线程B想访问方法setName也是不受影响的:

接下来我们看我们经常写的另一个例子,单例模式:


class TestInstance {
    private TestInstance(){}
    
    private static TestInstance sInstance;
    
    public static TestInstance newInstance() {
				**// ② 这里判空的目的?**
        if (sInstance == null) {
						**// ① 为什么锁加在这里?**
            synchronized (TestInstance.class) {
								**// ③ 这里判空的目的?**
                if (sInstance == null) {
                    sInstance = new TestInstance();
                }
            }
        }
        return sInstance;
    }
}

我们来依次搞清楚上面的三个问题,

①锁为什么加在里面而不是在方法上加锁,因为加锁后会带来性能上的损失的,单例对象只会创建一次,没必要在实例已经有的时候获取单例时还加锁,对性能是浪费。

②第一个判空的目的就是在已经创建过实例之后的获取操作,不用再经过synchronized判断,这样更快。

③最后一个判空就是防止多个线程都会调到创建实例的操作。

到此这篇关于Java多线程之线程同步的文章就介绍到这了,更多相关Java线程同步内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Java多线程之线程同步

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

java多线程之线程同步的方法有哪些

Java中线程同步的方法有以下几种:1. synchronized关键字:使用synchronized关键字可以实现对代码块、方法或对象的同步,确保同一时间只有一个线程可以访问被同步的代码块、方法或对象。2. ReentrantLock类:
2023-09-27

Java如何实现多线程、线程同步

这篇文章主要介绍了Java如何实现多线程、线程同步的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java如何实现多线程、线程同步文章都会有所收获,下面我们一起来看看吧。1 多线程1.1 进程进程:是正在运行的程
2023-06-30

多线程之线程同步的方法(7种)

1. 锁机制:使用锁对象对需要同步的代码块进行加锁,确保同一时刻只有一个线程可以执行该代码块。2. 互斥量:使用互斥量(Mutex)来保证同一时刻只有一个线程可以访问共享资源。3. 信号量:使用信号量(Semaphore)来限制同时访问共享
2023-09-15

Java线程之线程同步synchronized和volatile详解

上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程共享的,一个线程改变了其成员变量num值
2023-05-30

Java中多线程同步类 CountDownLatch

在多线程开发中,常常遇到希望一组线程完成之后在执行之后的操作,java提供了一个多线程同步辅助类,可以完成此类需求:类中常见的方法:其中构造方法:CountDownLatch(int count) 参数count是计数器,一般用要执行线程的
2023-05-31

Java多线程 - 线程安全和线程同步解决线程安全问题

文章目录 线程安全问题线程同步方式一: 同步代码块方式二: 同步方法方式三: Lock锁 线程安全问题 线程安全问题指的是: 多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。 举例:
2023-08-20

python多线程之间的同步(一)

引言:       线程之间经常需要协同工作,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直到该线程完成对数据的操作。这些技术包括临界区(Critical Section),互斥量(Mutex),信号量(Semapho
2023-01-31

如何处理 Java 多线程状态同步?(Java多线程状态同步如何处理)

在Java编程中,多线程是一种强大的技术,它允许程序同时执行多个任务,提高程序的性能和响应性。然而,多线程也带来了一些挑战,其中之一就是线程状态同步的问题。线程状态同步是指确保多个线程在访问共享资源时的顺序和一致性。如果线程状态不同步,可能会导致数据不一致、死锁等问题。本文将介绍Java
如何处理 Java 多线程状态同步?(Java多线程状态同步如何处理)
Java2024-12-20

Java多线程怎么同步优化

这篇文章给大家分享的是有关Java多线程怎么同步优化的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。概述处理器上的寄存器的读写的速度比内存快几个数量级,为了解决这种速度矛盾,在它们之间加入了高速缓存。加入高速缓存带
2023-06-15

Java学习之线程同步与线程间通信详解

这篇文章主要为大家详细介绍了线程同步和线程之间的通信的相关知识,文中的示例代码讲解详细,对我们学习Java有一定的帮助,感兴趣的可以了解一下
2022-12-27

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录