关于synchronized与volatile的一点认识

贪婪是一种原罪,不要再追求性能的路上离正确越来越远。

内存模型

java内存模型

提到同步、锁,就必须提到java的内存模型,为了提高程序的执行效率,java也吸收了传统应用程序的多级缓存体系。

在共享内存的多处理器体系架构中,每个处理器都拥有自己的缓存,并且定期地与主内存进行协调。在不同的处理器架构中提供了不同级别的缓存一致性(Cache Coherence),其中一部分只提供最小的保证,即允许不同的处理器在任意时刻从同一个存储位置上看到不同的值。操作系统、编译器以及运行时(有时甚至包括应用程序)需要弥合这种在硬件能力与线程安全之间的差异。

要想确保每个处理器都能在任意时刻知道其他处理器正在进行的工作,将需要非常大的开销。在大多数时间里,这种信息是不必要的。因此处理器会适当放宽存储一致性保证,以换取性能的提升。在架构定义的内存模型中将告诉应用程序可以从内存系统中获得怎样的保证,此外还定义了一些特殊的指令(称为内存栅栏),当需要共享数据时,这些指令就能实现额外的存储协调保证。为了使java开发人员无须关心不同架构内存模型之间的差异,Java还提供了自己的内存模型,并且JVM通过在适当的位置上插入内存栅栏来屏蔽在JVM与底层之平台内存模型之间的差异。

经过上面的讲解和上图,我们知道线程在运行时候有一块内存专用区域,Java程序会将变量同步到线程所在的内存。这时候会操作工作内存中的变量,而线程中的变量何时同步回到内存是不可预期的。但是java内存模型规定,通过关键词”synchronized“、”volatile“可以让java保证某些约束。

“volatile” - 保证读写的都是主内存变量。

“synchronized” - 保证在块开始时,都同步主内存值到工作内存,而快结束时,将工作内存同步会主内存。

重排序

public class PossibleReordering {
    static int x = 0,y=0;
    static int a=0,b=0;
    public static void main(String[] args) throws InterruptedException {
        Thread one = new Thread(new Runnable() {

            @Override
            public void run() {
                a = 1;
                x = b;
            }
        });

        Thread two = new Thread(new Runnable() {

            @Override
            public void run() {
                b = 2;
                y = a;
            }
        });
        one.start();two.start();
        one.join();two.join();
        System.out.println("x:" + x+",y:"+y);
    }
}

重排序。如上图,执行结果,一般人可能认为是1,1;真正的执行结果可能每次都不一样。拜JMM重排序所赐,JMM使得不同线程的操作顺序是不同的,从而导致在缺乏同步的情况下,要推断操作的执行结果将变得更加复杂。各种使操作延迟或看似乱序执行的不同原因,都可以归为重排序。内存级的重排序会使程序的行为变得不可预测。如果没有同步,要推断出程序的执行顺序是非常困难的,而要确保在程序中正确的使用同步却是非常容易的。同步将限制编译器和硬件运行时对内存操作重排序的方式。

锁synchronized

锁实现了对临界资源的互斥访问,被synchronized修饰的代码只有一条线程可以通过,是严格的排它锁、互斥锁。没有获得对应锁对象监视器(monitor)的线程会进入等待队列,任何线程必须获得monitor的所有权才可以进入同步块,退出同步快或者遇到异常都要释放所有权,JVM规范通过两个内存屏障(memory barrier)命令来实现排它逻辑。内存屏障可以理解成顺序执行的一组CPU指令,完全无视指令重排序。

什么是锁

public class TestStatic {
	public syncronized static void write(boolean flag) {
		xxxxx
	}
	public synchronized static void read() {
		xxxxx
	}
}

线程1访问TestStatic.write()方法时,线程2能访问TestStatic.read()方法吗

线程1访问new TestStatic().write()方法时,线程2能访问new TestStatic().read()方法吗

线程1访问TestStatic.write()方法时,线程2能访问new TestStatic().read()方法吗

public class Test {
	public syncronized void write(boolean flag) {
			xxxxx
	 }
	public synchronized void read() {
		xxxxx
	}
}

Test test = new Test();线程1访问test.write() 方法,线程2能否访问test.read()方法

Test a = new Test(); Test b = new Test();线程1访问a.write()访问,线程2能否访问b.read()方法

答案,java中每个对象都可以作为一个锁,而对象就决定了锁的粒度大小。

对于实例同步方法,锁是当前对象。

对于静态方法,锁是TestSTatic.class对象

对于同步代码块,锁是Synchronized括号里面配置的对象

TestStatic类,1问,作用范围全体class对象,线程1拿到,线程2就不能拿到

2问,3问同上

Test类,1问,不能,锁都是实例对象test,线程1拿到锁之后,线程2无法访问

2问,可以,线程1锁是实例a,线程2是实例b。

独占锁

