Synchronized、lock、volatile、ThreadLocal、原子性总结、Condition

1. Synchronized

首先Synchronized锁住的是对象

1.1 互斥:两个线程去调用同一对象的synchronized代码块,只有一个线程能够获得锁,另个线程会被阻塞

1.2 共享:类中有多个Synchronized代码块,这些代码块共享对象上同一个锁,即类中有Synchronized方法f()、g(),如果有一个线程获得了对象上的锁,调用f()方法,另个线程调用该对象g()需要等待前一个线程执行完f()函数

1.3 可重入性:JVM跟踪对象被加锁的次数,同一个线程可以多次获得对象上的锁,线程给对象加锁时计数加1,当线程退出Synchronized代码块时计数减1,只有当计数减为0时其他线程才能调用该线程的Synchronized方法

参考《Thinking in Java》

以下为猜想:

synchronized实现原理:1.Synchronize是个对象加锁,所以对象上有个标志位,即对象锁,当标志位为0表示对象已经资源被占用不能使用,当标志位为1表示对象可以使用,在调用结束后

2. volatile与原子性

首先线程公有的变量:实例字段、静态字段等才有线程安全问题。

对计算机来说,为了提高效率在内存和处理器之间加入了高速缓存cache,运算结束后将运算结果从缓存同步回内存中。JVM也去定义了自己的内存模型来屏蔽各种硬件和操作系统内存访问的差异。JVM利用了硬件的各种特性(寄存器、高速缓存和指令集中某些特定指令)来获取更好的执行速度。

Java内存模型规定了所有变量都存储在主内存,但是对于变量的操作(读取、赋值)即计算过程都在线程的工作内存中进行,在线程的工作内存中保存了该线程使用到的变量的主内存副本(如果是主内存的一个对象,不会一次把整个对象拷贝一次,该线程访问到的对象的字段可能被拷贝)。

工作内存差不多对应于虚拟机栈中的部分。

Java线程 <----------------------->工作内存<-------------------->    Save

Java线程 <----------------------->工作内存<-------------------->   和Load  <----------------> 主内存

Java线程 <----------------------->工作内存<-------------------->   操作

普通变量:A线程修改普通变量的值,线程B只有在线程A将普通变量写回主内存

volatile变量:对volatile变量的修改操作能立即反映到其他线程中,线程在读/使用volatile变量时候,必须经过    从主内存读取数据并保存到线程工作区 -> 使用线程工作区变量的过程,每次使用volatile变量必须从主内存刷新最新值;在修改volatile变量时必须经过 在线程工作区修改 -> 回写到主内存,每次修改变量后必须立刻同步回主内存。

volatile变量与普通变量的区别:在使用volatile变量时必须先从主内存读取,而普通变量可能是使用线程工作区的变量,比如下面的程序片段,如果stop是普通变量,那么除第一次从主内存读取stop变量后的每次循环可能都是使用线程工作区中保存的副本,而其他线程修改stop时这个线程不能立即感应到。volatile每次使用时都必须从主内存中读取,就不存在这样的问题。但是在线程工作区已经保存有普通变量副本时,是否在每次使用该变量时候一定使用该副本?以下是例程1、2:

例程1.

public class Main{
	static boolean stop;
	public static void main(String[] args) throws InterruptedException{
		Thread t1 = new Thread(){
			int i = 0;
			public void run(){
				while(!stop){
//					System.out.println();
				}
			}
		};

		t1.start();
		TimeUnit.SECONDS.sleep(1);
		stop = true;
	}
}

例1程序中线程t1无法停止,因为程序被编译成

       if(!stop){
              while(true){}
       }

参考《Effective Java》

例程2.

public class Main{
	static boolean stop;
	public static void main(String[] args) throws InterruptedException{
		Thread t1 = new Thread(){
			int i = 0;
			public void run(){
				while(!stop){
					System.out.println();
				}
			}
		};

		t1.start();
		TimeUnit.SECONDS.sleep(1);
		stop = true;
	}
}

此处加了System.out.println()在我的机器上成功停止了线程运行,那么可以确定程序没有被编译成例程1那样。所以while(!stop)时在使用stop变量是否一定每次都从主内存复制stop到工作内存?如果是,那普通变量和volatile还有区别吗?

public class Main{
	static volatile boolean stop;
	public static void main(String[] args) throws InterruptedException{
		Thread t1 = new Thread(){
			int i = 0;
			public void run(){
				while(!stop){
					System.out.println(++i);
				}
			}
		};

		t1.start();
		TimeUnit.SECONDS.sleep(1);
		stop = true;
	}
}
public class Main{
	static boolean stop;
	public static void main(String[] args) throws InterruptedException{
		Thread t1 = new Thread(){
			int i = 0;
			public void run(){
				while(!stop){
					System.out.println(++i);
				}
			}
		};

		t1.start();
		TimeUnit.SECONDS.sleep(1);
		stop = true;
	}
}

