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线程同步内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 五种Java多线程同步的方法

    为什么要线程同步 因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常.举 个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块.假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果 呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚.因此多线程同步就是要解决这个问题. 一.不同步时的代码 Bank.java package threadTe

  • Java实现多线程同步五种方法详解

    一.为什么要线程同步 因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常.举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块.假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚.因此多线程同步就是要解决这个问题. 二.不同步时的代码 Bank.java package threadTe

  • Java多线程同步器代码详解

    同步器 为每种特定的同步问题提供了解决方案,同步器是一些使线程能够等待另一个线程的对象,允许它们协调动作.最常用的同步器是CountDownLatch和Semaphore,不常用的是Barrier 和Exchanger Semaphore Semaphore[信号标:旗语],通过计数器控制对共享资源的访问. 测试类: package concurrent; import concurrent.thread.SemaphoreThread; import java.util.concurrent.

  • java多线程之线程同步七种方式代码示例

    为何要使用同步?  java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),     将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,     从而保证了该变量的唯一性和准确性. 1.同步方法  即有synchronized关键字修饰的方法.     由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,     内置锁会保护整个方法.在调用该方法前,需要获得内置锁,否则就处于阻塞状态.     代码

  • 详解Java多线程编程中的线程同步方法

    1.多线程的同步: 1.1.同步机制: 在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生.所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问. 1.2.共享成员变量的例子: 成员变量与局部变量: 成员变量: 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的. 局部变量: 如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝.他们

  • Java多线程编程中synchronized线程同步的教程

    0.关于线程同步 (1)为什么需要同步多线程? 线程的同步是指让多个运行的线程在一起良好地协作,达到让多线程按要求合理地占用释放资源.我们采用Java中的同步代码块和同步方法达到这样的目的.比如这样的解决多线程无固定序执行的问题: public class TwoThreadTest { public static void main(String[] args) { Thread th1= new MyThread1(); Thread th2= new MyThread2(); th1.st

  • 浅析Java多线程同步synchronized

    单线程是安全的,因为线程只有一个,不存在多个线程抢夺同一个资源 代码例子: public class SingleThread { int num=10; public void add(){ while(num<13){ num++; try{ Thread.sleep(1000); } catch(Exception e){ System.out.println("中断"); } System.out.println(num); } } public static void

  • Java多线程 线程同步与死锁

     Java多线程 线程同步与死锁 1.线程同步 多线程引发的安全问题 一个非常经典的案例,银行取钱的问题.假如你有一张银行卡,里面有5000块钱,然后你去银行取款2000块钱.正在你取钱的时候,取款机正要从你的5000余额中减去2000的时候,你的老婆正巧也在用银行卡对应的存折取钱,由于取款机还没有把你的2000块钱扣除,银行查到存折里的余额还剩5000块钱,准备减去2000.这时,有趣的事情发生了,你和你的老婆从同一个账户共取走了4000元,但是账户最后还剩下3000元. 使用代码模拟下取款过

  • 以银行取钱为例模拟Java多线程同步问题完整代码

    简单了解下在操作系统中进程和线程的区别: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程.(进程是资源分配的最小单位) 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小.(线程是cpu调度的最小单位) 线程和进程一样分为五个阶段:创建.就绪.运行.阻塞.终止. 多进程是指操作系统能同时运行多个任务(程序). 多线程是指在同一程序中有多个顺序流在执行.首先存钱取钱的这个操作,应该是线程操作的

  • Java commons io包实现多线程同步图片下载入门教程

    目的: 实现多线程同时下载网络图片,入门级. 多线程入门 commons io: 是针对开发IO流功能的工具类库,其中包含了许多可调用的函数. 1.commons io 可直接百度,进入官网直接下载即可 Linux下载tar.gz,window下载.zip. 2.解压commons io ,复制下面的java文件,后在项目中,新建package,我的名为lib,如下,将复制的java文件粘贴到package中,并鼠标右击此文件,点击add as a library即可. 3.代码如下:多线程基础

  • Java中多线程同步类 CountDownLatch

    在多线程开发中,常常遇到希望一组线程完成之后在执行之后的操作,java提供了一个多线程同步辅助类,可以完成此类需求: 类中常见的方法: 其中构造方法: CountDownLatch(int count) 参数count是计数器,一般用要执行线程的数量来赋值. long getCount():获得当前计数器的值. void countDown():当计数器的值大于零时,调用方法,计数器的数值减少1,当计数器等数零时,释放所有的线程. void await():调所该方法阻塞当前主线程,直到计数器减

  • Java中CountDownLatch进行多线程同步详解及实例代码

    Java中CountDownLatch进行多线程同步详解 CountDownLatch介绍 在前面的Java学习笔记中,总结了Java中进行多线程同步的几个方法: 1.synchronized关键字进行同步. 2.Lock锁接口及其实现类ReentrantLock.ReadWriteLock锁实现同步. 3.信号量Semaphore实现同步. 其中,synchronized关键字和Lock锁解决的是多个线程对同一资源的并发访问问题.信号量Semaphore解决的是多副本资源的共享访问问题. 今天

随机推荐