如果你不敢确定该用什么锁,就用这个吧,在保证正确的前提下,后续在提高开发效率。

public class ServerStatus {
	public final Set<String> users;
	public final Set<String> quers;
	public synchronized void addUser(String u ) {
		users.add(u);
	}
	public synchronized void addQuery(String q ) {
		quers.add(q);
	}
	public synchronized void removeUser(String u) {
		users.remove(u);
	}
	public synchronized void removeQuery(String q) {
		quers.remove(q);
	}
}

分拆锁

如果在整个应用程序只有一个锁,而不是为每个对象分配一个独立的锁,那么所有同步代码块的执行就会变成串行化执行。由于很多线程都会竞争同一个全局锁,因此两个线程同时请求这个锁的概率将会剧增,从而导致更严重的竞争。所以如果将这些锁请求分到更多的锁上,就能有效降低锁竞争程度。由于等待而被阻塞的线程将更少,从而可伸缩性将提高。

上文中users、quers是两个相互独立的变量,可以将此分解为两个独立的锁,每个锁只保护一个变量,降低每个锁被请求的频率。

public class ServerStatus {
	public final Set<String> users;
	public final Set<String> quers;
	public void addUser(String u ) {
		synchronized(users) {
			users.add(u);
	  	}
	}
	public void addQuery(String q ) {
		synchronized(quers) {
			quers.add(q);
		}
	}
	public void removeUser(String u) {
		synchronized(users) {
			users.remove(u);
		}
	}
	public void removeQuery(String q) {
		synchronized(quers) {
			quers.remove(q);
		}
	}
}

分离锁

在某些情况下,可以将锁分解技术进一步扩展为对一组独立对象上的锁进行分解,这种情况称为锁分段。例如ConcurrencyHashMap是有一个包含16个锁的数组实现,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设所有关键字都时间均与分布,那么相当于把锁的请求减少到原来的1/16,可以支持多达16个的并发写入。

锁分段的劣势在于:与采用单个锁来实现独占访问相比,要获取多个锁来实现独占访问将更加困难并且开销更高,比如计算size、重hash。

分布式锁

zookeeper,判断临时节点是否存在,存在就说明已经有人争抢到锁;不存在就创建节点,表明拥有该锁。

记下,以后详细研究

https://github.com/qhwj2006/ZookeeperDistributedLock

https://github.com/s7/scale7-cages

volatile

volatile是比synchronized更轻量级的同步原语,volatile可以修饰实例变量、静态变量、以及数组变量(网上大牛说,维护的是引用,但是里面的对象。。。嘿嘿嘿)。被volatile修饰的变量,JVM规范规定,一个线程在修改完,另外的线程能读取最新的值。

但仅仅保证可见性,不保证原子性,所以volatile通常用来修饰boolean类型或者状态比较少的数据类型,而且不能用来更新依赖变量之前值的操作(例volatile++)。

volatile内部仅仅是对变量的操作多了一条cpu指令(lock#指令),它会强制写数据到缓存,如果缓存数据同时也在主存,会强制写数据更新到主存,并且使所有持有该主存数据地址的缓存统统失效,触发其他持有缓存数据的线程从主存获取最新数据,从而实现同步。

关于synchronized与volatile的一点认识

时间: 2024-10-28 20:32:57

关于synchronized与volatile的一点认识的相关文章

Java中的synchronized、volatile、ReenTrantLock、AtomicXXX

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

Synchronized、volatile与锁

时间尚早,今天多写一点 温故知新(三) Synchronized与volatile Synchronized有以下两个含义: 一.一次只有一个线程可以执行代码的受保护部分 二.一个线程更改的数据对于其他线程是可见的 volatile只适合于控制对基本变量(int.boolean等)的单个实例的访问.当一个变量被声明为volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主内存,而任何对改变量的读取也都绕过高速缓存,直接取自主内存.这表示所有线程在任何时候看到的volatile变量值都相

多线程三: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

4个点说清楚Java中synchronized和volatile的区别

作者 : Hollis 回顾一下两个关键字:synchronized和volatile 1.Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized.volatile.final.concurren包等. 2.synchronized通过加锁的方式,使得其在需要原子性.可见性和有序性这三种特性的时候都可以作为其中一种解决方案,看起来是“万能”的.的确,大部分并发控制操作都能使用synchronized来完成. 3.volat

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

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

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

synchronized和volatile

简述synchronized和volatile的区别 1.synchronized可以使用在变量.方法.类级别,而volatile只能使用在变量级别 2.synchronized可以保证变量修改的可见性和原子性,而volatile只能保证变量修改的可见性 3.synchronized有可能造成线程的阻塞,而volatile不会 4.synchronized标记的变量会被编译器优化,而volatile标记的变量不会被优化 5.volatile的变量若与自身相关,如以下的声明方式:n=n+1,n++

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

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