Java内存模型及线程案例分析

一、Java内存模型
首先来看一下操作系统的内存模型
操作系统内存模型
再看java的内存模型
java内存模型
在java的内存模型中,首先把线程工作内存中的值写入到主内存,另一个线程从主内存中读取这个值。,由于可见性原则,另一个线程拿到的值并不是实时的。
举个例子:

/**
 * @Author jhYang
 * @Date 2020/2/27 0027 9:08
 * @Discription todo 线程共享变量实践
 */
public class ThreadSharedVariables {

    private static int a = 0;
    public static void main(String[] args) {
        Thread threadA  = new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" a="+a);
            a = 1;
            System.out.println(Thread.currentThread().getName()+" a="+a);

        },"ThreadA");

        Thread threadB  = new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" a="+a);
            a = 1;
            System.out.println(Thread.currentThread().getName()+" a="+a);

        },"ThreadB");

        threadA.start();
        threadB.start();

        try {
            threadA.join();
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

运行结果:

ThreadA a=0
ThreadB a=0
ThreadB a=1
ThreadA a=1

再次运行:

ThreadA a=0
ThreadA a=1
ThreadB a=0
ThreadB a=1

根据java的内存模型的图示可知,共享变量a是在java的内存模型中,属于内存共享的区域,各个线程从这个这里拿到共享变量到自己的工作内存中,完成计算之后再放回公共区域。对于第一种结果当线程B去公共内存拿到a时,a已经被线程A计算过了,所以结果是1.对于结果2,当线程B去拿结果的时候,线程A还在自己的给你工作内存中计算,计算后的结果还没有放到共享内存。

二、synchronized和volatile关键字
1、synchronized的实现和使用

synchronized是Java语言关键字,用来给方法或者代码块加锁,控制方法或代码块同一时间只有一个线程执行,用来解决多个线程同时访问出现的并发问题。

synchronized给方法加锁示例:

/**
 * @Author jhYang
 * @Date 2020/2/27 0027 10:24
 * @Discription todo Synchronized使用
 */
public class SynchronizedTest {

    public static void main(String[] args) {
        sellTickets();
    }

    private static int totalTickets = 1000;
    private static final Object LOCK = new Object();

    private static void sellTickets(){
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        Runnable runnableA = ()->{
           int threadAtickits = 0;
           while(sellTicktsWithSyncMethod(1)){
               threadAtickits++;
           }
            System.out.println("A buys "+threadAtickits +" tickts");
        };

        Runnable runnableB = ()->{
            int threadBtickits = 0;
            while(sellTicktsWithSyncMethod(1)){
                threadBtickits++;
            }
            System.out.println("B buys "+threadBtickits +" tickts");
        };

        executorService.execute(runnableA);
        executorService.execute(runnableB);
    }

    /**
     * 方法级别的使用synchronized关键字的方法
     * @param count 卖票的数量
     * @return 返回剩余票数是否足够
     */
    private static synchronized boolean sellTicktsWithSyncMethod(int count){
        if(totalTickets - count < 0){
            return false;
        }else {
            totalTickets = totalTickets - count;
            return true;
        }
    }
}

运行结果:

A buys 392 tickts
B buys 608 tickts

再运行一次:

A buys 1000 tickts
B buys 0 tickts

从两次运行结果来看,程序是正确的 ,总票数和卖出的总票数是一致的。

2、synchronized给对象加锁示例

/**
 * @Author jhYang
 * @Date 2020/2/27 0027 10:24
 * @Discription todo Synchronized使用
 */
public class SynchronizedTest {

    public static void main(String[] args) {
        sellTickets();
    }

    private static int totalTickets = 1000;
    private static final Object LOCK = new Object();

    private static void sellTickets(){
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        Runnable runnableA = ()->{
           int threadAtickits = 0;
           while(sellTicktsWithSyncObject(1)){
               threadAtickits++;
           }
            System.out.println("A buys "+threadAtickits +" tickts");
        };

        Runnable runnableB = ()->{
            int threadBtickits = 0;
            while(sellTicktsWithSyncObject(1)){
                threadBtickits++;
            }
            System.out.println("B buys "+threadBtickits +" tickts");
        };

        executorService.execute(runnableA);
        executorService.execute(runnableB);
    }

    /**
     * 对象级别的使用synchronized关键字
     * @param count 卖票的数量
     * @return 返回剩余票数是否足够
     */
    private static boolean sellTicktsWithSyncObject(int count){
        synchronized (LOCK){
            if(totalTickets - count < 0){
                return false;
            }else {
                totalTickets = totalTickets - count;
                return true;
            }
        }
    }
}

运行结果:

A buys 1000 tickts
B buys 0 tickts

再次运行:

B buys 494 tickts
A buys 506 tickts

可以看到这两种方式均可。
值得注意的是,当使用synchoronized修饰方法的时候,如果方法被static修饰,则说明这个锁是作用在类级别的,就是说整个类拥有一把锁。如果这个方法被static修饰则说明锁是作用在对象级别的,对象级别的锁,各线程之间互不影响,也就是每个对象拥有一把锁。

3、另外除了synchronized锁之外还有ReentrantLock
二者的比较如下:

/**
 * @Author jhYang
 * @Date 2020/2/27 0027 11:30
 * @Discription todo  Synchronized与ReentrantLock的比较
 */
public class SynchronizedAndReentrantLock {

    private static Object LOCK = new Object();
    public static void main(String[] args) {

    }

    private static void useSync(){
        synchronized (LOCK){
            System.out.println("hoding lock");
            synchronized (LOCK){
                System.out.println("hoding lock agin");
            }
        }
    }

    private static void useReentantLock(){
        ReentrantLock lock = new ReentrantLock();
        try {
            lock.lock();
            System.out.println("hoding lock");
        } finally {
            lock.unlock();
        }
    }
    
}

根据使用示例得出以下相同点不同点:
相同点:

  1. 都是用于多线程中对资源的加锁,控制代码同一时间只有一个线程在执行。
    2)当一个线程获取了锁,其他线程均需要阻塞等待。
    3)均为可重入的锁

