volatile与Java内存模型、同步方法的使用synchronized关键字

本文中的模型是一位大佬画的,我借用一下
java内存模型(JMM),Java线程之间的通信由Java内存模型(本文简称为JMM)控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。Java内存模型的抽象示意图如下:
在这里插入图片描述
从上图中可以看出,线程A需要和线程B通信,必须要经历下面2个步骤:

首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去
然后,线程B到主内存中去读取线程A之前已更新过的共享变量

public  class Demo09 {
    public static boolean
            flag = true;
            public static class T1 extends Thread {
             public T1(String name) {
                 super(name);
             }
             @Override
             public void run() {
                    System.out.println("线程" + this.getName() + " in");
                    while (flag) {
                        ;
                    }
                    System.out.println("线程" + this.getName() + "停止了");
        }
    }
    public static void main(String[] args) throws InterruptedException { 
                new T1("t1").start();
                //休眠1秒
                Thread.sleep(1000);
                //将flag置为false
                 flag = false;
    }
}

运行上面代码,会发现程序无法终止
线程t1中为何看不到被主线程修改为false的flag的值,有两种可能:

1、主线程修改了flag之后,未将其刷新到主内存,所以t1看不到
2、主线程将flag刷新到了主内存,但是t1一直读取的是自己工作内存中flag的值,没有去主内存中获取flag最新的值

使用volatile修饰共享变量

线程中修改了工作内存中的副本之后,立即将其刷新到主内存;工作内存中每次读取共享变量时,都去主内存中重新读取,然后拷贝到工作内存
被volatile修改的变量有以下特点:

1、线程中读取的时候,每次读取都会去主内存中读取共享变量最新的值,然后将其复制到工作内存
2、线程中修改了工作内存中变量的副本,修改之后会立即刷新到主内存

public volatile static
	boolean flag =true;

volatile解决了共享变量在多线程中可见性的问题,可见性是指一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的。

同步方法的使用synchronized关键字

线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点:

一是存在共享数据(也称临界资源)
二是存在多条线程共同操作共享数据

可以理解为同步的方法必定是线程安全的
临界区是共享数据(公共的逻辑处理)
按照思路,将公共的方法或者数据变为同步方法就是线程安全
同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式有个高尚的名称叫互斥锁
添加"锁",将方法锁住,不让别的线程调用,直到当前线程释放锁
synchronized

synchronized主要有3种使用方式

1、修饰实例方法,作用于当前实例,进入同步代码前需要先获取实例的锁
2、修饰静态方法,作用于类的Class对象,进入修饰的静态方法前需要先获取类的Class对象的锁
3、修饰代码块,需要指定加锁对象(记做lockobj),在进入同步代码块前需要先获取lockobj的锁

举例代码–静态方法

package com.example.demo.demo.threadGroupDemo;

/**
 * 修饰实例方法,作用于当前实例,进入同步代码前需要先获取实例的锁
 * 修饰静态方法,作用于类的Class对象,进入修饰的静态方法前需要先获取类的Class对象的锁
 * 修饰代码块,需要指定加锁对象(记做lockobj),在进入同步代码块前需要先获取lockobj的锁
 */
public class MoreThread {
    static int num = 0;
    public  static void m1 (){
        synchronized( MoreThread.class){
            for (int i = 0; i <100 ; i++) {
                num++;
                System.out.println(Thread.currentThread().getName()+"++"+num);
            }
        }

    }
    public static  class T1 extends Thread{

        @Override
        public void run() {
            MoreThread.m1();
        }
    }

    public static void main(String[] args) {
        T1 t1 = new T1();
        T1 t2 = new T1();
        T1 t3 = new T1();
        t1.start();
        t2.start();
        t3.start();
        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(MoreThread.num);
    }
}

举例–修饰实例代码

package com.example.demo.demo.threadGroupDemo;

/**
 * 修饰实例方法,作用于当前实例,进入同步代码前需要先获取实例的锁
 * 修饰静态方法,作用于类的Class对象,进入修饰的静态方法前需要先获取类的Class对象的锁
 * 修饰代码块,需要指定加锁对象(记做lockobj),在进入同步代码块前需要先获取lockobj的锁
 */
public class MoreThread {
    static int num = 0;
    public   void m1 (){
        synchronized( this){
            for (int i = 0; i <100 ; i++) {
                this.num++;
                System.out.println(Thread.currentThread().getName()+"++"+num);
            }
        }

    }
    public static  class T1 extends Thread{
		private MoreThread moreThread;
		public T1 (MoreThread  moreThread ){
		this.moreThread =moreThread ;
		}
        @Override
        public void run() {
         // new  MoreThread().m1();//这种情况每次都创建了一个实例无法保证线程安全
         moreThread .m1();
        }
    }

    public static void main(String[] args) {
    	MoreThread moreThread= new MoreThread ();//只有一个实例
        T1 t1 = new T1(moreThread);
        T1 t2 = new T1(moreThread);
        T1 t3 = new T1(moreThread);
        t1.start();
        t2.start();
        t3.start();
        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(MoreThread.num);
    }
}

synchronize作用于实例方法需要注意

1、实例方法上加synchronized,线程安全的前提是,多个线程操作的是同一个实例,如果多个线程作用于不同的实例,那么线程安全是无法保证的
2、同一个实例的多个实例方法上有synchronized,这些方法都是互斥的,同一时间只允许一个线程操作同一个实例的其中的一个synchronized方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值