Java多线程安全问题和锁

多线程安全问题和锁

什么是线程安全问题?
当多个线程同时操作同一个数据是,可能会出现数据不一样的情况,这就是线程安全问题。
线程安全机制用于保证多个线程访问数据时的一致性.
线程安全问题体现在三个方面:
1、原子性
2、可见性
3、有序性

原子操作是不可分割的,体现在两个方面: 一个线程对数据的操作对于其他的线程来说是原子的,要么操作完成,要么什么也没做;当一个线程在操作数据时,不允许其他的线程参与.

可见性是指线程对共享数据的访问是否对其他的线程可见. A线程对共享数据做了修改, B线程不一定能够立即读取到A线程修改后的数据。

有序性包含指令重排序与内存重排序指令重排序是指CPU执行指令的顺序与程序的顺序可能不一样; 内存重排序是指内存访问顺序与感知顺序可能不一样。

线程在jvm中的特点

在这里插入图片描述

1)每个线程都有独立的线程栈
2)一个线程栈不能访问另外一个线程栈的内容
3)所有的线程栈都可以访问堆中对象
4)局部变星理解为存储在方法栈中,局部变量肯定不存在线程安全问题
5)当多个线程同时访问同一个对象的实例变量,或者访问同一 个静态变量时才可能会出现线程安全问题
6)线程安全问题不是肯定会发生的,有时可能会出现线程安全问题

java抽象内存模型
在这里插入图片描述
1)每个线程都有自己独立的工作内存
2)线程1无法访问线程2的工作内存
3)线程在访问共享数据时,会把主内存中的共享变量复制到自己的工作内存中,线程操作的是工作内存中数据的副本

锁的出现

多线程并发操作同一个数据可能会引发线程安全问题。
多个线程串行操作某个数据就不会引发线程安全问题。

锁就是把多个线程对数据的并发操作转换为串行操作。

想要访问共享数据,必须先获得锁对象(锁就相当于一个访问许可证)。锁对象在某一时刻只能被一个线程持有,锁具有排它性。当线程访问完共享数据后,会自动释放锁对象。

在这里插入图片描述
1)某个线程想要访问共享数据,必须先获得锁对象
2)锁对象在某一时刻只能被一个线程持有当线程2获得了锁对象之后,如果还有其他线程想要访问共享数据,其他的线程也必须先获得锁对象现在锁对象被线程2持有其他的线程转为阻塞状态
3)当线程2执行完临界区代码后,会释放锁对象阻塞队列中的线程会获得锁对象,访问共享数据
4)注意: 多个线程想要实现同步访问,必须使用同一一个锁对象读数据时也需要进行同步,否则会出现脏读现象(另一个线程操作的中间值,还未更新)

synchronized 关键字-监视器锁monitor lock

synchronized的底层是使用操作系统的mutex lock实现的。

synchronized 关键字的使用
1、同步代码块
1)可以定义一个常量作为锁对象
2)只要是同一个锁对象,同步代码快可以在不同的方法体中也能同步
2、同步实例方法
直接使用synchronized修饰实例方法
把整个方法体作为同步代码块,默认的锁对象就是this对象
3、同步静态方法
就是使用synchronized修饰静态方法
把整个方法体作为同步代码块,默认的锁对象是 当前类的运行时类对象,简单的理解为把当前类的字节码文件作为锁

当线程释放锁时,JMM会把该线程对应的工作内存中的共享变量刷新到主内存中
当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内 存中读取共享变量
synchronized用的锁是存在Java对象里的

在这里插入图片描述
锁的是SynchronizedDemo 对象

/*
锁的是SynchronizedDemo 对象
 */
public class SynchronizedDemo {
    public synchronized void methond() {
    }

    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        demo.methond(); // 进入方法会锁 demo 指向对象中的锁;出方法会释放 demo 指向的对象中的锁
    }
}

锁的 SynchronizedDemo 类的对象

/*
锁的是SynchronizedDemo 类的对象
 */
public class SynchronizedDemo {
    public synchronized static void methond() {
    }

    public static void main(String[] args) {
        // 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;
        // 出方法会释放 SynchronizedDemo.class 指向的对象中的锁
        methond();
    }
}

谁调用的这个方法锁的就是哪个对象

/*
谁调用的这个方法锁的就是哪个对象
 */
public class SynchronizedDemo {
    public void methond() {
        // 进入代码块会锁 this 指向对象中的锁;
        // 出代码块会释放 this 指向的对象中的锁         
        synchronized (this) {
        }
    }

    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        demo.methond();
    }
}

死锁的产生和避免

当多个线程需要使用多个同步锁时,如果获得锁的顺序不一致,可能会导致相互等待的情况,这种现象称为死锁。

如何解决死锁问题?。
如果需要获得多个锁对象时,保证获得锁对象的顺序一致,就可以避免死锁的产生。

发布了65 篇原创文章 · 获赞 135 · 访问量 7022

猜你喜欢

转载自blog.csdn.net/lzh_99999/article/details/104559437