不同点:

  1. synchronized是java关键字,由虚拟机字节码指令实现,ReentantLock是Java SDK 提供的API级别的锁是实现。
    2)synchronized可以在方法级别加锁,ReentantLock不行。
    3)ReentantLock可以通过方法tryLock()等待指定时间内的锁,synchronized则不行。
    4)ReentantLock提供了公平锁和非公平锁,synchronized只有非公平锁。(非公平锁需要阻塞的线程去竞争,公平锁按照先来后到的原则拿锁)

二、volatile关键字的存取规则
1、volatile是Java语言关键字,也是一个指令的关键字。该关键字可以有以下作用:
a) 用来保证多线程之间对变量的内存可见性,讲最新的变量值及时通知给其他线程。
b) 禁止volatile前后的程序指令进行重排序。
c) 不保证线程安全,不可用于数字的线程安全递增

2、volatile的使用场景
a) 用于修饰状态变量,线程间访问该变量保证各个线程看到最新的内存值。
修饰状态变量
b) 用于单实例对象的构造,避免多线程情况下由于内存不可见而重复多次构造对象。
单实例对象构造

三、synchronized和volatile关键字的区别
1、synchronized是用于同步锁控制,具有原子性。控制同一时间只有一个线程执行一个方法或者代码块。
2、volatile只保证线程间内存的可见性,不具备锁的特性,无法保证修饰对象的原子性。

可见性:一个线程对共享变量的修改,更够及时的被其他线程看到
原子性:即不可再分了,不能分为多步操作。

volatile只保证可见性,不保证原子性,导致非原子性的操作不安全,具体可以参考https://blog.csdn.net/qq_27409289/article/details/84981453
因此使用volatile修饰变量时一般用于标志,这样的操作具有原子性,于是可以达到线程安全。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值