上面两段程序运行输出i的最终值并没有显然的差别,所以似乎在此时volatile变量和普通变量并没有区别。参考下面两段程序:

public class Main{
	static boolean stop;
	public static void main(String[] args) throws InterruptedException{
		Thread t1 = new Thread(){
			int i = 0;
			public void run(){
				while(!stop){
				}
			}
		};

		t1.start();
		TimeUnit.SECONDS.sleep(1);
		stop = true;
	}
}
public class Main{
	static volatile boolean stop;
	public static void main(String[] args) throws InterruptedException{
		Thread t1 = new Thread(){
			int i = 0;
			public void run(){
				while(!stop){
				}
			}
		};

		t1.start();
		TimeUnit.SECONDS.sleep(1);
		stop = true;
	}
}

加上volatile后线程t1在大约1s后停止了,而不加volatile线程t1不会停止,原因在上面已论述。所以volatile的一个作用是可以禁止指令重排序。参考《深入理解Java虚拟机》。

另一个问题:对volatile变量而言,修改变量总是要经过修改副本的值然后刷新到主内存两个步骤,那么这两个步骤是否不可被中断?如果可以被中断那么就不能立即刷新到主存。猜测是不能被中断的,两个操作合并到一起,是原子操作。

volatile还有更高级的特性,可参考 http://tutorials.jenkov.com/java-concurrency/volatile.html

里面介绍 Happens-Before Guarantee特点。

参考《Thinking in Java》、《深入理解Java虚拟机》

此外,同步会导致向主存刷新。

原子操作:一旦操作开始,那么它一定不会在可能发生的上下文切换之前(切换到其他线程执行)执行完毕。基本类型的读写操作都是原子的,除了long和double类型,JVM会将64位long、double变量的读写分为两个32位操作来执行。

volatile和原子性:

Java中count++这样的操作不是原子操作,即使将count设置为volatile变量也不能在多线程中保证安全性,简单例子就是多个线程都去执行count++,并打印出来,并且count为volatile变量,但是打印出来count并不会是递增的。如果希望打印出的count是递增的必须将count++加上同步操作。

3. ThreadLocal

先上链接 http://tutorials.jenkov.com/java-concurrency/threadlocal.html 中的Example:

public class ThreadLocalExample {

    public static class MyRunnable implements Runnable {

        private ThreadLocal<Integer> threadLocal =
               new ThreadLocal<Integer>();

        @Override
        public void run() {
            threadLocal.set( (int) (Math.random() * 100D) );

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }

            System.out.println(threadLocal.get());
        }
    }

    public static void main(String[] args) {
        MyRunnable sharedRunnableInstance = new MyRunnable();

        Thread thread1 = new Thread(sharedRunnableInstance);
        Thread thread2 = new Thread(sharedRunnableInstance);

        thread1.start();
        thread2.start();

        thread1.join(); //wait for thread 1 to terminate
        thread2.join(); //wait for thread 2 to terminate
    }

}

ThreadLocal是一个类,利用它的set、get方法可以在不同线程中都去使用同一个对象的副本。如果多个线程希望使用相同的初始值,但是又不希望在不同线程中相互干扰,这时可以使用ThreadLocal类。

4. Lock & unLock

5. Condition

额外的知识点:CPU动态调度

参考<<程序员的自我修养 链接、装载与库>>1.6节

先看下面的单例模式代码,书中为C++代码:

volatile T* pInst = 0;
T* GetInstance(){
  if(pInst == NULL){
    lock();
    if(pInst == NULL){
      pInst = new T;
    }
    unlock();
  }
  return pInst;
}

C++里new包含两个步骤:

1. 分配内存

2. 调用构造函数

所以pInst = new T包含三个步骤:

1. 分配内存

2. 在内存位置上调用构造函数

3. 将内存的地址赋值给pInst

但2,3两个步骤顺序可以颠倒,这样会造成一种情况:pInst已经不是NULL,但对象仍未构造完毕,这时如果有另个线程调用GetInstance那么该线程执行到第一个if语句时判断不满足条件而直接执行return pInst,这样该线程将获得pInst地址,但其实此时pInst对象并未执行完构造函数,如果此线程后续还使用pInst则会造成错误。

以上问题的根本原因是cpu乱序执行了步骤2,3

很早写的文章没完成,今天补补坑,回头继续补坑

时间: 2024-08-30 17:10:07

Synchronized、lock、volatile、ThreadLocal、原子性总结、Condition的相关文章

