线程安全问题的原因和解决方案的总结

造成线程不安全的原因

1.随机调度,抢占式执行(万恶之源,无法解决)

2. 多个线程修改同一个变量(可适当调节代码结构,避免这种情况)

3.修改操作不是原子性的(加锁,关键字synchronized)

4.内存可见性(使用关键字volatile)

5.指令重排序(使用关键字synchronized)

解决方法

1)使用synchronized关键字及其基本使用

本质操作是修改了Object对象中的“对象头”里面的一个标记

只有当两个线程同时针对一个对象加锁,才会产生竞争

//把synchronized加到普通的方法上,也就相当于把锁对象指定为this了
synchronized public void increased() {
    count++;
}
//把synchronized加到代码块上,就需要手动锁定要锁的锁对象是什么
public void increased() {
    synchronized (this) {
        coun++;
    }
}
//把synchronized加到静态方法上,“静态方法”更严谨的叫法应该叫做“类方法”,普通的方法,更严谨的叫法
//叫做”实例方法“ ,针对类对象加锁如下:
public static void func() {
    synchronized (Count.class) {
     
    }
}

类对象就是运行程序的时候,.class文件被加载到JVM内存中的模样 

synchronized的特性

(1)原子性:一个或多个操作要么全部执行成功,要么全部执行失败。synchronized 关键字可以保证只有一个线程拿到锁,访问共享资源。
(2)可见性:当一个线程对共享变量进行修改后,其他线程可以立刻看到。可见性是通过Java内存模型中的 “对一个变量 unlock 操作之前,必须要同步到主内存中;如果对一个变量进行 lock 操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中 load 操作或 assign 操作初始化变量值” 来保证的;
(3)有序性:程序的执行顺序会按照代码的先后顺序执行。有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”。
 

2)volatile关键字

保证内存可见性,禁止编译器优化

处理一个线程读,一个线程写的情况

不保证原子性,也不会引起线程阻塞

3)wait和notify

wait()方法:

wait()方法是Object类中定义的一个方法,用于使调用该方法的线程释放所持有的锁并进入等待状态,直到其他线程通过notify()或notifyAll()方法来唤醒它。wait()方法必须在synchronized代码块或方法中使用,以确保在释放锁之前调用wait()方法。

notify()方法:

notify()方法也是Object类中定义的一个方法,用于唤醒正在等待该对象锁的一个线程。如果有多个线程等待,则只能唤醒其中一个,具体唤醒哪个线程无法确定,取决于JVM的实现。而使用notifyAll方法可以一次唤醒所有的等待线程。同样,notify()方法也必须在synchronized代码块或方法中使用。

wait()和notify()的配合使用:

wait()和notify()方法通常在生产者-消费者模式或线程间的其他协作场景中使用。基本的使用方式如下:

  • 生产者线程在生产完数据后调用notify()方法唤醒一个或多个消费者线程;
  • 消费者线程在消费数据之前调用wait()方法进入等待状态,并在被唤醒后继续执行

wait()和notify()的注意事项:

  • wait()和notify()方法必须在同步块或同步方法中使用,以确保在调用wait()和notify()方法前后能持有相同的对象锁。
  • wait()方法会释放当前线程持有的锁,因此其他线程可以获得锁并执行。而notify()方法只是唤醒一个等待线程,并不释放锁。
  • wait()方法和notify()方法必须在调用对象的监视器锁上。监视器锁可以是任意对象,即要调用wait()和notify()的对象。

示例代码

class SharedData {
    public synchronized void produce() {
        // 生产数据
        notify(); // 唤醒等待的线程
        try {
            wait(); // 进入等待状态
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void consume() {
        try {
            wait(); // 进入等待状态
            // 消费数据
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        notify(); // 唤醒等待的线程
    }
}

public class Main {
    public static void main(String[] args) {
        SharedData sharedData = new SharedData();

        Thread producerThread = new Thread(() -> {
            while (true) {
                sharedData.produce();
                Thread.sleep(1000);
            }
        });

        Thread consumerThread = new Thread(() -> {
            while (true) {
                sharedData.consume();
                Thread.sleep(1000);
            }
        });

        producerThread.start();
        consumerThread.start();
    }
}

wait和sleep的对比

相同点就是都可以让线程放弃执行一段时间

wait用于线程之间的通信的,sleep是让线程阻塞一段时间

 wait 需要搭配 synchronized 使用. sleep 不需要.

wait 是 Object 的方法 sleep 是 Thread 的静态方法

你可能感兴趣的:(java,开发语言,安全,jvm)