Java线程安全问题

线程安全

	如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。

线程不安全

线程不安全的原因

  1. 从代码层面看:多个线程对同一变量的操作(读、写),有一个写操作,就有线程安全问题

  2. 从原理看:
    (1)原子性:多行执行代码,执行时是一组不可分割的最小执行单位,多个线程同时并发执行的代码指令,可能在一个线程操作一个共享变量时,是有前后依赖关系的,指令之间有其他的线程操作,就会导致想线程不安全

    (2)可见性
    主存:线程都使用的共享区域,对其中变量/对象的操作
    工作内存:线程之间是互相不可见的。CPU执行线程中代码指令,是从主存复制到CPU高速缓存。
    可见性(3)有序性
    代码重排序:Java代码顺序是固定的。但是JVM执行字节码或者CPU执行机器码,都可能重排序指令顺序,目的是为了提高运行效率,重排序会考虑指令前后的依赖关系。

如何解决线程安全问题

	一组代码,如果存在多线程对共享变量的操作,都需要考虑线程安全问题。把多线程操作的共享变量,称为临界资源,一组代码称为临界区。

synchronized关键字

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

使用:

  1. 在静态方法上:加锁整个方法,锁对象为当前的类对象(Class<当前类型>,可能加锁多个多想)
public class SynchronizedDemo {
public synchronized static void methond() {
}
public static void main(String[] args) {
method(); // 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;出方法会释放
SynchronizedDemo.class 指向的对象中的锁
}
}
//锁对象为SynchronizedDemo.class

2.在实例方法上:加锁整个方法,锁对象为this指向对象(只加锁一个对象)

public class SynchronizedDemo {
    public void methond() {
        // 进入代码块会锁 this 指向对象中的锁;出代码块会释放 this 指向的对象中的锁
        synchronized (this) {
            
       }
   }
    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        demo.method();
   }
}
  1. 同步代码块:synchronized(锁对象){
    }
    作用:对同一对象加锁的线程,造成同步互斥的作用(多个线程依次执行临界区代码)
public class SynchronizedDemo {
    public void methond() {
        // 进入代码块会锁 SynchronizedDemo.class 指向对象中的锁;出代码块会释放
SynchronizedDemo.class 指向的对象中的锁
        synchronized (SynchronizedDemo.class) {
            
       }
   }

synchronized加锁的原理

  1. 本质上是对对象头进行加锁,对同一个对象加锁的线程,是同步互斥
  2. synchronized底层原理是基于操作系统的锁实现的

volatile关键字

作用:

  1. 修饰共享变量,可以保证可见性,保证顺序性
  2. 建立一个内存屏障,
class ThraedDemo {
    private volatile int n;
}

应用场景:

  1. 多线程对共享变量的操作,如果代码本身保证了原子性,就可以不加锁,只使用volatile保证可见性
  2. 多线程代码的设计目标:线程安全下尽可能提高效率
    临界区代码越多,多线程对临界区加同一个锁,进行同步互斥,执行临界区代码的效率越低

提高效率优化方案:

  1. 锁的细粒度化(临界区代码行越少)、(共享变量写操作的代码行)
  2. 哪些代码不加锁也能保证线程安全
    以上,总体可以称为读写分离(读读并发,读写并发,写写互斥)

通信-对象的等待集

  1. wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁

  2. notify()和notifyAll()的作用,唤醒当前对象上的等待进程;notify()是唤醒单个线程,notifyAll()是唤醒全部线程

  3. wait(long timeout)让当前线程处于“等待(阻塞)状态”,直到其他线程调用此对象的notify()方法或notifyAll(),或超过指定的时间量,当前线程被唤醒(进入就绪态)

wait和sleep的对比

  1. wait()用于线程通信,sleep()用于线程等待
  2. wait之前需要请求锁,而执行wait时会先释放锁,等线程被唤醒时再重新请求锁。这个锁是wait对象上的monitor lock
  3. wait 是object的方法
  4. sleep是Thread的静态方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值