volatile和锁

一、volatile

1.1回顾线程不安全因素

a.抢占式执⾏
b. 每个线程操作⾃⼰的变量
c. ⾮原⼦操作
d. 内存可⻅性
e. 指令重排序

1.2 volatile 解决内存可⻅性和指令重排序

a.代码在写⼊ volatile 修饰的变量的时候:
改变线程⼯作内存中volatile变量副本的值
将改变后的副本的值从⼯作内存刷新到主内存
b.从主内存中读取volatile变量的最新值到线程的⼯作内存中
从⼯作内存中读取volatile变量的副本

加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了

volatile 的缺点:
volatile 虽然可以解决内存可⻅性和指令重排序的问题,但是解决不了原子性问题

二、锁(synchronized和lock)

java中的锁是解决线程安全问题的最主要手段。

2.1内存锁synchronized锁

2.1.1 synchronized 基本使用

a.修饰普通⽅法


public class ThreadSynchronized2 {
    private static int num=0;
    static class Counter{
        private static int MAX_COUNT=100000;
        //++方法
        public  synchronized void increment(){
            for (int i = 0; i < MAX_COUNT; i++) {
                num++;
            }
        }
        //--方法
        public  synchronized void decrement(){
            int temp=0;
            for (int i = 0; i < MAX_COUNT; i++) {
                num--;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Counter counter=new Counter();
        Thread  thread1=new Thread(()->{
            counter.increment();
        });
        Thread thread2=new Thread(()->{
            counter.decrement();
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("最终结果:"+num);
    }
}

b.修饰静态⽅法

public class ThreadSynchronized {
    private static int num=0;
    static class Counter{
        private static int MAX_COUNT=100000;
        //++方法
        public static synchronized void increment(){
            for (int i = 0; i < MAX_COUNT; i++) {
                num++;
            }
        }
        //--方法
        public static synchronized void decrement(){
            int temp=0;
            for (int i = 0; i < MAX_COUNT; i++) {
                num--;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread  thread1=new Thread(()->{
            Counter.increment();
        });
        Thread thread2=new Thread(()->{
            Counter.decrement();
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("最终结果:"+num);
    }
}

c. 修饰代码块

注意事项:
a.修饰代码块时,对于同一个业务的多个对象的加锁对象,注意加锁的应是同一个对象(即加同一把锁)
b.synchronized修饰代码块,代码块在静态方法中时,不能使用this。

synchronized中的对象可以有以下三种形式:

public void method(){
        //1.使用this锁当前对象
        synchronized (this){
        }
        
        //2.锁当前类对象
        synchronized (SynchronizedDemo.class){
        }
        
        //3.自定义的锁对象
        Object obj=new Object();
        synchronized (obj){
        }
    }

2.1.2synchronized 特性

1.互斥性
synchronized 会起到互斥效果, 某个线程执⾏到某个对象的 synchronized 中时, 其他线程如果也执⾏到同⼀个对象 synchronized 就会阻塞等待.
a.进⼊ synchronized 修饰的代码块, 相当于 加锁
b.退出 synchronized 修饰的代码块, 相当于 解锁

2.刷新内存
synchronized 的⼯作过程:
a.获得互斥锁
b.从主内存拷⻉变量的最新副本到⼯作的内存
c.执⾏代码
d.将更改后的共享变量的值刷新到主内存
e.释放互斥锁

3.可重入性
synchronized 同步块对同⼀条线程来说是可重⼊的,不会出现⾃⼰把⾃⼰锁死的问题。

public class ThreadSynchronized {
    public static void main(String[] args) {
        synchronized (ThreadSynchronized.class){
            System.out.println("当前主线程已经得到了锁");
            synchronized (ThreadSynchronized.class){
                System.out.println("当前主线程再次得到了锁");
            }
        }
    }
}

在这里插入图片描述

2.1.3synchronized的底层实现

a.在JVM层面:依靠监视器Monitor实现的
b.在操作系统层面:基于操作系统的互斥锁(Mutex)来实现的

监视器是一个概念或者说是一个机制,它用来保障在任何时候,只有一个线程能够执行指定区域的代码。

以下是synchronized在字节码层面的实现:


public class SynchronizedToMonitorExample {
    public static void main(String[] args) {
        int count = 0;
        synchronized (SynchronizedToMonitorExample.class) {
            for (int i = 0; i < 10; i++) {
                count++;
            }
        }
        System.out.println(count);
    }
}

将上述代码翻译成字节码:
在这里插入图片描述
在main方法中多了monitorenter和moniterexit两个指令,他们分别表示进入监视器和退出监视器,由此得出在JVM层面synchronized是依靠监视器实现的。

2.1.4锁升级

在jdk1.6之前synchronized使用的较少,因为synchronized默认使用重量级锁实现,所以性能较差。jdk1.6对synchronized做了优化,实现了锁升级。

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

2.2可重入锁lock(ReentrantLock)

2.2.1手动锁的实现

实现步骤:
a.创建Lock
b.加锁lock.lock()
c.释放锁lock.unlock()

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**\
 * 关于可重入锁的基本使用
 */
public class ThreadLock {
    public static void main(String[] args) {
        //1.创建锁对象
        Lock lock = new ReentrantLock();

        //2.加锁操作
        lock.lock();
        try{
            System.out.println("你好 ReentrantLock");
        }finally {//释放锁一定要放在try...finally中

            //3.释放锁
            lock.unlock();
        }
    }
}

注意事项:
1.释放锁unlock操作一定要放在finally代码块中,防止因为业务代码有异常直接结束执行,而导致的锁资源永久占用的问题。
2.加锁lock.lock()一定要放在try之前,或者是try的首行。
原因有两个:①如果没放在try的首行或try前面,如果因为try中代码异常导致加锁失败,还会执行finally中释放锁的操作;②释放锁的异常会覆盖try中的业务异常,增加排查难度。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadLock1 {
    public static void main(String[] args) {
        //1、创建锁对象
        Lock lock = new ReentrantLock();

        try {
            //业务代码(可能会非常复杂->导致异常)
            int y=10/0;
            //2、加锁操作
            lock.lock();

        }finally {//unlock一定要放在finally中
            //3.释放锁
            lock.unlock();
        }
    }
}

在这里插入图片描述

2.2.2非公平锁

Lock可以指定锁的类型,默认情况下创建一个非公平锁,非公平锁的执行效率较高。

创建锁时参数默认为false,传递参数true时会创建一个公平锁

2.2.3synchronized VS Lock

1.Lock更灵活,有更多的方法:比如tryLock()…
2.锁的类型不同,Lock默认是非公平锁,但可以设置为公平锁,而synchronized只能是非公平锁。
3.synchronized可以修饰方法(静态方法、普通方法)和代码块,而Lock只能修饰代码块。
4.synchronized是JVM层面提供的锁,它是自动进行加锁和释放锁操作,对于开发者是无感的,而 Lock需要开发者自己进行加锁和释放锁的操作。
5.调用lock()方法和synchronized线程等待锁的状态不同,lock方法会让线程状态变为waiting,而synchronized会让线程状态变成blocked。

使用synchronized加锁:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadSynchronized1 {
    public static void main(String[] args) throws InterruptedException {
        Lock lock=new ReentrantLock();

        Thread t1=new Thread(()->{
            synchronized (ThreadLock1.class) {
                System.out.println("线程1得到了锁");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1释放了锁");
            }
        });
        t1.start();

        Thread t2=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (ThreadLock1.class) {
                System.out.println("线程2获取到了锁");
            }
        });
        t2.start();
        Thread.sleep(1500);
        System.out.println("线程2的状态:"+t2.getState());

    }
}

在这里插入图片描述
调用lock方法加锁:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadLock2 {
    public static void main(String[] args) throws InterruptedException {
        Lock lock=new ReentrantLock();

        Thread t1=new Thread(()->{
            lock.lock();
            System.out.println("线程1得到了锁");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                System.out.println("线程1释放锁");
                lock.unlock();
            }
        });
        t1.start();

        Thread t2=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.lock();
            try {
                System.out.println("线程2获取到了锁");
            }finally {
                lock.unlock();
            }
        });
        t2.start();
        Thread.sleep(1500);
        System.out.println("线程2的状态:"+t2.getState());

    }
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值