java多线程synchronized volatile解析

  先简单说说原子性:具有原子性的操作被称为原子操作。原子操作在操作完毕之前不会线程调度器中断。即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。在Java中,对除了longdouble之外的基本类型的简单操作都具有原子性。简单操作就是赋值或者return。比如”a = 1;“和 “return a;”这样的操作都具有原子性。但是在Java中,类似”a += b”这样的操作不具有原子性,不是同步的就会出现难以预料的结果。

  在我们平常的编程过程中,经常会遇到线程安全与不全这类的词语,相信刚开始很多新手碰到这个都会很头疼。现拿万恶的火车票举个例子吧。如杭州到武汉还剩10张车票,有4个售票点正在售杭州到武汉的票,若所写程序是线程不安全的,则当a售票点售出票1时,b售票点也正在售票,由于a售票点售出后还未及时更新到主内存中,因此b售票点可能同样售出的是票1,这就造成了两个人买到一张座位的情况,这就是线程不安全的。若线程安全,则当a售票点在售票1时,只有等a售票点将票1售出,并更新了主内存数据后,其余售票点才能继续卖票,并且数据中票1已经卖掉了,不会再重复操作。此时即利用了synchronized锁的机制。

  • synchronized是一个方法或块的修饰符,synchronized分为方法同步块同步

1、synchronized 实例方法

 1 //加在方法上
 2 public synchronized void function() {
 3     //somecode
 4 }

该同步是同步在拥有该方法的对象上的,同一时间只有一个线程能访问到该方法(前提是在一个实例上,若有几个实例,则可以同时进行)。

2、synchronized 静态方法

1 public static synchronized void  function(){
2     //somecode
3 }

由于静态方法不属于对象,而是属于类,会将这个方法所在类的class对象上锁。

3、synchronized 块

 1 //同步块
 2 public void method1(){
 3     synchronized(this){
 4         //somecode
 5     }
 6 }
 7
 8 public void method2(Object o){
 9     synchronized(o){
10         //somecode
11     }
12 }

锁定的是调用这个方法的对象(method1)或传入的对象(method2),当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(对象)来充当锁。

  • volatile是一个变量修饰符

一个volatile类型的变量不允许线程从主内存中将变量的值拷贝到自己的存储空间。因此,一个声明为volatile类型的变量将在所有的线程中同步的获得数据,不论你在任何线程中更改了变量,其他的线程将立即得到同样的结果。volatile变量具有synchronized的可见性特性,但是不具备原子特性。这就是说线程能够自动发现volatile变量的最新值。volatile变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值之间没有约束。

正确使用volatile修饰符:a、对变量的写操作不依赖于当前值。b、该变量没有包含在具有其他变量的不变式中。

=========================================关于两者的区别==============================

1.volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2.volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
3.volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
4.volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
5.volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

一些例子:

synchronized

 1 public class SyncTest {
 2
 3     public static void main(String[] args) {
 4         Outputter outputter = new Outputter();
 5         MyThread myThread1 = new MyThread(outputter, "zhangsan");
 6         MyThread myThread2 = new MyThread(outputter, "lisi");
 7         Thread threada = new Thread(myThread1);
 8         Thread threadb = new Thread(myThread2);
 9         threada.start();
10         threadb.start();
11     }
12
13 }
14
15 class MyThread implements Runnable {
16     Outputter outputter;
17     String name;
18
19     public MyThread(Outputter outputter, String name) {
20         this.outputter = outputter;
21         this.name = name;
22     }
23
24     @Override
25     public void run() {
26         outputter.output(name);
27     }
28 }
29
30 /**
31  * 当没有锁定时,将输出"zhlangisisan"等不确定的值。
32  *
33  * @author lcm
34  *
35  */
36 class Outputter {
37     public void output(String name) {
38         //  为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符
39         for (int i = 0; i < name.length(); i++) {
40             System.out.print(name.charAt(i));
41         }
42     }
43 }
44
45 /**
46  * 若想得到zhangsanlisi或者lisizhangsan,就需要用到synchronized。
47  *
48  * @author lcm
49  *
50  */
51 class Outputter {
52     public synchronized void output(String name) {
53         // 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符
54         for (int i = 0; i < name.length(); i++) {
55             System.out.print(name.charAt(i));
56         }
57     }
58 }
59
60 class Outputter {
61     public void output(String name) {
62         // 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符
63         synchronized (this) { //代码块也可以传入一个对象,但需保证是同一个对象
64             for (int i = 0; i < name.length(); i++) {
65                 System.out.print(name.charAt(i));
66             }
67         }
68     }
69 }
70
71 /**
72  * 若修饰静态静态代码块,则是对整个class对象加锁
73  *
74  * @author lcm
75  *
76  */
77 class Outputter {
78     public synchronized static void output(String name) {
79         // 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符
80         for (int i = 0; i < name.length(); i++) {
81             System.out.print(name.charAt(i));
82         }
83     }
84 }

