热门标签 | 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 Service 组件中 onStartCommand 方法的四种不同返回值及其应用场景。Service 可以在后台执行长时间的操作,无需提供用户界面,支持通过启动和绑定两种方式创建。 ... [详细]
  • 使用R语言进行Foodmart数据的关联规则分析与可视化
    本文探讨了如何利用R语言中的arules和arulesViz包对Foodmart数据集进行关联规则的挖掘与可视化。文章首先介绍了数据集的基本情况,然后逐步展示了如何进行数据预处理、规则挖掘及结果的图形化呈现。 ... [详细]
  • 本文探讨了在不同场景下如何高效且安全地存储Token,包括使用定时器刷新、数据库存储等方法,并针对个人开发者与第三方服务平台的不同需求提供了具体建议。 ... [详细]
  • Java高级工程师学习路径及面试准备指南
    本文基于一位朋友的PDF面试经验整理,涵盖了Java高级工程师所需掌握的核心知识点,包括数据结构与算法、计算机网络、数据库、操作系统等多个方面,并提供了详细的参考资料和学习建议。 ... [详细]
  • Hadoop MapReduce 实战案例:手机流量使用统计分析
    本文通过一个具体的Hadoop MapReduce案例,详细介绍了如何利用MapReduce框架来统计和分析手机用户的流量使用情况,包括上行和下行流量的计算以及总流量的汇总。 ... [详细]
  • 如何高效学习鸿蒙操作系统:开发者指南
    本文探讨了开发者如何更有效地学习鸿蒙操作系统,提供了来自行业专家的建议,包括系统化学习方法、职业规划建议以及具体的开发技巧。 ... [详细]
  • Android 开发技巧:使用 AsyncTask 实现后台任务与 UI 交互
    本文详细介绍了如何在 Android 应用中利用 AsyncTask 来执行后台任务,并及时将任务进展反馈给用户界面,提高用户体验。 ... [详细]
  • 如何为PDF文档添加水印?简单步骤实现
    为了增强PDF文档的安全性和版权保护,添加水印是一个有效的方法。本文将介绍如何通过专业软件或在线工具轻松为PDF文档添加水印,确保您的文档在共享时仍能保持其独特性和安全性。 ... [详细]
  • This article explores the process of integrating Promises into Ext Ajax calls for a more functional programming approach, along with detailed steps on testing these asynchronous operations. ... [详细]
  • 我在尝试将组合框转换为具有自动完成功能时遇到了一个问题,即页面上的列表框也被转换成了自动完成下拉框,而不是保持原有的多选列表框形式。 ... [详细]
  • 本文详细介绍了Socket在Linux内核中的实现机制,包括基本的Socket结构、协议操作集以及不同协议下的具体实现。通过这些内容,读者可以更好地理解Socket的工作原理。 ... [详细]
  • 个人博客:打开链接依赖倒置原则定义依赖倒置原则(DependenceInversionPrinciple,DIP)定义如下:Highlevelmo ... [详细]
  • UVa 11683: 激光雕刻技术解析
    自1958年发明以来,激光技术已在众多领域得到广泛应用,包括电子设备、医疗手术工具、武器等。本文将探讨如何使用激光技术进行材料雕刻,并通过编程解决一个具体的激光雕刻问题。 ... [详细]
  • Excel技巧:单元格中显示公式而非结果的解决方法
    本文探讨了在Excel中如何通过简单的方法解决单元格显示公式而非计算结果的问题,包括使用快捷键和调整单元格格式两种方法。 ... [详细]
  • 本文旨在探讨Swift中的Closure与Objective-C中的Block之间的区别与联系,通过定义、使用方式以及外部变量捕获等方面的比较,帮助开发者更好地理解这两种机制的特点及应用场景。 ... [详细]
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社区 版权所有