java面试宝典线程安全问题|线程死锁的出现|线程安全的集合类(代码片段)

微凉秋意 微凉秋意     2023-04-02     787

关键词:

CSDN话题挑战赛第2期
参赛话题:面试宝典

文章目录

  前言

  线程安全在面试中是考官比较青睐的考点,那我就从多线程的组成特点上开始,分析线程安全问题、死锁出现与解决的方法以及线程安全的集合类总结。希望可以帮助大家理清有关知识点,直面考官,收割offer!


1、多线程概述

1.1、线程的由来

概念

线程是进程中并发执行的多个任务,进程是操作系统中并发执行的多个程序任务。

进程具有宏观并行,微观串行的特点:

  • 原理:
    在同一时间段内,CPU会将该时间段划分为很多个时间片,时间片之间交替执行,一个时间片只能被一个进程拥有,只有拿到时间片的程序才能执行自身内容,当时间片的划分足够细小,交替频率足够快,就会形成宏观并行的假象,本质仍然是串行。
  • 注意:
    只有正在执行的程序才能叫进程。

1.2、多线程特点

只存在多线程,不存在多进程

  • 线程是进程的基本组成部分
  • 宏观并行,微观串行
    • 原理: 一个"时间片"只能被一个进程拥有,一个进程一次只能执行一个线程
  • 线程的组成:
    1. 时间片
      • OS进行调度分配,是线程执行的因素之一
    2. 数据
      • 栈:每个线程都有自己独立的栈空间(栈独立
      • 堆:堆空间被所有线程共享(堆共享
    3. 代码
      • 特指书写逻辑的代码

2、线程安全问题

当多个线程同时访问同一临界资源时,有可能破坏其原子操作,从而导致数据缺失。

  • 临界资源:被多个线程同时访问的对象
  • 原子操作:线程在访问临界资源的过程中,固定不可变的操作步骤

2.1、互斥锁

每个对象都默认拥有互斥锁,开启互斥锁之后,线程必须同时拥有时间片和锁标记才能执行,其他线程只能等待拥有资源的线程执行结束释放时间片和锁标记之后,才有资格继续争夺时间片和锁标记。

利用synchronized开启互斥锁,使线程同步,可以采取两种方法:

  1. 同步代码块
  2. 同步方法

2.1.1、同步代码块

思路:谁访问临界资源,谁对其加锁

synchronized(临界资源对象)
    //对临界资源对象的访问操作

示例:

public class Test 
    public static void main(String[] args) throws Exception 
        //myList 是自定义的集合类,封装了添加与遍历集合的方法
        MyList m = new MyList();
        //线程1:往集合中添加1-5
        Thread t1=new Thread(new Runnable() 
            @Override
            public void run() 
                for (int i=1;i<=5;i++)
                    synchronized (m)
                        m.insert(i);
                    
                
            
        );

        //线程2:往集合中添加6-10
        Thread t2 = new Thread(new Runnable() 
            @Override
            public void run() 
                for (int i=6;i<=10;i++)
                    synchronized (m)
                        m.insert(i);
                    
                
            
        );

        //开启线程
        t1.start();
        t2.start();

        //让t1和t2优先执行
        t1.join();
        t2.join();

        //查看集合元素
        m.query();
    

此例中临界资源是m,为了防止t1进程中for循环执行后没来得及为其添加元素就被其他进程抢走时间片,因此在刚执行for循环时就将m锁住。

2.1.2、同步方法

思路:对多个线程同时访问的方法进行加锁

访问修饰符 synchronized 返回值类型 方法名()

示例:

public class MyList 
    List<Integer> list = new ArrayList<>();
    //往集合属性中添加一个元素
    public synchronized void insert(int n)
        list.add(n);
    
    //查看集合元素
    public void query()
        System.out.println("集合长度:"+list.size());
        for (int n : list)
            System.out.print(n+"  ");
        
        System.out.println();
    



这里是我定义MyList类的源码,如果这时候在insert方法加锁标记,那么这时线程再想被调度执行就需要同时拥有时间片和锁标记。

2.2.3、两种同步思路的区别

  1. 同步代码块:线程之间只需要争抢时间片,拥有时间片的线程默认拥有锁标记(效率更高)
  2. 同步方法:线程之间需要争抢时间片以及锁标记(效率慢)

2.2、死锁

通常是由其中一个线程突然休眠导致

当多个线程同时访问多个临界资源对象时:假设线程1拥有锁标记1但是没有时间片和锁标记2,线程2拥有时间片和锁标记2但是没有锁标记1,则双方线程都无法正常执行,程序会被锁死。

结合线程通信来解决死锁问题

2.2.1、线程通信

临界资源对象.方法名()

  1. wait():使写入该方法的当前线程释放自身所有资源,进入无限期等待状态,直到其他线程执行结束将其强制唤醒之后,才能回到就绪状态继续时间片和锁标记的争夺
  2. notify():在当前临界资源的等待队列中随机唤醒一个处于无限期等待状态的线程
    • 该方法的调用者应该与对应wait的调用者保持一致
  3. notifyAll():强制唤醒当前临界资源等待队列中的所有线程

示例:

public class Test2 
    public static void main(String[] args) 
        //创建临界资源对象
        Object o1=new Object();
        Object o2=new Object();

        //创建线程1:先访问o1,再访问o2
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                synchronized (o1)
                    try 
                        Thread.sleep(200);
                     catch (InterruptedException e) 
                        System.out.println("休眠异常!!");
                    
                    synchronized (o2)
                        System.out.println(1);
                        System.out.println(2);
                        System.out.println(3);
                        System.out.println(4);
                        //唤醒t2或t3
                        //o2.notify();
                        //唤醒t2和t3
                        o2.notifyAll();
                    
                
            
        );

        //先访问o2,再访问o1
        Thread t2 = new Thread(new Runnable() 
            @Override
            public void run() 
                synchronized (o2)
                    try 
                        o2.wait();//让当前线程释放自身所有资源,在o2的队列中进入无限期等待
                     catch (InterruptedException e) 
                        System.out.println("操作失败!");
                    
                    synchronized (o1)
                        System.out.println("A");
                        System.out.println("B");
                        System.out.println("C");
                        System.out.println("D");
                    
                
            
        );

        //先访问o2,再访问o1
        Thread t3 = new Thread(new Runnable() 
            @Override
            public void run() 
                synchronized (o2)
                    try 
                        o2.wait();//让当前线程释放自身所有资源,在o2的队列中进入无限期等待
                     catch (InterruptedException e) 
                        System.out.println("操作失败!");
                    
                    synchronized (o1)
                        System.out.println("+");
                        System.out.println("-");
                        System.out.println("*");
                        System.out.println("/");
                    
                
            
        );

        t1.start();
        t2.start();
        t3.start();
    

在线程t2t3中增加wait会释放时间片与锁标记陷入无限期等待,而t1进程可以在使用完成o2资源后唤醒其他线程从而操作o1资源,这样就不会出现死锁的情况。

2.2.2、sleep和wait的区别?

  1. sleep属于Thread类,wait属于Object
  2. sleep进入的是有限期等待,wait进入的是无限期等待
  3. sleep只会释放时间片,wait会释放时间片和锁标记

3、线程安全的集合类

  • 悲观锁:悲观的认为集合一定会出现线程安全问题,所有直接加锁
  • 乐观锁:乐观的认为集合不会出现线程安全问题,所以不加锁,当真正出现问题时,
    再利用算法+少量的synchronized解决问题
  1. ConcurrentHashMap:JDK5.0 java.concurrent

    • JDK8.0之前:悲观锁
      • 在16个数组位上桶加锁
    • JDK8.0之后:CAS算法+少量的synchronized
  2. CopyOnWriteArrayList:JDK5.0 java.concurrent

    • 原理:
      • 当集合进行增删改操作时,会先复制出来一个副本,在副本中进行写操作,如果未出现异常,再将集合地址指向副本地址,若出现异常,则舍弃当前副本,再次尝试。
      • 目的为确保当前集合无异常发生的可能,舍弃写的效率,提高读的效率
    • 适用于读操作远多于写操作时
  3. CopyOnWriteArraySet:JDK5.0 java.concurrent

    • 原理:与CopyOnWriteArrayList一致,在此基础上,如果进行的是增改操作,会进行去重

本文多为总结性内容,建议大家收藏哦~

java开发之——线程面试篇:死锁和如何避免死锁?(代码片段)

在面试的时候在问起线程锁的部分,经常被问到“什么是死锁”、“怎么避免死锁”之类的问题,甚至开发中在使用锁的时候因为逻辑不严谨导致出现程序无法正确终止或者执行的情况,这些都跟死锁有着不可分割的... 查看详情

有了这份java面试中的葵花宝典,让你面试起飞!!!

HashMap面试题HashMap与HashTable的区别1.HashMap线程不安全HashTable线程是安全的采用synchronized2.HashMap允许存放key为nullHashTable不允许存放key为null3.在多线程的情况下,推荐使用ConcurrentHashMap线程安全且效率非常高HashMap底层是如何实现的在... 查看详情

并发编程的缺点?

...和排查;问题常常诡异地出现,又诡异地消失。 Java面试题汇总,总有一道卡住你!  查看详情

007线程风险

一.概述多线程可以帮助我们实现并发,但是并发会带来一些问题.[1]线程安全问题[2]活跃问题[3]性能问题 二.活跃性问题常见的活跃性问题有:[1]死锁:  经典的死锁问题有哲学家问题,当出现死锁的时候,程序就无法继续运行了.... 查看详情

多线程之死锁产生

本篇楼主接着上篇多线程的安全问题继续讨论多线程的死锁问题。我们可以构造这样一种场景:传统(理想)情况下,楼主吃饭必须用两支筷子,而楼主老板(美国人)吃饭必须要用一刀,一叉;现在,楼主手上有一支筷子和一... 查看详情

阿里p7面试题及答案

Java多线程线程池的原理,为什么要创建线程池?线程的生命周期,什么时候会出现僵死进程;什么实现线程安全,如何实现线程安全;创建线程池有哪几个核心参数?如何合理配置线程池的大小?synchronized、volatile区别、synchroni... 查看详情

java线程安全之死锁(代码片段)

...图解 死锁代码演示packageDeadLock;/***死锁代码要会写*一般面试官要求你会写。*只有会写的,才会在以后开发中注意这个事儿*因为死锁很难调试*/publicclassDeadLockDemopublicstaticvoidmain(String[]args)Objecta1=newObject();Objecta2=newObject()... 查看详情

55行代码实现java线程死锁

...ava多线程的重要概念之一,也经常出现在各大公司的笔试面试之中。那么如何创造出一个简单的死锁情况?请看代码:classTestimplementsRunnable    {booleanflag;  Test(booleanflag){this.flag=flag;}publicvoidrun()    {if(flag){while(true) ... 查看详情

解决线程安全问题(代码片段)

线程带来的风险线程安全性问题出现安全性问题的需要满足的条件:多线程环境、有共享资源、非原子性操作活跃性问题死锁饥饿活锁性能问题cpu上下文切换会有性能问题(cpu分时间片执行)锁自旋锁自旋其实就是当一个线程获... 查看详情

分享几道java线程面试题

  不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员的欢迎。大多数待遇丰厚的Java开发职位都要求开发者精通多线程技术并且有丰... 查看详情

[interview]java面试宝典系列之java集合类(代码片段)

文章目录1、Java中有哪些容器(集合类)?2、Java中的容器,线程安全和线程不安全的分别有哪些?3、Map接口有哪些常用实现类?4、描述一下Map集合的put过程5、如何得到一个线程安全的Map?6、HashMap有... 查看详情

20210519使用jstack命令排查线程死锁问题(代码片段)

问题: 针对线上多线程死锁、阻塞,跑着跑着就卡住了;查看线上线程池的状态;jstack主要用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程... 查看详情

3线程带来的风险

线程安全问题从字节码的角度看线程安全性问题多线程环境下多个线程共享一个资源对资源进行非原子性问题线程活跃性问题 1、死锁  产生死锁的原因系统资源不足进程运行推进的顺序不合适资源分配不当    关于死锁出现... 查看详情

面试问题集

数据库两种引擎的区别。java垃圾回收***1000万条短信,选出其中重复数量最多的前10条短信。***消息队列中的方法mysql存储过程线程死锁(的条件,以及如何预防)手写归并排序和快速排序shell脚本常用命令(shell进行词频统计)... 查看详情

(更新中)谈谈个人对java并发编程中(管程模型,死锁,线程生命周期等问题)见解

之前未曾接触过多线程编程 公司的项目开始用到多线程,所以自己谈谈个人对于并发编程的见解。 并发编程会导致线程不安全,常说的线程不安全指的是 多个线程操作一个共享数据,导致线程之间的读取到的数据不... 查看详情

06.线程面试题-02

26)如何写代码来解决生产者消费者问题?  在现实中你解决的许多线程问题都属于生产者消费者模型,就是一个线程生产任务供其它线程进行消费,你必须知道怎么进行线程间通信来解决这个问题。比较低级的办法是用wait和... 查看详情

java岗面试12家大厂成功跳槽,你掌握了多少?

线程线程的启动实现Runnab1e接口继承Thread类实现Callable接口线程的状态线程的方法线程的优先级守护线程未捕获异常处理器并发编程的问题线程引入开销:上下文切换与内存同步线程安全性(原子性+可见性)死锁线... 查看详情

死锁现象

/*java中同步机制解决了线程安全问题,但是也同时引发死锁现象。死锁现象:死锁现象出现的根本原因:1.存在两个或者两个以上的线程。2.存在两个或者两个以上的共享资源。死锁现象的解决方案:没有方案。只能尽量避免发... 查看详情