volatile 无法保证对变量操作的原子性

 1 public class VolatileTest {
 2     private volatile int inc = 0;
 3
 4     public static void main(String[] args) {
 5         final VolatileTest volatileTest = new VolatileTest();
 6         for (int i = 0; i < 1000; i++) {
 7             new Thread() {
 8                 @Override
 9                 public void run() {
10                     try {
11                         Thread.sleep(1);
12                         volatileTest.inc();
13                     } catch (Exception e) {
14                         e.printStackTrace();
15                     }
16                 }
17             }.start();
18         }
19
20         while (Thread.activeCount() > 1){
21             // 保证前面的线程都执行完
22             Thread.yield();
23         }
24         System.out.println(volatileTest.inc);
25     }
26
27     private void inc() {
28         inc++;
29     }
30 }

运行结果并不是1000,volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。

线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值是10,然后进行加1操作,并把11写入工作内存,最后写入主存。然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。那么两个线程分别进行了一次自增操作后,inc只增加了1。

1 //线程1
2 boolean stop = false;
3 while(!stop){
4     doSomething();
5 }
6
7 //线程2
8 stop = true;

上述代码当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。

若加上volatile,会强制将修改的值立即写入主存,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。

时间: 2024-08-27 06:38:40

java多线程synchronized volatile解析的相关文章

java多线程关键字volatile的使用

java多线程关键字volatile的作用是表示多个线程对这个变量共享. 如果是只读的就可以直接用,写数据的时候要注意同步问题. 例子: package com.ming.thread.volatiletesttrhead1; /** * volatile 关键字的使用 * volatile 这个关键字的作用就是保持由此关键字修饰的变量在多个线程之间可以看得见 * @author mingge * */ public class volatiletesttrhead extends Thread

Java多线程编程——volatile关键字

(本篇主要内容摘自<Java多线程编程核心技术>) volatile关键字的主要作用是保证线程之间变量的可见性. package com.func; public class RunThread extends Thread{ private boolean isRunning = true; // volatile private boolean isRunning = true; public boolean isRunning() { return isRunning; } public

Java多线程synchronized同步

非线程安全问题 “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程问题”.也即是说,方法中的变量永远是线程安全的. 如果多个线程共同访问1个对象中的实例变量,则可能线程不安全.下面以实例说明 1 public class HasSelfNum { 2 private int num = 0; 3 public void add(String name) { 4 try { 5 if (name.equals("a")) { 6 num = 100; 7

JAVA多线程synchronized详解

Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块. 尤其关键

Java多线程:volatile变量、happens-before关系及内存一致性

什么是 Volatile 变量?Volatile 是 Java 中的一个关键字.你不能将它设置为变量或者方法名,句号. 认真点,别开玩笑,什么是 Volatile 变量?我们应该什么时候使用它?哈哈,对不起,没法提供帮助. volatile 关键字的典型使用场景是在多线程环境下,多个线程共享变量,由于这些变量会缓存在 CPU 的缓存中,为了避免出现内存一致性错误而采用 volatile 关键字. 考虑下面这个生产者/消费者的例子,我们每次生成/消费一个元素: 1 2 3 4 5 6 7 8 9

Java多线程:volatile 关键字

一.内存模型的相关概念 大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度.因此在CPU里面就有了高速缓存. 也就是,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高

Java多线程之三volatile与等待通知机制示例

原子性,可见性与有序性 在多线程中,线程同步的时候一般需要考虑原子性,可见性与有序性 原子性 原子性定义:一个操作或者多个操作在执行过程中要么全部执行完成,要么全部都不执行,不存在执行一部分的情况. 以我们在Java代码中经常用到的自增操作i++为例,i++实际上并不是一步操作,而是首先对i的值加一,然后将结果再赋值给i.在单线程中不会存在问题,但如果在多线程中我们考虑这样一个情况:i是一个共享变量,初始值为0,假设线程一以执行到某一步正好进行自增操作i++,刚好对i进行了加一但是还没将值重新赋

java 多线程 Synchronized方法和方法块 synchronized(this)和synchronized(object)的理解

synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块. 1. synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法.如:public synchronized void accessVal(int newVal);synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否

java 多线程 synchronized块 同步方法

synchronized关键字又称同步锁 当方法执行完后,会自动释放锁,只有一个线程能进入此方法 看看以下的各种例子对synchronized的详细解释 1.是否加synchronized关键字的不同 1 public class ThreadTest { 2 3 public static void main(String[] args) { 4 Example example = new Example(); 5 6 Thread t1 = new Thread1(example); 7 T