【Java多线程】-线程同步synchronized和volatile

前言

1) 多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。
2)程序的主内存是多个线程共享的,每个线程都有自己的工作内存,工作内存中存储了主内存某些对象的副本。
3)原子性:系统提供的原子性操作包括:lock,unlock,read,load,use,assign,store,write。
4)线程操作共享变量的执行顺序

  • 第一步:read,load,将共享变量加载到工作内存;
  • 第二步:use,assign,使用和赋值;
  • 第三步:store,write,用变量副本刷新共享变量。

5)可见性:当一个共享变量在多个线程工作内存中都存在副本时,其中一个线程修改了共享变量,其他线程也应该能看到被修改后的值。
6)有序性:比如一个账户里有100元,线程A往账户中存10元,线程B从账户中取10元,人们期待的最终结果是账户里的余额为100元,但由于取钱和存钱都不是原子操作,而且系统对线程的调度是随机的,所以可能会出现线程A从主内存读到100,线程B从主内存读到100,线程A存钱后变成110,更新主内存变成110,线程B取钱后变成90,更新主内存变成90,最终主内存共享变量的值为90。这就会造成很严重的错误,因此我们必须保证线程A,B的执行顺序,先存后取或者先取后存,此为有序性


下面看一个线程同步的问题:

public static void main(String[] args) {
        final Account account = new Account();
        new Thread() {
            public void run() {
                account.printName("tailyou");
            };
        }.start();
        new Thread() {
            public void run() {
                account.printName("lixueyang");
            };
        }.start();
}


public class Account {
    public void printName(String name) {
        for (int i = 0; i < name.length(); i++) {
            System.out.print(name.charAt(i));
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出:
这里写图片描述

很显然,输出的结果被打乱了,我们期待的结果是tailyoulixueyang,这就是线程同步问题,我们希望printName()方法被线程执行完后再切换到其他线程。


synchronized

Java中使用synchronized关键字来保证一段代码在多线程执行时是互斥的。有两种方法:
同步代码块:

    public void printName(String name) {
        synchronized (this) {
            for (int i = 0; i < name.length(); i++) {
                System.out.print(name.charAt(i));
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

同步方法:

    public synchronized void printName(String name) {
        for (int i = 0; i < name.length(); i++) {
            System.out.print(name.charAt(i));
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

使用synchronized关键字将需要互斥的代码包起来,或者将synchronized关键字加在需要同步的方法上,使用synchronized修饰的方法或者代码块可以看成是一个原子操作。一个线程执行互斥代码的过程如下:

  • 获得同步锁;
  • 清空工作内存
  • 从主内存拷贝对象副本到工作内存;
  • 执行临界区代码;
  • 刷新主内存数据;
  • 释放同步锁。
    所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性

volatile

对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的
例如线程A,线程B在进行read,load操作时发现主内存中count的值为5,那么它们都会加载这个最新的值;
线程A对count+1后,更新主内存中的count为6;
由于线程B已经load,所以对count+1后,也会更新主内存中count为6.
所以,即使用volatile关键字修饰count变量,也不能解决并发带来的问题。
volatile只能保证多线程的内存可见性,不能保证多线程的并发有序性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值