热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

多线程面经

文章目录线程和进程的区别线程的五个状态多线程中使用run()和start()的区别线程常用的方法synchronized死锁预防死锁:避免死锁:检测死锁

文章目录

  • 线程和进程的区别
  • 线程的五个状态
  • 多线程中使用run()和start()的区别
  • 线程常用的方法
  • synchronized
  • 死锁
    • 预防死锁:
    • 避免死锁:
    • 检测死锁:
    • 解除死锁:
  • CAS操作 (CompareAndSwap 系统原语,底层是用汇编实现的)
  • 锁升级过程
  • 生产者消费者模式
  • 单例模式


线程和进程的区别

线程只能属于一个进程,而进程可以创建多个线程,且最少创建一个主线程
进程是资源分配的基本单位,线程是系统调度的基本单位,线程是真正的执行体

线程的五个状态

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

多线程中使用run()和start()的区别

run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,
调用start()方法来启动一个线程,线程会被放到等待队列,等待CPU调度,并不一定要马上开始执行
在这里插入图片描述

线程常用的方法
  • join()

    就是夹队,暂停其他线程,优先执行此线程,它能够使得t.join()中的t优先执行,当t执行完后才会执行其他线程。能够使得线程之间的并行执行变成串行执行 ,举个例子,如下代码:

public class Mythred implements Runnable {&#64;Overridepublic void run() {for (int i &#61; 0; i < 1000; i&#43;&#43;) {System.out.println("vip插队啦" &#43; i);}}public static void main(String[] args) throws InterruptedException {Thread thread1 &#61; new Thread(new Mythred());thread1.start();for (int i &#61; 0; i < 1000; i&#43;&#43;) {if (i &#61;&#61; 50) thread1.join();System.out.println("普通用户排队中" &#43; i);}}}

起初两个线程并行执行,打印着各自的结果,在主线程执行到50时,thred1调用啦join()方法,暂停啦主线程,
执行完thred1的内容后,才继续执行主线程的内容

在这里插入图片描述

  • yield()

  1. 暂停当前正在执行的线程对象(将线程转为就绪态),并执行其他线程
  2. 让cpu重新调度,但不一定成功

  • stop()
    停止线程,不推荐使用

  • Thread.currentThread().getName()
    获取当前线程名字


synchronized

部分内容摘自 synchronized

synchronized关键字最主要有以下3种应用方式&#xff0c;下面分别介绍

  • 修饰实例方法&#xff0c;作用于当前实例加锁&#xff0c;进入同步代码前要获得当前实例的锁
  • 修饰静态方法&#xff0c;作用于当前类对象加锁&#xff0c;进入同步代码前要获得当前类对象的锁
  • 修饰代码块&#xff0c;指定加锁对象&#xff0c;对给定对象加锁&#xff0c;进入同步代码库前要获得给定对象的锁。

注意锁的必须是同一个对象时才管用,对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁,若是不同对象,岂不就没啦标志的意义?

死锁

部分内容摘自 添加链接描述

产生的四个必要条件