多线程三:Thread API,ThreadLocal,synchronized,volatile和Condition

一.Thread API: setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh) 首先要了解什么是Thread.UncaughtExceptionHandler,默认来说当线程出现未捕获的异常时,会中断并抛出异常,抛出后的动作只有简单的堆栈输出.如: public class ThreadTest{ public static void main(String[] args) throws Exception

[转帖]synchronized、volatile、Lock详解

synchronized.volatile.Lock详解 https://blog.csdn.net/u012102104/article/details/79231159   在Java并发编程过程中,我们难免会遇到synchronized.volatile和lock,其中lock是一个类,而其余两个则是Java关键字.以下记录了小博开发过程中对这三者的理解,不足之处请多指教. 关于线程与进程请参考博文 以操作系统的角度述说线程与进程 synchronized  synchronized是Ja

Java中的synchronized、volatile、ReenTrantLock、AtomicXXX

多线程和并发性并不是什么新内容,但是 Java 语言设计中的创新之一就是,它是第一个直接把跨平台线程模型和正规的内存模型集成到语言中的主流语言.核心类库包含一个 Thread 类,可以用它来构建.启动和操纵线程,Java 语言包括了跨线程传达并发性约束的构造 -- synchronized 和 volatile .在简化与平台无关的并发类的开发的同时,它决没有使并发类的编写工作变得更繁琐,只是使它变得更容易了. synchronized 快速回顾 把代码块声明为 synchronized,有两个

并发编程之关键字(synchronized、volatile)

并发编程主要设计两个关键字:一个是synchronized,另一个是volatile.下面主要讲解这两个关键字,并对这两个关机进行比较. synchronized synchronized是通过JMV种的monitorenter和monitorexit指令实现同步.monitorenter指令是在编译后插入到同步代码的开始位置,而monitorexit插入到同步代码的结束位置和异常位置.每一个对象都与一个monitor相关联,当monitor被只有后,它将处于锁定状态. 当一个线程试图访问同步代

synchronized和volatile使用

synchronized和volatile volatile :保证内存可见性,但是不保证原子性: synchronized:同步锁,既能保证内存可见性,又能保证原子性: synchronized实现可重入锁 (1.持有同一锁自动获取   2.继承锁) 锁定的对象有两种:1.类的实例(对象锁) 2.类对象(类锁) 对象锁(synchronized修饰普通方法或代码块)    对象锁已被其他调用者占用,则需要等待此锁被释放  /** * 对象锁的两种方式 */ //方式一 private int

java 语言多线程可见性(synchronized 和 volatile 学习)

共享变量可见性实现的原理 java 语言层面支持的可见性实现方式: synchronized volatile 1. synchronized 的两条规定: 1 线程解锁前,必须把共享变量的最新值刷新到主内存中. 2 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(加锁与解锁需要是同一锁) 线程解锁前对共享变量的修改在下次加锁时对其他线程可见. 2. volatile 实现可见性 深入来说,通过加入内存屏障和禁止重排序优化来实现 的. 对volatil

关于synchronized与volatile的一点认识

贪婪是一种原罪,不要再追求性能的路上离正确越来越远. 内存模型 java内存模型 重排序 锁synchronized 什么是锁 独占锁 分拆锁 分离锁 分布式锁 volatile 内存模型 java内存模型 提到同步.锁,就必须提到java的内存模型,为了提高程序的执行效率,java也吸收了传统应用程序的多级缓存体系. 在共享内存的多处理器体系架构中,每个处理器都拥有自己的缓存,并且定期地与主内存进行协调.在不同的处理器架构中提供了不同级别的缓存一致性(Cache Coherence),其中一部

Java线程(二):线程同步synchronized和volatile

上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程共享的,一个线程改变了其成员变量num值,下一个线程正巧读到了修改后的num,所以会递增输出. 要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性.多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现.拿上篇博文中的例子来说明,在多个线程之间共享了Count类的

java多线程之内存可见性-synchronized、volatile

1.JMM:Java Memory Model(Java内存模型) 关于synchronized的两条规定: 1.线程解锁前,必须把共享变量的最新值刷新到主内存中 2.线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁需要是同一把锁) 注:线程解锁前对共享变量的修改在下次加锁时对其他线程可见 2.线程执行互斥代码的过程: 1.获得互斥锁 2.清空工作内存 3.从主内存拷贝变量的最新副本到工作内存 4.执行代码 5.将更改后的共享变量的值刷

synchronized和volatile的使用方法以及区别

先看看下面的例子: public class ThreadTest { public static void main(String[] args) { final Counter counter = new Counter(); for (int i = 0; i < 1000; i++) { new Thread(new Runnable() { @Override public void run() { counter.inc(); } }).start(); } System.out.p