精华内容
下载资源
问答
  • 一、volatile介绍 ...相比于synchronized加锁同步,volatile关键字比较轻量级,开销更低,因为他不会引起线程上下文的切换调度。 2、volatile定义 Java编程语言允许线程访问共享变量,为了确保共享变量...

    一、volatile介绍

    1、什么是volatile

    volatile是Java提供的一种轻量级的同步机制。Java语言包含两种同步机制:1、同步块(或同步方法) 2、volatile变量。相比于synchronized加锁同步,volatile关键字比较轻量级,开销更低,因为他不会引起线程上下文的切换调度。

    2、volatile定义

    Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。

    3、volatile关键字的作用

    volatile关键字保证了共享变量在多个线程中的可见性。当一个线程修改了共享变量的值时,其他的线程能够看见这个值的改变,并且将重新读取该值。

    二、多线程中如何操作共享变量

    在这里插入图片描述
    说明: 共享变量一般是存放在主存中,在多线程中每一个要使用到共享变量的线程都需要将主存中的共享变量拷贝一份放入到自己的工作内存中(线程私有,高速缓存)。每一个线程中都是读取的自己工作内存里共享变量的副本值来进行相关操作,而不会每次都会去访问主存取值(CPU直接访问主存效率低下)。
    问题: 如果现在有一个线程(线程A)修改了自己的值。但是别的线程都不知道该值已经改变了,所以还是继续延用他们各自从主存中拷贝过来的共享变量值的副本。这样就会造成一些多线程相关的问题。

    三、代码演示

    1、示例代码

    public class TestVolatitle1 {
    
    	public static void main(String[] args) {
    		ThreadDemo td = new ThreadDemo();
    		Thread threadA = new Thread(td);
    		threadA.start();
    		
    		while (true) {
    			if (td.isFlag()) {
    				System.out.println("------------------");
    				break;
    			}
    		}
    	}
    }
    
    class ThreadDemo implements Runnable {
    
    	private boolean flag = false;
    	@Override
    	public void run() {
    		try {
    			Thread.sleep(200);
    		} catch (InterruptedException e) {}
    		flag = true;
    		System.out.println("flag=" + isFlag());
    	}
    
    	public boolean isFlag() {
    		return flag;
    	}
    
    	public void setFlag(boolean flag) {
    		this.flag = flag;
    	}
    }
    

    代码解释:
    1、在以上代码中,一共有两个线程,一个是我们手动开启的线程threadA,一个是主线程。ThreadDemo td = new ThreadDemo(); 是创建一个共享变量td。
    2、在线程threadA中会执行 run() 方法,先休眠200毫秒,然后将flag的值改变为true。
    3、在主线程中while循环会一直不停的判断共享变量的flag值是否为true,若为true则结束程序。
    运行结果:
    在这里插入图片描述
    点击运行该程序以后发现,运行结果并不是我们想象的那样。主线程中的while循环一直不会结束。
    问题产生原因:
    首先线程threadA和主线程都去主存中拷贝一份共享变量的值,放入到自己的工作内存中。线程threadA将自己工作内存中的flag值改变成了true。但是主线程工作内存中的值并没有改变(依然是false),所以while循环就会一直循环不会结束。
    问题解决:
    当我们给共享变量flag加上volatile关键字修饰以后,程序正常运行并结束了。

    四、volatile关键字详解

    1、详解

    从以上的代码示例中,我们看见给共享变量添加了volatile关键以后程序就正常运行结束了。那么volatile关键字到底是如何工作的呢?通过上面的代码示例来说明一下加入了volatile关键字以后的程序流程。
    在这里插入图片描述
    1、给共享变量添加了volatile关键字修饰后。首先,线程threadA和主线程都会去主存中拷贝一份共享变量的值。
    2、然后在线程A中将flag的值改变了,同时主线程里面的while循环也一直在执行。线程A将共享变量的flag值改变为true了。然后线程A就将flag改变后的值刷写到主存中。
    3、当主存中共享变量的值更改以后,会导致其他线程中的共享变量副本失效,这时候其他线程需要重新从主存中读取一次共享变量的值到线程的工作内存中。
    4、当主线程重新从主存读取共享变量的值以后,读取到的flag的值为true,此时while循环就结束了。

    2、volatile底层实现

    volatile是如何来保证可见性的呢?让我们在X86处理器下通过工具获取JIT编译器生成的汇编指令来查看对volatile进行写操作时,CPU会做什么事情呢?

    当有volatile变量修饰时会出现汇编指令lock addl $0×0,(%esp),Lock前缀的指令在多核处理器下会引发了两件事情

    1)将当前处理器缓存行的数据写回到系统内存。
    2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。

    为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

    3、指令重排序

    重排序分为编译重排序和运行重排序两种。下面主要记录编译重排序!

    编译期重排: 编译源代码时,编译器依据对上下文的分析,对指令进行重排序,以之更适合于CPU的并行执行。

    运行期重排: CPU在执行过程中,动态分析依赖部件的效能,对指令做重排序优化

    3.1、什么是指令重排序

    JVM为了尽可能的使CPU得到最大的利用率,Java虚拟机在不影响单线程程序执行结果的前提下,尽可能的提高并行度,所以JVM会按照自己的一些规则将程序编写的顺序打乱,比如:写在后面的代码在时间顺序上可能先执行,写在前面的代码在时间顺序上可能会后执行。

    举例说明:

    double r = 2.1; //(1) 
    double pi = 3.14;//(2) 
    double area = pi*r*r;//(3)
    

    在我们程序中编写了如下代码,代码中定义的执行顺序为(1)—>(2)—>(3),在单线程环境中顺序(1)—>(2)—>(3)和顺序(2)—>(1)—>(3)对执行结果并无影响。(1)和(2)中并不存在着 **数据依赖关系。**所以JVM可能会对(1)、(2)两步打乱顺序执行来优化程序效率。

    所以,编译时指令重排序可以理解为JVM为了提高程序效率而可能会将不存在数据依赖关系的代码进行执行顺序的调整的过程。

    3.2、什么情况可能会造成指令重排序呢

    1、一个不是原子的操作,可能会给JVM留下重排序的可能。

    2、不存在数据依赖关系的代码语句可能会给JVM留下重排序的可能。

    3.3、指令重排序带来的影响

    指令重排序在单个线程中不会引发问题,但是到了多线程中由于指令重排序的原因可能就会引发一系列的异常。 在Java内存模型(JMM)中有这么一句话很好的描述了重排序的影响:如果在本线程内观察,所有操作都是有序的,如果在一个线程中观察另一个线程,所有操作都是无序的。

    有序性是指对于单线程的执行代码,执行是按顺序依次进行的。但在多线程环境中,则可能出现乱序现象,因为在编译过程会出现“指令重排”,重排后的指令与原指令的顺序未必一致。因此,前半句指的是线程内保证串行语义执行,后半句则指指“令重排现”象和“工作内存与主内存同步延迟”现象。

    一个经典的重排序案例:

    public class SingleTon {
    
    	private static SingleTon singleTon;
    
    	private SingleTon() {}
    
    	public SingleTon getInstance() {
    
    		if (singleTon == null) {
    			synchronized (SingleTon.class) {
    				if (singleTon == null) {
    					//非原子操作
    					singleTon = new SingleTon();
    				}
    			}
    		}
    		return singleTon;
    	}
    }
    

    问题分析:

    上面是一个双重检查的单例模式,由于使用了双重检查保证了整个上下文环境中只有一个 SingleTon对象,但是在多线程环境中,由于 singleTon = new SingleTon();不是一个原子操作,所以经过JVM的指令重排序以后仍然会引发问题。

    singleTon = new SingleTon();语句分为三步:

    (1)、在堆内存开辟一块空间

    (2)、SingleTon对象初始化

    (3)、栈中的 singleTon指向刚刚分配的内存地址

    在上面三步中(2)依赖于(1),但是(3)不依赖于(2),所以JVM就可以将这三个步骤进行重排序。假如经过JVM一番重排序后,上面的顺序变成了(1)—>(3)—>(2)。此时有两个线程,线程A和线程B需要获取该单例对象, A线程抢到锁执行同步块中的语句,A线程正在执行(1)—>(3)—>(2)中的(3),此时B线程开始执行了,B线程在锁外面开始做第一次判断 if (singleTon == null),判断结果为false,然后直接返回singleTon进行使用,但是这时候singleTon还没有初始化,导致出错。

    解决办法:
    如果使用volatile关键字修饰singleTon变量,禁止JVM进行重排序,使得singleTon在读、写操作前后都会插入内存屏障,避免重排序。

    3.4、小结
    1. 所以,当使用了volatile关键字修饰共享变量以后。每一个线程对共享变量做的更改操作都会被重新刷写到主存中,并且当主存中共享变量的值改变以后,其他线程中的共享变量副本就失效了,需重新从主存中读取值。
    2. 用volatile关键字的变量的值只要一经改变就会自动刷写到主存中,而不会等待改变该值的线程执行完毕再刷写。
    3. 当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile;

    五、volatile的优缺点

    • 优点: volatile是轻量级同步机制。在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,是一种比synchronized关键字更轻量级的同步机制。
    • 缺点:volatile只能保证变量的可见性,无法保证原子性。除此之外,由于volatile屏蔽掉了VM中必要的代码优化,所以在效率上会稍微低点。这是两个缺点。volatile禁止了JVM底层的指令重排序优化,造成了一定的性能损耗,但是在多线程环境中提高了安全性。
    展开全文
  • 电脑的8个CPU可同时处理3000多个线程,CPU执行的速度的很快,在执行中不断的切换,看起来像是在同时处理。 二、线程的创建 在 Java程序中,有两种方法创建线程: 1.对 Thread 类进行派生并覆盖 run方法,例: public...

    一、进程和线程
    进程是指一个内存中运行的应用程序,而线程是指进程中的一个执行流程。
    一个程序有一个或者多个进程,一个进程有一个或者多个线程。
    电脑的8个CPU可同时处理3000多个线程,CPU执行的速度的很快,在执行中不断的切换,看起来像是在同时处理。

    二、线程的创建
    在 Java程序中,有两种方法创建线程:
    1.对 Thread 类进行派生并覆盖 run方法,例:

    public class 线程名 extends java.lang.Thread{
        //重写run方法
    	public void run(){
    	}
    }
    

    2.通过实现Runnable接口创建,例:

    public class 线程名 implements Runnable {
    	//重写run方法
    	public void run(){
    	}
    }
    

    三、线程的使用
    写一个枫叶落下的效果,每点击一下按钮或页面则会产生一个线程,生成一个下落的枫叶。

    首先,创建JFrame添加背景按钮等

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Font;
    import java.awt.Graphics;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    
    public class UI extends JFrame{
    	Color color1 = new Color(106,163,216);
    	Color color2 = new Color(233,218,144);
    	Color color3 = new Color(180,180,180);
    	Font font1 = new Font("华文中魏",Font.BOLD,30);
    	
    	public void showUI() {
    		this.setTitle("落叶");
    		this.setSize(1200,800);
    		this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    		this.setLocationRelativeTo(null);
    	
    		Listener lis = new Listener();
    		JButton but1 = new JButton("枫叶");
    		but1.setFont(font1);
    		but1.setPreferredSize(new Dimension(100,50));
    		but1.setBackground(color2);
    		but1.addActionListener(lis);
    		
    		JPanel buttonPanel = new JPanel();
    		buttonPanel.setPreferredSize(new Dimension(0,60));
    		this.add(buttonPanel,BorderLayout.NORTH);
    		buttonPanel.setVisible(true);
    		buttonPanel.setBackground(color3);
    		JPanel showPanel = new JPanel();
    		this.add(showPanel,BorderLayout.CENTER);
    		showPanel.addMouseListener(lis);
    		buttonPanel.add(but1);	
    		this.setVisible(true);
    		Graphics g = showPanel.getGraphics();
    		lis.g = g;
    	}
    	
    	public void paint(Graphics g) {
    		super.paint(g);
    		g.setColor(color1);
    		g.fillRect(0,112,this.getWidth(),this.getHeight());
    	}
    
    	public static void main (String[] args) {
    		UI ui = new UI();
    		ui.showUI();
    	}
    }
    
    

    动作监听器和鼠标监听器代码,每次点击新建一个线程来控制新的枫叶。点击按钮,在上方随机位置生成枫叶,点击其他位置则在点击位置生成枫叶。

    
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseListener;
    
    
    public class Listener implements ActionListener,MouseListener {
    	Graphics g;
    	int x,y;
    
    	public void actionPerformed(ActionEvent e) {
    		java.util.Random ran=new java.util.Random();
        	int x=ran.nextInt(1200);
        	int y=0;
        	DrawThread dt=new DrawThread(g,x,y);
            dt.start();//启动线程,调用start   
            //System.out.println("己启动一个线程画一片落叶");
    	}
    	
    	public void mouseClicked(MouseEvent e) {
    		x=e.getX()-50;
    		y=e.getY()-50;
    		DrawThread dt=new DrawThread(g,x,y);
    		dt.start();
    		
    	};
        public void mousePressed(MouseEvent e) {};
        public void mouseReleased(MouseEvent e) {};
        public void mouseEntered(MouseEvent e) {}; 
        public void mouseExited(MouseEvent e) {};
    
    }
    
    

    接下来是线程部分代码,关键部分是重写run方法,注意重写run方法的格式为public void run(){},()中不能加入任何参数。

    
    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.Image;
    import javax.swing.ImageIcon;
    import javax.swing.JFrame;
    
    public class DrawThread extends java.lang.Thread{
    	private int x,y;
    	private Graphics g;
    	JFrame ui;
    	Color color1 = new Color(106,163,216);
    	public DrawThread(Graphics g,int x,int y) {
    		this.g=g;
    		this.x=x;
    		this.y=y;
    	}
    	
    	public void draw(Graphics g) {
    		Image image = new ImageIcon("立秋枫叶.png").getImage();
    		Image image1 = new ImageIcon("蓝色.png").getImage();
    		for(int i=0;i<1000;i++){
        	     g.drawImage(image,x,y+i,100,100,null);
       	         //每画一个,停一下
       	         try{
       	    	     Thread.sleep(60);
       	         }catch(Exception ef){}
       	         g.drawImage(image1,x,y+i,100,100,null);
         	}
    	}
    	//重写run方法
    	public void run(){
    		draw(g);
    	}
    
    }
    

    以下是枫叶下落的效果图:看下效果

    上传png图片,不知道下载之后还是不是透明背景。蓝色.png
    立秋枫叶.png

    四、volatile关键字
    volatile关键字可以使一个变量在多个线程中可见,也就是当一个线程修改了某个变量的值,这新值对其他线程来说立即可见。
    这主要是因为:使用volatile关键字,会强制将修改的值立即写入主存,并且让缓存行无效,然后其他线程去读就会读到新的值。

    展开全文
  • Java线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转环需要相对比较长的时间,时间成本...

    目录:

    【Java锁体系】一、悲观锁和乐观锁

    【Java锁体系】二、Java中的CAS机制算法

    【Java锁体系】三、自旋锁详解

    【Java锁体系】四、Synchronized关键字详解

    【Java锁体系】五、隐式锁和显氏锁的区别(Synchronized和Lock的区别)

    【Java锁体系】六、AQS原理分析(AbstractQueuedSynchronizer实现分析)

    【Java锁体系】七、JMM内存模型详解

    【Java锁体系】八、MESI缓存一致性协议讲解

    【Java锁体系】九、volatile关键字

    【Java锁体系】四、Synchronized关键字详解

    补充知识:

    synchronized是JVM的内置锁,通过内部对象Monitor(监视器锁)实现的,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁的实现)。

    > [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l5ltdW8E-1608211820954)(D:\software\typora\workplace\imgs_cas\19.png)]

    每一个同步对象都有一个Monitor(监视器锁)

    > [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WJLN5NiM-1608211820957)(D:\software\typora\workplace\imgs_cas\20.png)]

    那么如何知道一个对象是否加锁了呢?

    这里就需要引入对象的内存结构来看:

    > [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-45U76RiF-1608211820959)(D:\software\typora\workplace\imgs_cas\21.png)]

    对象的内存结构:

    • 对象头:比如hash码,对象所属的年代,对象锁,锁状态标志
    • 对象实例数据:即创建对象时,对象中成员变量、方法。
    • 对齐填充

    那么对象具体是如何存储的呢?

    具体详情可看【JVM篇】一、初识JVM

    • 对象的引用存储在栈空间中
    • 对象的实例数据存储在堆空间中
    • 对象中的元数据存储在方法区中

    具体对象是如何存储锁信息的可看后面的知识点。

    synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

    Java线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转环需要相对比较长的时间,时间成本相对较高。

    但在Java1.6之后Java官方从JVM层面对synchronized较大优化,所以现在的synchronized锁效率也优化的很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

    1.synchronized关键字的使用

    synchronized有以下三种作用范围:

    • 在静态方法上加锁
    • 在非静态方法上加锁
    • 在代码块上加锁
    public class SynchronizedSample {
    
        private final Object lock = new Object();
    
        private static int money = 0;
    		//非静态方法
        public synchronized void noStaticMethod(){
            money++;
        }
    		//静态方法
        public static synchronized void staticMethod(){
            money++;
        }
    		
        public void codeBlock(){
          	//代码块
            synchronized (lock){
                money++;
            }
        }
    }
    
    

    那synchronized这三种作用范围的加锁方式的区别吗?

    锁都是加在对象上面的。

    • 对于静态方法,锁是当前类的class对象;
    • 对于非静态方法,锁是当前实例对象;
    • 对于同步方法快,锁是synchronized括号里面配置的对象。

    那么JVM在实现这三种synchronized的实现方式一样吗?

    JVM基于进入和退出Monitor对象来实现同步和代码块同步,但是两者的实现细节不一样。

    对代码编译成class文件之后,通过javap -v SynchronizedSample.class 来看下synchronized 到底在源码层面如何实现的?

    • 在代码块上是通过monitorenter和monitorexit指令实现的
    • 在静态方法和非静态方法上是通过ACC_SYNCHRONIZED实现的。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KDOmUtp3-1608211820962)(D:\software\typora\workplace\imgs_cas\22.jpg)]

    构造方法不能使用 synchronized 关键字修饰。

    构造方法本身就属于线程安全的,不存在同步的构造方法一说。

    2.synchronized实现对象单例模式

    补充:单例模式定义-一个类有且仅有一个实例,并且自行实例化向整个系统提供。

    其实现方式主要是通过饿汉式(线程安全的)和懒汉式(线程不安全的)。

    饿汉式

    package com.lcz.syn;
    
    // 单例模式实现方式-饿汉式
    class SingleOne{
       // 创建类中私有构造
        private SingleOne(){
    
        }
        // 创建私有对象
        private static SingleOne instance = new SingleOne();
    
        // 创建公有静态返回
        public static SingleOne getInstance(){
            return instance;
        }
    }
    
    public class Test1 {
        // 主函数
        public static void main(String[] args){
            SingleOne singleOne1 = SingleOne.getInstance();
            SingleOne singleOne2 = SingleOne.getInstance();
            System.out.println(singleOne1 == singleOne2);
        }
    }
    
    

    懒汉式

    // 单例模式实现方式-懒汉式
    class SingeleTwo{
        // 创建类中私有构造
        private SingeleTwo(){
    
        }
        // 创建静态私有对象
        private static SingeleTwo instance;
        // 创建返回
        public static SingeleTwo getInstance(){
            if (instance==null){
                instance = new SingeleTwo();
            }
            return instance;
        }
    }
    
    public class Test1 {
        // 主函数
        public static void main(String[] args){
            SingeleTwo singleOne1 = SingeleTwo.getInstance();
            SingeleTwo singleOne2 = SingeleTwo.getInstance();
            System.out.println(singleOne1 == singleOne2);
        }
    }
    

    但是懒汉式在多线程下不安全。

    这里通过双重校验锁使其安全。

    双重校验锁实现懒汉式

    // 双重校验锁实现
    class Singleton{
        //私有化
        private Singleton(){
    
        }
        // 私有化对象
        private volatile static Singleton instance;
    
        // 返回方法
        public static Singleton getInstance(){
            if (instance==null){
                // 加锁
                synchronized (Singleton.class){
                    if (instance==null){
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    
    }
    

    1.为什么使用volatile修饰了singleton引用还用synchronized?

    volatile 只保证了共享变量 singleton 的可见性,但是 singleton = new Singleton(); 这个操作不是原子的,可以分为三步:

    步骤1:在堆内存申请一块内存空间;

    步骤2:初始化申请好的内存空间;

    步骤3:将内存空间的地址赋值给 singleton;

    所以singleton = new Singleton(); 是一个由三步操作组成的复合操作,多线程环境下A 线程执行了第一步、第二步之后发生线程切换,B 线程开始执行第一步、第二步、第三步(因为A 线程singleton 是还没有赋值的),所以为了保障这三个步骤不可中断,可以使用synchronized 在这段代码块上加锁。

    2.第一次检查singleton为空后为什么内部还进行第二次检查

    A 线程进行判空检查之后开始执行synchronized代码块时发生线程切换(线程切换可能发生在任何时候),B 线程也进行判空检查,B线程检查 singleton == null 结果为true,也开始执行synchronized代码块,虽然synchronized 会让二个线程串行执行,如果synchronized代码块内部不进行二次判空检查,singleton 可能会初始化二次。

    3.volatile 除了内存可见性,还有别的作用吗?

    volatile 修饰的变量除了可见性,还能防止指令重排序。

    指令重排序 是编译器和处理器为了优化程序执行的性能而对指令序列进行重排的一种手段。现象就是CPU 执行指令的顺序可能和程序代码的顺序不一致,例如 a = 1; b = 2; 可能 CPU 先执行b=2; 后执行a=1;

    singleton = new Singleton(); 由三步操作组合而成,如果不使用volatile 修饰,可能发生指令重排序。步骤3 在步骤2 之前执行,singleton 引用的是还没有被初始化的内存空间,别的线程调用单例的方法就会引发未被初始化的错误。

    3.synchronized关键字的底层实现

    • 在JDK6之前,synchronized那是还是重量级锁,每次加锁都依赖操作系统Mutex Lock实现,设计到操作系统让线程从用户态切换到内核态,切换成本很高l
    • 在JDK6之后,synchronized的锁主要有四种状态:无锁状态、偏向锁状态、重量级锁状态,会随着竞争的激烈而逐渐升级,注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。因为sun程序员发现大部分程序大多数时间不会发生多个线程同时访问静态资源的情况。

    (1)预备知识-Java对象头(存储锁类型)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ODJrIxSX-1608211820964)(D:\software\typora\workplace\imgs_cas\23.png)]

    对象存储在堆中,主要分为三部分内容:对象头、对象实例化数据和对齐填充。

    • 对象头

      对象头分为二个部分,Mard Word 和 Klass Word(若是数组对象还多了一个区别:记录数组长度),列出了详细说明:

      对象头结构 存储信息-说明
      Mard Word 存储对象的hashCode、锁信息或分代年龄或GC标志, monitor对象 地址等信息
      Class Metadata Address 存储指向对象所属类(元数据)的指针,JVM通过这个确定这个对象属于哪个类
    • 对象实例数据

      如上图所示类中的成员变量data就属于对象实例化数据;

    • 对齐填充

      JVM要求对象占用的空间必须是8的倍数,方便内存分配,因此这部分就是用于填满不够的空间的凑数用的。

    (2)预备知识-Monitor

    每一个对象都有一个与之关联的Monitor对象,Monitor对象属性如下所示:

    ObjectMonitor() {
        _header       = NULL;
        _count        = 0;   // 重入次数
        _waiters      = 0,   // 等待线程数
        _recursions   = 0;
        _object       = NULL;
        _owner        = NULL;  // 当前持有锁的线程
        _WaitSet      = NULL;  // 调用了 wait 方法的线程被阻塞 放置在这里
        _WaitSetLock  = 0 ;
        _Responsible  = NULL ;
        _succ         = NULL ;
        _cxq          = NULL ;
        FreeNext      = NULL ;
        _EntryList    = NULL ; // 等待锁 处于block的线程 有资格成为候选资源的线程
        _SpinFreq     = 0 ;
        _SpinClock    = 0 ;
        OwnerIsThread = 0 ;
      }
    
    

    对象关联的ObjectMonitor对象有一个线程内部竞争锁的机制。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o0Afk3Me-1608211820966)(D:\software\typora\workplace\imgs_cas\24.png)]

    (3)JDK1.6对synchronized的变化

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GuEguTne-1608211820967)(D:\software\typora\workplace\imgs_cas\25.png)]

    I.适应性自旋锁

    自旋锁的思想:让一个线程在请阿牛一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可以避免进入阻塞状态

    自旋锁的缺点:需要进行忙循环操作占用CPU时间,它只适用于共享数据的锁定状态很短的场景。

    适应性自旋锁:若锁被其它线程长时间占用,会带来许多性能上的开销,所以自旋的次数不再固定。由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定。

    II.锁消除

    JIT编译时,会去除不可能存在竞争的锁。通过JIT的逃逸分析消除一些没有在当前同步块以外被其它线程共享的数据的锁的保护,通过逃逸分析在TLAB来分配对象,这样就不存在共享数据带来的线程安全问题。

    III.锁粗化

    减少不必要的紧连载一起的lock,unlock操作,将多个连续的锁扩展成一个范围更大的锁。

    IV.偏向锁、轻量级锁、重量级锁

    偏向锁:只有一个线程进入临界区;

    轻量级锁:多个线程交替进入临界区;

    重量级锁:多个线程同时进入临界区;

    背景 :研究者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。

    偏向锁

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bKVyhOd8-1608211820968)(D:\software\typora\workplace\imgs_cas\26.jpg)]

    **偏向锁由来:**假设整个过程只有一个线程获得锁,那么没必要对一个线程进行多次的加锁和解锁。

    偏向锁的使用:

    • 首先线程A访问同步代码块, 使用CAS操作将Thread ID放到Mark Word当中;
    • 如果CAS成功,则此时线程A就获取了锁;
    • 如果CAS失败,证明有别的线程持有锁,此时启动偏向锁撤销。

    偏向锁的撤销:

    • 如果第一个线程A已经执行完毕,那么CAS操作将Mark Word设置为null,第二个线程获取偏向锁,此时不会升级成轻量级锁;
    • 如果第一个线程A未执行完毕,那么此时第二个线程获取锁失败,会对A线程在全局安全点阻塞,并升级为轻量级锁。

    轻量级锁

    **轻量级锁由来:**假设多线程竞争是互相错开的,不会发生线程阻塞,那么上下文切换(用户态到内核态)是多余的。

    轻量级锁的特点:

    • 采用了CAS操作加锁和解锁,由于轻量级锁的锁记录(Lock Record)是存放在对象头和线程空间里,因此加锁和解锁不需要上下文切换,性能消耗较少;
    • 一旦发生多线程竞争,首先基于“自旋锁”思想,自旋CPU旋转等待一段时间,不会发生上下文切换,如果还是无法获得锁,那么将锁升级为重量级锁。

    重量级锁

    重量级锁由来: 当同一时刻有不同的线程尝试获取锁,那么就会从轻量级锁上升为重量级锁。

    重量级锁的特点: 依赖于底层操作系统的Mutex Lock,线程会被阻塞,加锁和解锁都需要从用户态切换到内核态,性能消耗较大。

    偏向锁、轻量级锁、重量级锁的三者对比

    • 偏向锁:只需要判断Mark Word中的一些值是否正确就行。

      只有一个线程访问同步块时,使用偏向锁。

      适用于一个线程访问

    • 轻量级锁:需要执行CAS操作自旋来获取锁

      如果执行同步代码块的时间比较少,那么多个线程之间使用轻量级锁交替执行。

      追求响应时间

    • 重量级锁:会发生上下文切换,CPU状态会从用户态转为内核态执行操作系统提供的互斥锁,系统开销比较大。

      如果执行同步块的时间比较长,那么多个线程之间刚开始使用轻量级锁,后面膨胀为重量级锁。(因为执行同步代码块的时间长,线程CAS自旋获得轻量级失败后就会锁膨胀)

      追求吞吐量

    4.synchronized是公平锁吗

    synchronized是非公平锁。

    synchronized在线程竞争锁时,首先做的不是直接进contentionlist队列排队,在轻量级锁时会尝试自旋获取锁,如果获取不到升级为重量级锁进入contentionlist队列,这明显对于已经进入队列的线程是不公平的。

    5.除了synchronized之后还有锁吗?(ReentrantLock)

    ReentrantLock也可以实现加锁操作,例子如下:

    public class Test2 {
    
        private static  int money = 0;
        private final static ReentrantLock lock = new ReentrantLock();
    
        private static void increaseMoney(int n){
            money += n;
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread a= new Thread(()->{
                lock.lock();
                try{
                    for (int i=0;i<1000;i++){
                        increaseMoney(1);
                    }
                }finally {
                    lock.unlock();
                }
            });
    
            Thread b = new Thread(()->{
                lock.lock();
                try{
                    for (int i=0;i<1000;i++){
                        increaseMoney(1);
                    }
                }finally {
                    lock.unlock();
                }
            });
    
            a.start();
            b.start();
    
            a.join();
            b.join();
    
            System.out.println("当前总经济:"+money);
        }
    }
    
    

    (1)synchronized和ReentrantLock两者都是可重入锁

    可重入锁指的是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都会自增1,所以要等到锁的计数器下降为0才能释放锁。

    (2)synchronized依赖于JVM而ReentrantLock依赖于API

    synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。

    (3)ReentrantLock相比synchronized增加了一些高级功能

    相比synchronizedReentrantLock增加了一些高级功能。主要来说主要有三点:

    • 等待可中断 : ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
    • 可实现公平锁 : ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
    • 可实现选择性通知(锁可以绑定多个条件): synchronized关键字与wait()notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。

    Condition是 JDK1.5 之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是 Condition 接口默认提供的。而synchronized关键字就相当于整个 Lock 对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。

    展开全文
  • 线程状态切换线程状态切换※说明:新创建一个新的线程对象后, 再调用它的start() 方法, 系统会为此线程分配CPU资源, 使其处于Runnable(可运行) 状态, 这是一个准备运行的阶段。如果线程抢占到CPU资源, 此线程就...

    线程状态切换

    e162f3c74a2a

    线程状态切换

    ※说明:

    新创建一个新的线程对象后, 再调用它的start() 方法, 系统会为此线程分配

    CPU资源, 使其处于Runnable(可运行) 状态, 这是一个准备运行的阶段。如果

    线程抢占到CPU资源, 此线程就处于Running(运行) 状态

    Runnable状态和Running状态可相互切换, 因为有可能线程运行一段时间后,

    有其他高优先级的线程抢占了CPU资源, 这时此线程就从Running状态变成

    Runnable状态。

    线程进入Runnable状态大体分为如下5种情况:

    调用sleep() 方法后经过的时间超过了指定的休眠时间

    线程调用的阻塞IO已经返回,阻塞方法执行完毕

    线程成功地获得了试图同步的监视器

    线程正在等待某个通知,其他线程发出了通知

    处于挂起状态的线程调用了resume恢复方法

    Blocked是阻塞的意思, 例如遇到了一个IO操作, 此时CPU处于空闲状态, 可

    能会转而把CPU时间片分配给其他线程, 这时也可以称为“暂停”状态。Blocked

    状态结束后, 进入Runnable状态, 等待系统重新分配资源

    出现阻塞的情况大体分为如下5种:

    线程调用sleep方法, 主动放弃占用的处理器资源。

    线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。

    线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。

    线程等待某个通知。

    程序调用了suspend方法将该线程挂起。此方法容易导致死锁, 尽量避免使用该方

    法。

    run() 方法运行结束后进入销毁阶段, 整个线程执行完毕。

    每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将

    要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就

    绪队列, 等待CPU的调度; 反之, 一个线程被wait后, 就会进入阻塞队列, 等待下

    一次被唤醒。

    wait()方法 释放锁; notify()方法 不释放锁

    当wait()方法执行后,会自动释放锁,notify()方法执行后,锁不会自动释放

    参考:https://gitee.com/kaixinshow/java-concurrent-learn

    package:com.zhxin.threadLab.notify.chapter1 下的示例

    代码如下:

    ThreadA.java

    package com.zhxin.threadLab.notify.chapter1;

    /**

    * @ClassName ThreadA

    * @Description // wait

    * @Author singleZhang

    * @Email 405780096@qq.com

    * @Date 2020/11/28 0028 上午 10:46

    **/

    public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object object){

    super();

    this.lock = object;

    }

    @Override

    public void run(){

    try{

    synchronized (lock){

    System.out.println("begin wait! time=" + System.currentTimeMillis());

    lock.wait();

    System.out.println("end wait! time=" + System.currentTimeMillis());

    }

    }catch (InterruptedException e){

    e.printStackTrace();

    }

    }

    }

    ThreadB.java

    package com.zhxin.threadLab.notify.chapter1;

    /**

    * @ClassName ThreadB

    * @Description // notify

    * @Author singleZhang

    * @Email 405780096@qq.com

    * @Date 2020/11/28 0028 上午 10:54

    **/

    public class ThreadB extends Thread {

    private Object lock;

    public ThreadB(Object object){

    this.lock = object;

    }

    @Override

    public void run(){

    synchronized (lock){

    System.out.println("begin notify! time="+System.currentTimeMillis());

    lock.notify();

    System.out.println("end notify! time="+System.currentTimeMillis());

    }

    }

    }

    RunWaitAndNotify.java

    package com.zhxin.threadLab.notify.chapter1;

    /**

    * @ClassName RunWaitAndNotify

    * @Description // Thread wait notify Test

    * // 示例中可以看出,wait需要等notify后边的代码执行完后才得到锁,继续执行

    * @Author singleZhang

    * @Email 405780096@qq.com

    * @Date 2020/11/28 0028 上午 10:56

    **/

    public class RunWaitAndNotify {

    public static void main(String[] args){

    try{

    Object lock = new Object(); // 提供同一个锁对象

    ThreadA a = new ThreadA(lock);

    a.start();

    Thread.sleep(1000);

    ThreadB b = new ThreadB(lock);

    b.start();

    } catch (InterruptedException e){

    e.printStackTrace();

    }

    }

    }

    运行示例结果如下:

    e162f3c74a2a

    waitAndNotify

    ※notify()只能唤醒一个线程,notifyAll()可以唤醒所以等待线程。

    ※wait(long) 限定等待时间,超过后自动唤醒,在其间可以由notify或notifyAll唤醒

    wati/notify 最经典的案例就是"生产者/消费者"模式

    生产者-消费者实现方式有

    synchronized结合wait+notify实现

    用Lock类实现

    使用BlockingQueue阻塞队列实现

    synchronized结合wait+notify实现"生产者/消费者"模式

    参考案例:https://gitee.com/kaixinshow/java-concurrent-learn

    package:com.zhxin.threadLab.notify.chapter2

    代码如下

    Producer.java

    package com.zhxin.threadLab.notify.chapter2;

    /**

    * @ClassName Productor

    * @Description //生产者类

    * @Author singleZhang

    * @Email 405780096@qq.com

    * @Date 2020/11/28 0028 下午 2:14

    **/

    public class Producer {

    private String lock;

    public Producer(String lock){

    super();

    this.lock = lock;

    }

    public void setValue(){

    try{

    synchronized (lock){

    if(!ValueObject.value.equals("")){

    lock.wait();

    }

    String value = "threadNme =" +Thread.currentThread().getName()+"设值,time="+System.currentTimeMillis();

    System.out.println("value值:"+value);

    ValueObject.value = value;

    lock.notifyAll();

    }

    }catch (InterruptedException e){

    e.printStackTrace();

    }

    }

    }

    Consumer.java

    package com.zhxin.threadLab.notify.chapter2;

    /**

    * @ClassName Consumer

    * @Description //消费者

    * @Author singleZhang

    * @Email 405780096@qq.com

    * @Date 2020/11/28 0028 下午 2:24

    **/

    public class Consumer {

    private String lock;

    public Consumer(String lock){

    this.lock = lock;

    }

    public void getValue(){

    try{

    synchronized (lock){

    if(ValueObject.value.equals("")){

    lock.wait();

    }

    System.out.println("get的值是:"+ValueObject.value);

    ValueObject.value = "";

    lock.notifyAll();

    }

    }catch (InterruptedException e){

    e.printStackTrace();

    }

    }

    }

    ValueObject.java

    package com.zhxin.threadLab.notify.chapter2;

    /**

    * @ClassName ValueObject

    * @Description //值对象

    * @Author singleZhang

    * @Email 405780096@qq.com

    * @Date 2020/11/28 0028 下午 2:13

    **/

    public class ValueObject {

    public static String value = "";

    }

    测试RunThreadTest.java

    package com.zhxin.threadLab.notify.chapter2;

    /**

    * @ClassName RunThreadTest

    * @Description //生产者 消费者 示例

    * @Author singleZhang

    * @Email 405780096@qq.com

    * @Date 2020/11/28 0028 下午 2:27

    **/

    public class RunThreadTest {

    public static void main(String[] args){

    String lock = new String("");

    ThreadP p = new ThreadP(new Producer(lock));

    p.start();

    ThreadC c = new ThreadC(new Consumer(lock));

    c.start();

    }

    }

    执行结果如下

    e162f3c74a2a

    生产者/消费者模式

    多生产与多消费:操作值-假死

    是否考虑过这类情况:如果全部线程都进入waiting状态,则程序就不再执行任何业务功能了,整个项目呈停止状态。这个就是假死,假死的现象其实就是进入waiting状态。这在生产者/消费者模式里常见。

    假死的原因:

    由于唤醒线程调用的是notify()唤醒单个线程,所以有可能唤醒的是同类的线程,也就是生产者唤醒的是生产者,消费者唤醒的是消费者。导致最后几个线程都处于waiting状态。

    解决办法:

    唤醒的时候采用notifyAll()唤醒所有的线程唤醒所有的线程,避免只唤醒同类线程。

    sleep/yield/join方法解析

    这组方法跟上面方法的最明显区别是:这几个方法都位于Thread类中,而上面三个方法都位于*Object类中。

    sleep

    sleep方法的作用是让当前线程暂停指定的时间(毫秒)。

    Thread.sleep(1000);//线程暂停1秒

    sleep与wait方法的区别:

    wait方法依赖于同步,而sleep方法可以直接调用

    sleep方法只是暂时让出CPU的执行权,并不释放锁。而wait方法则需要释放锁。

    yield方法

    yield方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将Running状态转变为Runnable状态。

    ※很少有场景要用到该方法,主要使用的地方是调试和测试

    3.join方法

    void join()

    void join(long millis)

    void join(long millis, int nanos)

    join方法的作用是父线程等待子线程执行完成后再执行,换句话说就是将异步执行的线程合并为同步的线程。JDK中提供三个版本的join方法,其实现与wait方法类似,join()方法实际上执行的join(0),而join(long millis, int nanos)也与wait(long millis, int nanos)的实现方式一致,暂时对纳秒的支持也是不完整的,查看join的源码:

    //Thread类

    public final synchronized void join(long millis, int nanos)

    throws InterruptedException {

    if (millis < 0) {

    throw new IllegalArgumentException("timeout value is negative");

    }

    if (nanos < 0 || nanos > 999999) {

    throw new IllegalArgumentException(

    "nanosecond timeout value out of range");

    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {

    millis++;

    }

    join(millis);

    }

    public final void join() throws InterruptedException {

    join(0);

    }

    public final synchronized void join(long millis)

    throws InterruptedException {

    long base = System.currentTimeMillis();

    long now = 0;

    if (millis < 0) {

    throw new IllegalArgumentException("timeout value is negative");

    }

    if (millis == 0) {

    while (isAlive()) {

    wait(0);

    }

    } else {

    while (isAlive()) {

    long delay = millis - now;

    if (delay <= 0) {

    break;

    }

    wait(delay);

    now = System.currentTimeMillis() - base;

    }

    }

    }

    ※说明:我们主要看一下join(long millis)方法的实现,因为另外两个方法的实现也都是通过这个方法来实现的。可以看出join方法就是通过wait方法来将线程的阻塞,如果join的线程还在执行,则将当前线程阻塞起来,直到join的线程执行完成,当前线程才能执行。这里的join只调用了wait方法,却没有对应的notify方法,原因是join中提供了时间参数,所以当join的线程执行完成以后,会自动唤醒主线程继续往下执行。

    展开全文
  • 众所周知,无限制下多线程操作共享变量是危险的,为了保证线程安全语义,...volatile是轻量级的synchronized,在正确使用的前提下,它可以达到与synchronized一样的线程安全的语义,而且不会带来线程切换的开销。vo...
  • synchronized使用锁太笨重,因为它会带来线程上下文的切换开销。对于解决内存可见性问题, Java 还提供了一种弱形式的同步,也就是使用volatile 关键字。该关键字可以确保对一个变量的更新对其他线程马上可见。当一...
  • 之前的synchronized关键字虽然可以实现同步,但却是重量级锁,相对于synchronized来说,volatile更为轻量级,volatile的使用不会引起线程的调度和切换,但是volatile的同步性较差,而且其使用也更容易出错。...
  • 作者:城北有个混子出自:博客园1、线程的状态...当线程启动以后,CPU需要在多个线程之间切换,所以线程也会随之在运行、阻塞、就绪这几种状态之间切换线程的状态转换如图:当使用new关键字创建一个线程对象后,该...
  • Java:volatile关键字

    2020-11-19 11:39:15
    volatile是Java里面提供的一种轻量级的同步机制,非常的轻量级,没有线程的上下文切换和调度。 Volatile 关键字包含两个基本语义,分别是线程的可见性和有序性。可见性指的是当一个线程对某个变量值执行了修改...
  • 线程 ——一个程序可以包括多个子任务,可串行 / 并行 ——每个子任务可称为一个线程 ...——线程是轻量级的,切换更为方便 ——多个线程更易管理   Java线程方法 ——java.lang.Thread ...
  • # 前言 首先讲解一下多线程的概念,然后通过一个案例来加深对...如果该关键字使用恰当,那么效率比synchronized要好,因为不会引起线程的上下文切换和调度 volatile 关键字能保证内存的可见性,但是不能保证原子性
  • Java-volatile关键字

    2021-03-24 15:16:14
    volatile是Java提供的一种轻量级的同步机制,Java提供了两种内在的同步机制,分别是同步块和volatile,synchronized通常称之为重量级锁,而volatile相对比较轻量,因为它不会引起线程上下文的切换和调度,因此...
  • Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile
  • Java中volatile关键字

    2019-11-28 19:42:16
    Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性...
  • volatile则是轻量级的synchronized,它不会引起线程上下文的切换和调度。 java语言规范中的定义如下: Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得...
  • Java语言包含两种内在的同步机制:同步块(或方法)和volatile变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile变量的同步性较差...
  • Java语言包含两种内在的同步机制:同步块(或方法)和volatile变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile变量的同步性较差...
  • 本文是对《Java并发编程的艺术》一书中volatile部分的小结。 一、volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。 “可见性”:当一个线程修改一个共享变量时,另外一个线程能读...
  • Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差...
  • Java语言包含两种内在的同步机制:同步块(或方法)和volatile变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile变量的同步性较差...
  • 作者:老鼠只爱大米blog....Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。...
  • 1 什么是 volatilevolatile 是 Java 的一个关键字,它提供了一种轻量级的同步机制。相比于重量级锁 synchronized,volatile 更为轻量级,因为它不会引起线程上下文的切换和调度。2 volatile 的两个作用可以禁止指令...
  • Volatile变量修饰符如果使用恰当的话,它比synchronized的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度。   当我们声明共享变量为volatile后,对这个变量的读/写将会很特别。理解volatile特性的...
  • Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性...
  • 1 什么是 volatilevolatile 是 Java 的一个关键字,它提供了一种轻量级的同步机制。相比于重量级锁 synchronized,volatile 更为轻量级,因为它不会引起线程上下文的切换和调度。2 volatile 的两个作用可以禁止指令...
  • Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性...
  • java volatile关键字

    2019-01-25 16:22:00
    volatile特性 内存可见性:通俗来说就是,...通过关键字sychronize可以防止多个线程进入同一段代码,在某些特定场景中,volatile相当于一个轻量级的sychronize,因为不会引起线程的上下文切换,但是使用volatile必...
  • Java语言包含两种内在的同步机制:同步块(或方法)和volatile变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile变量的同步性较差...
  • JavaVolatile关键字是一种轻量级的数据一致性保障机制,之所以说是轻量级的是因为volatile 不...因为 volatile 不会涉及到线程的上下文切换,以及操作系统对线程执行的调度运算。同时 volidate 关键字的另一个功能是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 510
精华内容 204
关键字:

java线程切换关键字

java 订阅