  • 1、互斥使用&#xff0c;即当资源被一个线程使用(占有)时&#xff0c;别的线程不能使用
  • 2、不可抢占&#xff0c;资源请求者不能强制从资源占有者手中夺取资源&#xff0c;资源只能由资源占有者主动释
  • 3、请求和保持&#xff0c;即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  • 4、循环等待&#xff0c;即存在一个等待队列&#xff1a;P1占有P2的资源&#xff0c;P2占有P3的资源&#xff0c;P3占有P1的资源。这样就形成了一个等待环路。

产生的原因: 多个线程争夺资源而产生的互相等待的状况

举个例子: 有两个线程,线程1占用啦打印机这个对象锁(其他线程在这个线程释放打印机这个对象锁前是不能获取这个打印机锁的),线程2占用啦电脑锁,现在线程1申请获取线程2的电脑锁,线程2申请获取线程1的打印机锁,结果就导致两个线程在没有拿到自己想要的对象锁前是不会释放自己目前所占有的锁的,然后两者就处于相互等待的状况!

代码演示

public class Ticket implements Runnable {//线程开启标志位,若是1占有ob1的锁,若是2占有ob2的锁int flag;//打印机锁public static Object ob1 &#61; new Object();//电脑锁public static Object ob2 &#61; new Object();&#64;Overridepublic void run() {if (flag &#61;&#61; 1) {synchronized (ob1) {System.out.println(Thread.currentThread().getName() &#43; "目前拥有打印机ob1的锁");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//在未释放ob1锁前申请获取ob2的锁synchronized (ob2) {System.out.println(Thread.currentThread().getName() &#43; "目前拥有打印机ob1和ob2的锁");}}} else {synchronized (ob2) {System.out.println(Thread.currentThread().getName() &#43;"目前拥有打印机ob2的锁");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//在未释放ob2锁前申请获取ob1![在这里插入图片描述](https://img-blog.csdnimg.cn/20200826164702854.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ0NjQzMDUx,size_16,color_FFFFFF,t_70#pic_center)
的锁synchronized (ob1) {System.out.println(Thread.currentThread().getName() &#43;"目前拥有打印机ob1和ob2的锁");}}}}//构造函数,初始化线程最初拥有的对象锁public Ticket(int flag) {this.flag &#61; flag;}//现在要模拟的情况是,两个线程争夺对方拥有的对象锁public static void main(String[] args) throws InterruptedException {Ticket ticket1 &#61; new Ticket(1);Ticket ticket2 &#61; new Ticket(2);Thread thread1 &#61; new Thread(ticket1);Thread thread2 &#61; new Thread(ticket2);thread1.start();thread2.start();}}

结果
上述就是发生死锁的过程

解决死锁的办法

预防死锁&#xff1a;

通过设置某些限制条件&#xff0c;去破坏产生死锁的四个必要条件中的一个或几个条件&#xff0c;来防止死锁的发生

  1. 破坏“互斥”条件 “ 互斥”条件是无法破坏的。因此&#xff0c;在死锁预防里主要是破坏其他几个必要条件&#xff0c;而不去涉及破坏“互斥”条件。
  2. 破坏“占有并等待”条件 要求每个进程提出新的资源申请前&#xff0c;释放它所占有的资源。这样&#xff0c;一个进程在需要资源S时&#xff0c;须先把它先前占有的资源R释放掉&#xff0c;然后才能提出对S的申请&#xff0c;即使它可能很快又要用到资源R。要求每个进程提出新的资源申请前&#xff0c;释放它所占有的资源。这样&#xff0c;一个进程在需要资源S时&#xff0c;须先把它先前占有的资源R释放掉&#xff0c;然后才能提出对S的申请&#xff0c;即使它可能很快又要用到资源R。
  3. 破坏“不可抢占”条件 破坏“不可抢占”条件就是允许对资源实行抢夺。
    如果一个进程请求当前被另一个进程占有的一个资源&#xff0c;则操作系统可以抢占另一个进程&#xff0c;要求它释放资源。只有在任意两个进程的优先级都不相同的条件下&#xff0c;方法二才能预防死锁。
  4. 破坏“循环等待”条件 破坏“循环等待”条件的一种方法&#xff0c;是将系统中的所有资源统一编号&#xff0c;进程可在任何时刻提出资源申请&#xff0c;但所有申请必须按照资源的编号顺序&#xff08;升序&#xff09;提出。这样做就能保证系统不出现死锁。

避免死锁&#xff1a;

在资源的动态分配过程中&#xff0c;用某种方法去防止系统进入不安全状态&#xff0c;从而避免死锁的发生

  1. 有序资源分配法 这种算法资源按某种规则系统中的所有资源统一编号&#xff08;例如打印机为1、磁带机为2、磁盘为3、等&#xff09;&#xff0c;申请时必须以上升的次序。系统要求申请进程&#xff1a;
    1、对它所必须使用的而且属于同一类的所有资源&#xff0c;必须一次申请完&#xff1b;
    2、在申请不同类资源时&#xff0c;必须按各类设备的编号依次申请。例如&#xff1a;进程PA&#xff0c;使用资源的顺序是R1&#xff0c;R2&#xff1b; 进程PB&#xff0c;使用资源的顺序是R2&#xff0c;R1&#xff1b;若采用动态分配有可能形成环路条件&#xff0c;造成死锁。
    采用有序资源分配法&#xff1a;R1的编号为1&#xff0c;R2的编号为2&#xff1b;
    PA&#xff1a;申请次序应是&#xff1a;R1&#xff0c;R2
    PB&#xff1a;申请次序应是&#xff1a;R1&#xff0c;R2
    这样就破坏了环路条件&#xff0c;避免了死锁的发生
  2. 银行家算法

检测死锁&#xff1a;

允许系统在运行过程中发生死锁&#xff0c;但可设置检测机构及时检测死锁的发生&#xff0c;并采取适当措施加以清除

解除死锁&#xff1a;

当检测出死锁后&#xff0c;便采取适当措施将进程从死锁状态中解脱出来

CAS操作 (CompareAndSwap 系统原语,底层是用汇编实现的)

是用汇编编写的一道指令,偏硬件级别 lock cmpxchg, 作用是多个线程操作同一变量时,能保证线程安全,java中关于cas的操作其实也是调用的本地方法

具体操作思路:比如现在,现有一个变量&#xff49;值为0&#xff0c;多个线程想对这个变量进行自增的操作。CAS的具体操作如下&#xff0c;线程1访问变量i的时候&#xff0c;会把变量i的初始值&#xff0c;存在另一个变量e中&#xff0c;之后线程1再进行i自增的操作&#xff0c;此时&#xff0c;i的新值应该是1&#xff0c;但这个这时候不着急把新值赋给i,而是先再将e的值与i当前的值进行比较(因为其他线程有可能在此时更改啦i的值,也就是说i本身的值是有随时有可能变化的,你不能把前一秒i的值自加吧 )&#xff0c;进行比较&#xff0c;如果相等,则说明此时i的值未经其他线程的变更,是准确无误的值,可以放心的把新值赋给i,若不相等,说明此时有线程动过这个值,那怎么办,回炉重新走遍这个流程! 那么在这无限回炉重复的过程也就是自旋操作!

具体思路
在这里插入图片描述

锁升级过程

无锁–>>偏向锁->>自旋锁(轻量级锁)->>重量级锁

最开始,当线程没有获取锁对象时,处于无锁状态,当只有一个线程(没有竞争),当此线程获取对象锁时,升级为偏向锁

  • 偏向锁
    其实就是在对象头(markword)中把自己线程的id加入进去,当此线程再次尝试获取这个对象锁时,将自己的线程id与对象头中的记录的线程id进行比对,如果相同,则此线程便可以拥有这把锁,因为这把锁总是自己拥有,所以叫做偏向锁

当超过1个线程发生竞争对象锁时,此时便自动升级为自旋锁(轻量级锁)

  • 自旋锁
    当另一个线程尝试获取对象锁时,发现这把锁被其他线程占有,此时它并不放弃cpu的执行,而是选择不断的判断这把锁是否已经释放,这个过程叫做自旋,特点是占有cpu,并不放弃cpu的使用权,但损耗cpu较大,适用于同步代码块执行时间短,线程数较少,

当线程数较多,且自旋次数超过10次时,便升级为重量级锁

  • 重量级锁
    把工作交给操作系统,当另一个线程尝试获取对象锁时,发现这把锁被其他线程占有,此时操作系统会将此线程加入到阻塞队列里,由操作系统负责调度,特点是不占有cpu的使用权,适用于同步代码块执行时间长,线程数较多,

生产者消费者模式

就是两个线程&#xff0c;一个生产者当没货啦则负责生产&#xff0c;产完后等待&#xff0c;当有货时就唤醒生产者&#xff1b;&#xff1b;一个消费者当有货啦则负责消费&#xff0c;消费后就等待&#xff0c;当没货时就唤醒生产者&#xff1b;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Test {static int baozi &#61; 0;static Lock lock &#61; new ReentrantLock();public static void main(String[] args) {new Thread(new Producer()).start();new Thread(new Customer()).start();}static class Producer implements Runnable {&#64;Overridepublic void run() {while (true) {synchronized (lock) {if (baozi &#61;&#61; 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}baozi&#43;&#43;;System.out.println("生产完后当前包子数目&#xff1a;" &#43; baozi);lock.notify();} else {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}static class Customer implements Runnable {&#64;Overridepublic void run() {while (true) {synchronized (lock) {if (baozi !&#61; 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}baozi--;System.out.println("消费完后当前包子数目&#xff1a;" &#43; baozi);lock.notify();} else {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}

单例模式

最最基础的单例模式手写&#xff0c;死也呆给记住&#xff1b;

public class SingleExample {private static volatile SingleExample example;public static synchronized SingleExample getExample() {if (example &#61;&#61; null) {synchronized (SingleExample.class) {if (example &#61;&#61; null) {example &#61; new SingleExample();}}}return example;}public static void main(String[] args) {System.out.println(SingleExample.getExample());}
}


推荐阅读
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • 1Lock与ReadWriteLock1.1LockpublicinterfaceLock{voidlock();voidlockInterruptibl ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 本文介绍了Java集合库的使用方法,包括如何方便地重复使用集合以及下溯造型的应用。通过使用集合库,可以方便地取用各种集合,并将其插入到自己的程序中。为了使集合能够重复使用,Java提供了一种通用类型,即Object类型。通过添加指向集合的对象句柄,可以实现对集合的重复使用。然而,由于集合只能容纳Object类型,当向集合中添加对象句柄时,会丢失其身份或标识信息。为了恢复其本来面貌,可以使用下溯造型。本文还介绍了Java 1.2集合库的特点和优势。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 模块化区块链生态系统的优势概述及其应用案例
    本文介绍了相较于单体区块链,模块化区块链生态系统的优势,并以Celestia、Dymension和Fuel等模块化区块链项目为例,探讨了它们解决可扩展性和部署问题的方案。模块化区块链架构提高了区块链的可扩展性和吞吐量,并提供了跨链互操作性和主权可扩展性。开发人员可以根据需要选择执行环境,并获得奖学金支持。该文对模块化区块链的应用案例进行了介绍,展示了其在区块链领域的潜力和前景。 ... [详细]
  • BZOJ1233 干草堆单调队列优化DP
    本文介绍了一个关于干草堆摆放的问题,通过使用单调队列来优化DP算法,求解最多可以叠几层干草堆。具体的解题思路和转移方程在文章中进行了详细说明,并给出了相应的代码示例。 ... [详细]
  • 在IDEA中运行CAS服务器的配置方法
    本文介绍了在IDEA中运行CAS服务器的配置方法,包括下载CAS模板Overlay Template、解压并添加项目、配置tomcat、运行CAS服务器等步骤。通过本文的指导,读者可以轻松在IDEA中进行CAS服务器的运行和配置。 ... [详细]
  • 本文由编程笔记#小编为大家整理,主要介绍了源码分析--ConcurrentHashMap与HashTable(JDK1.8)相关的知识,希望对你有一定的参考价值。  Concu ... [详细]
author-avatar
zc163com
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有