多线程问题(二)(安全问题)

简介: 多线程问题(二)(安全问题)

一、多线程不安全引例

使用两个线程同时对count变量进行相加,观察结果。

public class Count {
    public int count;
    void test(){
        count++;
    }
    public static void main(String[] args) throws InterruptedException {
        Count counter = new Count();
        Thread t1=new Thread(()->{
            for(int i=0;i<3000;i++){
                counter.test();
            }
        });
        Thread t2=new Thread(()->{
            for(int i=0;i<3000;i++){
                counter.test();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

运行结果:

此时代码的运行结果并不是6000,并且每次运行得到的结果不一致,运行结果在3000~6000之间,此时多线程并发编程就是不安全的。 线程不安全就是多线程并发执行代码没有得到预期的结果。

二、线程不安全的原因

1、线程是抢占式执行

线程的抢占式执行就是在一个线程的执行过程中,另一个更优先的进程会抢占当前线程执行的任务,当前线程就会被迫中断,这是引发线程不安全的根本原因,但是线程的调度是随机的,这是由系统决定的,我们无法改变。

2、多线程共享同一变量

多线程共享同一变量如果只是读操作就不会引发线程安全问题,但是如果多线程都修改同一变量就会引发安全问题,就是引例当中的情况,因为修改操作不是原子性,需要多步完成就有可能发生线程抢占导致中断。

3、对变量的操作不是原子性

操作原子性也就是操作能一步完成,但是修改变量的操作就可以分为:将变量从内存加载到寄存器(load) 、修改变量(update)、把寄存器的值加载回内存(save),那么如果有两个线程对变量进行修改操作由于线程的抢占式执行就会出现以下情况:

安全情况:未发生抢占,两个线程都能按序执行完。

不安全情况:发生了抢占,应该有多种,仅画出两种说明

出现上述情况导致的结果就是更新丢失,例如是自增操作: 假设内存中的变量的初始值为0,t1就先把0加载到寄存器,但是t2进行抢占,也从内存中把0加载到寄存器然后自增为1,然后将1加载回内存,然后t1再自增为1,再将1加载回内存,按道理两次自增应该为2,但是由于线程的抢占以及自增操作是非原子的就会出现上述情况。

4、内存可见性

变量通常存放在内存中,线程对变量操作需要首先从内存中拿出到寄存器,但是一个线程频繁进行读操作,就可能会直接从寄存器上读,不再进入内存这就引发线程不安全,因为线程得不到内存中变量的最新值。

5、指令重排序

指令重排序是编译器的优化操作来提高代码运行的效率,但是对于多线程在进行指令重排序时就可能会出现错误引发线程安全问题。

三、线程不安全问题的解决方案

1、使用synchronized关键字进行加锁

使用synchronized关键字加锁后可以处理内存可见性和保证原子性,使多线程之间互斥。synchronized可以修饰普通方法、代码块、静态方法。

a、 synchronized修饰普通方法

此时synchronized的加锁对象就是this。

例如将引例编程线程安全,就在test方法前加上synchronize即可。

synchronized void test(){
        count++;
    }

运行结果:

b、synchronized修饰代码块

这里需要显示指定锁对象,在Java中任何对象都可以成为锁对象,对引例的test方法进行修改:

void test(){
         synchronized(this){
             count++;
         }
    }

c、synchronized修饰静态方法

这里相当于是给当前类的类对象加锁。

public synchronized static void method() {
}

2、使用volatile关键字保证内存可见性

使用volatile只能修饰变量,保证内存的可见性,但是volatile无法保证原子性。

四、死锁问题

1、死锁

多个线程相互竞争资源而引起的一种僵局,若无外力作用,僵局就会一直保持。

2、死锁场景

a、一个线程一把锁

一个线程连续加锁两次,如果是不可重入锁,就会发生死锁

b、两个线程两把锁

两个线程对各自已有的对象进行上锁,但是执行过程中又需要对方的对象,两个线程此时都不释放锁,就都不能继续执行就会产生死锁。

c、m个线程n把锁

例如哲学家吃饭问题。

3、死锁产生的四个必要条件

a、互斥使用

一个锁被一个进程使用之后,其他的线程无法使用。

b、 不可抢占

一个锁被一个进程使用了之后,其余的线程不能抢走该锁。

c、请求和保持

一个锁占用了多把锁之后,除非显示地释放锁,否则这些锁始终都被该线程持有。

d、环路等待

等待关系形成了一个环,A等B,B又等A。

4、wait方法

wait()方法:线程等待方法

线程一旦调用了wait方法后就会进入阻塞状态

调用wait方法后,wait方法内部首先会释放锁,然后进入阻塞状态等待唤醒,收到唤醒通知后重新加锁继续向下执行。

5、notify()方法

notify()方法:唤醒等待的线程。

6、noifyAll()方法

notifyAll()方法:唤醒所有等待的线程。

当多个线程都是wait状态时,使用notify()只能随机唤醒一个线程,而notifyAll能唤醒所有的wait线程。

  • 注意:
  • notify()/wait()是针对同一个对象操作。
  • notify()/wait()都要搭配synchronized使用。

目录
相关文章
|
6月前
|
缓存 安全 Java
为什么全局变量可能成为多线程环境中的安全隐患
为什么全局变量可能成为多线程环境中的安全隐患
|
5月前
|
安全 Java
java线程之List集合并发安全问题及解决方案
java线程之List集合并发安全问题及解决方案
853 1
|
6月前
|
安全
python_threading多线程、queue安全队列
python_threading多线程、queue安全队列
53 2
|
6月前
|
缓存 安全 Java
7张图带你轻松理解Java 线程安全,java缓存机制面试
7张图带你轻松理解Java 线程安全,java缓存机制面试
|
3月前
|
Java
【Java集合类面试十二】、HashMap为什么线程不安全?
HashMap在并发环境下执行put操作可能导致循环链表的形成,进而引起死循环,因而它是线程不安全的。
|
3月前
|
安全 算法 Java
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
这篇文章讨论了Java集合类的线程安全性,列举了线程不安全的集合类(如HashSet、ArrayList、HashMap)和线程安全的集合类(如Vector、Hashtable),同时介绍了Java 5之后提供的java.util.concurrent包中的高效并发集合类,如ConcurrentHashMap和CopyOnWriteArrayList。
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
|
3月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
62 1
|
4月前
|
缓存 安全 Java
多线程线程池问题之为什么手动创建的线程池比使用Executors类提供的线程池更安全
多线程线程池问题之为什么手动创建的线程池比使用Executors类提供的线程池更安全
|
4月前
|
设计模式 安全 NoSQL
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
64 0
|
4月前
|
安全 Java 调度
Java面试题:Java内存优化、多线程安全与并发框架实战,如何在Java应用中实现内存优化?在多线程环境下,如何保证数据的线程安全?使用Java并发工具包中的哪些工具可以帮助解决并发问题?
Java面试题:Java内存优化、多线程安全与并发框架实战,如何在Java应用中实现内存优化?在多线程环境下,如何保证数据的线程安全?使用Java并发工具包中的哪些工具可以帮助解决并发问题?
61 0