Java多线程--多线程知识点总结和企业真题

文章目录

  • 一、知识点总结
    • (1)多线程相关概念
    • (2)创建多线程的基本方式
    • (3)Thread类的常用结构
    • (4)线程的生命周期
    • (5)线程的安全问题与线程的同步机制
    • (6)解决单例模式中的懒汉式的线程安全问题
    • (7)线程的同步机制带来的问题:死锁
    • (8)Lock的使用
    • (9)线程的通信
    • (10)JDK5.0新增线程创建方式
  • 二、企业真题
    • (1)线程概述
      • 1、题1
      • 2、题2
      • 3、题3
    • (2)如何实现多线程
      • 1、题1
      • 2、题2
      • 3、题3
      • 4、题4
      • 5、题5
    • (3)常用方法、生命周期
      • 1、题1
      • 2、题2
      • 3、题3
      • 4、题4
      • 5、题5
      • 6、题6
    • (4)线程安全与同步机制
      • 1、题1
      • 2、题2
      • 3、题3
      • 4、题4
      • 5、题5
      • 6、题6
      • 7、题7
      • 8、题8
    • (5)死锁
      • 1、题1
      • 2、题2
    • (6)线程通信
      • 1、题1
      • 2、题2
      • 3、题3
      • 4、题4
    • (7)单例模式(线程安全)
      • 1、题1
      • 2、题2

一、知识点总结

(1)多线程相关概念

详细博文链接:

https://blog.csdn.net/m0_55746113/article/details/135691683

️知识点梳理

1、程序、进程和线程的区分:

  • 程序(program):为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码。
  • 进程(process):程序的一次执行过程,或是正在内存中运行的应用程序。程序是静态的,进程是动态的
    • 进程作为操作系统调度和分配资源的最小单位
  • 线程(thread):进程可进一步细化为线程,是程序内部的一条执行路径。
    • 线程作为CPU调度和执行的最小单位

栈管运行”:Java里面通过方法的调用体现运行的过程,每一个方法的调用都对应一个栈帧,要入栈运行。

堆管存储”:对象的创建、数组的创建,这些数据都放在堆里面。

运行的时候,最小的调度单位就是“线程”,每个线程有一个栈。

2、线程调度策略

  • 分时调度:所有线程轮流使用 CPU 的使用权,并且平均分配每个线程占用 CPU 的时间。
  • 抢占式调度:让优先级高的线程以较大的概率优先使用 CPU。如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

3、了解

  • 单核CPU与多核CPU
  • 并行与并发

(2)创建多线程的基本方式

详细博文链接:

线程的创建方式一:继承Thread类

https://blog.csdn.net/m0_55746113/article/details/135708814

线程的创建方式二:实现Runnable接口

https://blog.csdn.net/m0_55746113/article/details/135840102

️知识点梳理

1、线程的创建方式一:继承Thread类

1.1 步骤:

① 创建一个继承于Thread类的子类

② 重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中

③ 创建当前Thread的子类的对象

④ 通过对象调用start(): 1.启动线程 2.调用当前线程的run()

start()是父类Thread的方法。

1.2 例题:创建一个分线程1,用于遍历100以内的偶数

【拓展】 再创建一个分线程2,用于遍历100以内的偶数

2、线程的创建方式二:实现Runnable接口

2.1 步骤:

① 创建一个实现Runnable接口的类

② 实现接口中的run() -->将此线程要执行的操作,声明在此方法体中

③ 创建当前实现类的对象

④ 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例

⑤ Thread类的实例调用start():1.启动线程 2.调用当前线程的run()

2.2 例题:创建分线程遍历100以内的偶数

3、对比两种方式?

  • 共同点:
    • ① 启动线程,使用的都是Thread类中定义的start()
    • ② 创建的线程对象,都是Thread类或其子类的实例。
  • 不同点:一个是类的继承,一个是接口的实现。
  • 建议:建议使用实现Runnable接口的方式。
  • Runnable方式的好处:
    • ① 实现的方式,避免的类的单继承的局限性
    • ② 更适合处理有共享数据的问题。
    • ③ 实现了代码和数据的分离。
  • 联系:public class Thread implements Runnable (代理模式)

(3)Thread类的常用结构

详细博文链接:

https://blog.csdn.net/m0_55746113/article/details/135881677

️知识点梳理

1、线程中的构造器

  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法。
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

2、线程中的常用方法

  • start():①启动线程 ②调用线程的run()。
  • run():将线程要执行的操作,声明在run()中。
  • currentThread():获取当前执行代码对应的线程。
  • getName(): 获取线程名。
  • setName(): 设置线程名。
  • sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数。
  • yield():静态方法,一旦执行此方法,就释放CPU的执行权。
  • join(): 在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行。
  • isAlive():判断当前线程是否存活。

过时方法:

  • stop():强行结束一个线程的执行,直接进入死亡状态。不建议使用。
  • void suspend() / void resume() :可能造成死锁,所以也不建议使用。

3、线程的优先级:

  • getPriority():获取线程的优先级。
  • setPriority():设置线程的优先级。范围[1,10]。

Thread类内部声明的三个常量:

  • MAX_PRIORITY(10):最高优先级
  • MIN _PRIORITY (1):最低优先级
  • NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。

(4)线程的生命周期

详细博文链接:

https://blog.csdn.net/m0_55746113/article/details/135885857

️知识点梳理

JDK1.5之前:(5种状态)

Java多线程--多线程知识点总结和企业真题_第1张图片

JDK1.5之后:(6种状态)

Java多线程--多线程知识点总结和企业真题_第2张图片

Thread类中定义了一个内部类State

public enum State {
        
        NEW,
    
        RUNNABLE,

        BLOCKED,

        WAITING,

        TIMED_WAITING,
 
        TERMINATED;
}

(5)线程的安全问题与线程的同步机制

详细博文链接:

https://blog.csdn.net/m0_55746113/article/details/135918247

️知识点梳理

1、多线程卖票,出现的问题:出现了重票和错票。

2、什么原因导致的?

线程1操作ticket的过程中,尚未结束的情况下,其他线程也参与进来,对ticket进行操作。

  • 什么是线程的安全问题?多个线程操作共享数据,就有可能出现安全问题

3、如何解决?

必须保证一个线程a在操作ticket的过程中,其它线程必须等待,直到线程a操作ticket结束以后,其它线程才可以进来继续操作ticket。

4、Java是如何解决线程的安全问题的?使用线程的同步机制

  • 如何解决线程的安全问题?有几种方式?
    • 同步机制:① 同步代码块同步方法
      • 重点关注两个事:共享数据及操作共享数据的代码;同步监视器(保证唯一性)
    • jdk5.0新增:Lock接口及其实现类。(保证多个线程共用同一个Lock的实例)
在实现Runnable接口的方式中,同步监视器可以考虑使用:this。
在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用:当前类.class。
非静态的同步方法,默认同步监视器是this
静态的同步方法,默认同步监视器是当前类本身。

①方式1:同步代码块

synchronized(同步监视器){
    //需要被同步的代码
}

说明:

  • 需要被同步的代码,即为操作共享数据的代码。
  • 共享数据:即多个线程都需要操作的数据。比如:ticket
  • 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其它线程必须等待。
  • 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。
  • 同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。

注意:

  • 在实现Runnable接口的方式中,同步监视器可以考虑使用:this
  • 在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用:当前类.class

②方式2:同步方法

说明:

  • 如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。
  • 非静态的同步方法,默认同步监视器是this静态的同步方法,默认同步监视器是当前类本身。

5、synchronized

  • 好处:解决了线程的安全问题。
  • 弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低。

(6)解决单例模式中的懒汉式的线程安全问题

详细博文链接:

https://blog.csdn.net/m0_55746113/article/details/135971616

️知识点梳理

  • 饿汉式:不存在线程安全问题。
  • 懒汉式:存在线程安全问题,(需要使用同步机制来处理)

(7)线程的同步机制带来的问题:死锁

详细博文链接:

https://blog.csdn.net/m0_55746113/article/details/135990968

️知识点梳理

1、如何看待死锁?

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

我们编写程序时,要避免出现死锁

2、诱发死锁的原因?

  • 互斥条件
  • 占用且等待
  • 不可抢夺(或不可抢占)
  • 循环等待

以上4个条件,同时出现就会触发死锁。

3、如何避免死锁?

针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。

针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。

针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。

针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。

(8)Lock的使用

详细博文链接:

https://blog.csdn.net/m0_55746113/article/details/135990968

️知识点梳理

除了使用synchronized同步机制处理线程安全问题之外,还可以使用jdk5.0提供的Lock锁的方式。

1、步骤

步骤1:创建Lock的实例,需要确保多个线程共用同一个Lock实例。

需要考虑将此对象声明为static final

步骤2.:执行lock()方法,锁定对共享资源的调用。

步骤3.:unlock()的调用,释放对共享数据的锁定。

2、面试题

synchronized同步的方式 与Lock的对比 ?

synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放对同步监视器的调用。

Lock是通过两个方法控制需要被同步的代码,更灵活一些。

Lock作为接口,提供了多种实现类,适合更多更复杂的场景,效率更高。

(9)线程的通信

详细博文链接:

https://blog.csdn.net/m0_55746113/article/details/135998682

️知识点梳理

  • 在同步机制下,考虑线程间的通信
  • wait() 、notify() 、notifyAll() 都需要使用在同步代码块或同步方法中。
  • 高频笔试题:wait() / sleep()

1、线程间通信的理解

当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。

2、涉及到三个方法的使用

wait():线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用。

notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait()的多个线程的优先级相同,则随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行。

notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

3、️注意点

  • 此三个方法的使用,必须是在同步代码块或同步方法中。(超纲:Lock需要配合Condition实现线程间的通信)
  • 此三个方法的调用者,必须是同步监视器。否则,会报IllegalMonitorStateException异常。
  • 此三个方法声明在Object类中。

4、案例

案例1:使用两个线程打印 1-100。线程1, 线程2 交替打印。

案例2:生产者&消费者

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品。

店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

5、wait()sleep()的区别?

  • 相同点:一旦执行,当前线程都会进入阻塞状态。
  • 不同点:
    • 声明的位置
      • wait():声明在Object类中。
      • sleep():声明在Thread类中,静态的。
    • 使用的场景不同
      • wait():只能使用在同步代码块或同步方法中。
      • sleep():可以在任何需要使用的场景。
    • 使用在同步代码块或同步方法中
      • wait():一旦执行,会释放同步监视器。
      • sleep():一旦执行,不会释放同步监视器。
    • 结束阻塞的方式
      • wait():到达指定时间自动结束阻塞 或 通过被notify唤醒,结束阻塞。
      • sleep():到达指定时间自动结束阻塞。

(10)JDK5.0新增线程创建方式

详细博文链接:

https://blog.csdn.net/m0_55746113/article/details/136008885

️知识点梳理

1、创建多线程的方式三:实现Callable (jdk5.0新增的)

与之前的方式的对比:与Runnable方式的对比的好处

  • call()可以有返回值,更灵活。
  • call()可以使用throws的方式处理异常,更灵活。
  • Callable使用了泛型参数,可以指明具体的call()的返回值类型,更灵活。

缺点吗?如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的。

2、创建多线程的方式四:使用线程池

此方式的好处:

  • 提高了程序执行的效率。(因为线程已经提前创建好了)
  • 提高了资源的复用率。(因为执行完的线程并未销毁,而是可以继续执行其他的任务)
  • 可以设置相关的参数,对线程池中的线程的使用进行管理。

二、企业真题

(1)线程概述

1、题1

题目描述

什么是线程(*云网络)

进程可进一步细化为线程,是程序内部的一条执行路径。

线程作为CPU调度和执行的最小单位

2、题2

题目描述

线程和进程有什么区别(团、腾云网络、神**岳、言有物、直*科技)

进程:对应一个运行中的程序。

线程:运行中的进程的一条或多条执行路径。

3、题3

题目描述

多线程使用场景(嘉*医疗)

  • 手机app应用的图片的下载。
  • 迅雷的下载。
  • Tomcat服务器上web应用,多个客户端发起请求,Tomcat针对多个请求开辟多个线程处理。

(2)如何实现多线程

1、题1

题目描述

如何在Java中出实现多线程?(阿校招、当置业、鸿网络、奥医药、科软、慧、上海驿软件、海科)

类似问题:
> 创建多线程用Runnable还是Thread(北京中*瑞飞)
> 多线程有几种实现方法,都是什么?(锐*(上海)企业管理咨询)

四种方式:

  • 方式1:继承Thread类
  • 方式2:实现Runnable接口
  • 方式3:实现Callable接口 (jdk5.0新增)
  • 方式4:使用线程池(jdk5.0新增)

2、题2

题目描述

Thread类中的start()和run()有什么区别?(北京中油**、爱信、神泰岳、直*科技,软国际,上海学网络)

start():① 开启线程 ② 调用线程的run()

run():调用run()方法,线程可能根本没有开启,还在原有的线程当中,只是当前对象调用方法而已。

3、题3

题目描述

启动一个线程是用run()还是start()?(*度)

start()

4、题4

题目描述

Java中Runnable和Callable有什么不同?(平金服、银数据、好在、亿征信、花儿**网络)

与之前的方式的对比:与Runnable方式的对比的好处

  • call()可以有返回值,更灵活。
  • call()可以使用throws的方式处理异常,更灵活。
  • Callable使用了泛型参数,可以指明具体的call()的返回值类型,更灵活。

缺点吗?如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的。

5、题5

题目描述

什么是线程池,为什么要使用它?(上海明*物联网科技)

此方式的好处:

  • 提高了程序执行的效率。(因为线程已经提前创建好了)
  • 提高了资源的复用率。(因为执行完的线程并未销毁,而是可以继续执行其他的任务)
  • 可以设置相关的参数,对线程池中的线程的使用进行管理。

(3)常用方法、生命周期

1、题1

题目描述

sleep() 和 yield()区别?(神*泰岳)

sleep():一旦调用,就进入“阻塞”(或TIMED_WAITING状态)。

yield():释放cpu的执行权,处在RUNNABLE的状态。

2、题2

题目描述

线程创建的中的方法、属性情况?(招通**、数*互融)

①线程中的构造器

  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法。
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

②线程中的常用方法

  • start():①启动线程 ②调用线程的run()。
  • run():将线程要执行的操作,声明在run()中。
  • currentThread():获取当前执行代码对应的线程。
  • getName(): 获取线程名。
  • setName(): 设置线程名。
  • sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数。
  • yield():静态方法,一旦执行此方法,就释放CPU的执行权。
  • join(): 在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行。
  • isAlive():判断当前线程是否存活。

3、题3

题目描述

线程的生命周期?(中国**电子商务中心、科软、慧)

JDK1.5之前:(5种状态)

Java多线程--多线程知识点总结和企业真题_第3张图片

JDK1.5之后:(6种状态)

Java多线程--多线程知识点总结和企业真题_第4张图片

4、题4

题目描述

线程的基本状态以及状态之间的关系?(直*科技)

类似问题:
> 线程有哪些状态?如何让线程进入阻塞?(华*中*,*兴)
> 线程有几个状态,就绪和阻塞有什么不同。(美*)
> Java的线程都有哪几种状态(字*跳动、*东、*手)

JDK1.5之前:(5种状态)
Java多线程--多线程知识点总结和企业真题_第5张图片

JDK1.5之后:(6种状态)
Java多线程--多线程知识点总结和企业真题_第6张图片

5、题5

题目描述

stop()suspend()方法为何不推荐使用?(上海驿*软件)

stop():一旦执行,线程就结束了,导致run()有未执行结束的代码。stop()会导致释放同步监视器,导致线程安全问题。

suspend():与resume()搭配使用,导致死锁。

6、题6

题目描述

Java 线程优先级是怎么定义的?(软*动力)

线程的优先级:

  • getPriority():获取线程的优先级。
  • setPriority():设置线程的优先级。范围[1,10]。

Thread类内部声明的三个常量:

  • MAX_PRIORITY(10):最高优先级
  • MIN _PRIORITY (1):最低优先级
  • NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。

(4)线程安全与同步机制

1、题1

题目描述

你如何理解线程安全的?线程安全问题是如何造成的?(*软国际)

类似问题:
> 线程安全说一下?(奥*医药)
> 对线程安全的理解(*度校招)
> 什么是线程安全?(银*数据)

  • 什么是线程的安全问题?多个线程操作共享数据,就有可能出现安全问题
  • 如何解决线程的安全问题?有几种方式?
    • 同步机制:① 同步代码块 ② 同步方法
      • 重点关注两个事:共享数据及操作共享数据的代码;同步监视器(保证唯一性)
    • jdk5.0新增:Lock接口及其实现类。(保证多个线程共用同一个Lock的实例)
在实现Runnable接口的方式中,同步监视器可以考虑使用:this。
在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用:当前类.class。
非静态的同步方法,默认同步监视器是this
静态的同步方法,默认同步监视器是当前类本身。

2、题2

题目描述

多线程共用一个数据变量需要注意什么?(史*夫软件)

线程安全问题。

3、题3

题目描述

多线程保证线程安全一般有几种方式?(来科技、北京信天*)

类似问题:
> 如何解决其线程安全问题,并且说明为什么这样子去解决?(北京联合**)
> 请说出你所知道的线程同步的方法。(天*伟业)
> 哪些方法实现线程安全?(阿*)   
> 同步有几种实现方法,都是什么? (锐*企业管理咨询)
> 你在实际编码过程中如何避免线程安全问题?(*软国际)
> 如何让线程同步?(*手)
> 多线程下有什么同步措施(阿*校招)
> 同步有几种实现方法,都是什么?(海*科)

  • 同步机制(同步代码块、同步方法)
  • Lock接口

4、题4

题目描述

用什么关键字修饰同步方法?(上海驿*软件)

synchronized

5、题5

题目描述

synchronized加在静态方法和普通方法区别(来*科技)

同步监视器不同。

  • 静态:当前类本身 (其实是一个对象)
  • 非静态:this

6、题6

题目描述

Java中synchronized和ReentrantLock有什么不同(三*重工)

类似问题:
> 多线程安全机制中 synchronized和lock的区别(中*国际、*美、鸿*网络)
> 怎么实现线程安全,各个实现方法有什么区别?(美*、字*跳动)
> synchronized 和 lock 区别(阿*、*壳)

synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放对同步监视器的调用。

Lock是通过两个方法控制需要被同步的代码,更灵活一些。

Lock作为接口,提供了多种实现类,适合更多更复杂的场景,效率更高。

7、题7

题目描述

当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?(鸿*网络)

需要看其他方法是否使用synchronized修饰,同步监视器的this是否是同一个。

只有当使用了synchronized,且this是同一个的情况下,就不能访问了。

8、题8

题目描述

线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?(阿校招、西安创佳*)

同步一定阻塞;

阻塞不一定同步。

(5)死锁

1、题1

题目描述

什么是死锁,产生死锁的原因及必要条件(腾*、阿*)

①如何看待死锁?

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
我们编写程序时,要避免出现死锁。

②诱发死锁的原因?

  • 互斥条件
  • 占用且等待
  • 不可抢夺(或不可抢占)
  • 循环等待

以上4个条件,同时出现就会触发死锁。

2、题2

题目描述

如何避免死锁?(阿*、北京*蓝、*手)

针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。

针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。

针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。

针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。

(6)线程通信

1、题1

题目描述

Java中notify()和notifyAll()有什么区别(汇*天下)

notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait()的多个线程的优先级相同,则随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行。

notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

2、题2

题目描述

为什么wait()和notify()方法要在同步块中调用(北京*智)

因为调用者必须是同步监视器。

3、题3

题目描述

多线程:生产者,消费者代码(同步、wait、notifly编程)(猫*娱乐)

类似问题:
> 如何写代码来解决生产者消费者问题(上海明*物联网)
> 多线程中生产者和消费者如何保证同步(*为)
> 消费者生产者,写写伪代码(字*)

package yuyi04.Communication;

/**
 * ClassName: ProducterConsumerTest
 * Package: yuyi04.Communication
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/2/2 0002 17:15
 */
public class ProducerConsumerTest {
    public static void main(String[] args) {
        Clerk clerk=new Clerk();    //clerk本身就充当了共享数据

        Producer pro1=new Producer(clerk);  //创建生产者对象pro1
        Consumer con1=new Consumer(clerk);  //创建消费者对象con1
        Consumer con2=new Consumer(clerk);  //创建消费者对象con1

        pro1.setName("生产者1");
        con1.setName("消费者1");
        con2.setName("消费者2");

        pro1.start();
        con1.start();
        con2.start();
    }
}

class Clerk{    //店员
    private int productNum=0;   //产品数量

    //增加产品数量的方法(对属性的修改通常用方法来体现)
    public synchronized void addProduct(){  //wait()需要在同步代码块或同步方法中去使用
        if(productNum>=20){ //产品数量大于等于20
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            productNum++;   //让产品加一
            System.out.println(Thread.currentThread().getName()+"生产了第"+productNum+"个产品");

            //生产了一个就可以唤醒消费者
            notifyAll();
        }
    }

    //减少产品数量的方法
    public synchronized void minusProduct(){    //wait()需要在同步代码块或同步方法中去使用
        if(productNum<=0){  //产品数量小于等于0
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName()+"消费了第"+productNum+"个产品");
            productNum--;   //让产品减一

            //只要消费者消费了一个,就可以唤醒生产者继续生产
            notifyAll();
        }

    }
}

class Producer extends Thread{ //生产者
    private Clerk clerk;

    //构造器
    public Producer(Clerk clerk){
        this.clerk=clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("生产者开始生产产品啦...");

            try {
                Thread.sleep(50);   //50ms生产一个产品
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.addProduct();
        }
    }
}

class Consumer extends Thread{ //消费者
    private Clerk clerk;

    public Consumer(Clerk clerk){   //通过构造器给clerk赋值
        this.clerk=clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("消费者开始消费产品喽...");

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.minusProduct();
        }
    }
}

4、题4

题目描述

wait()和sleep()有什么区别?调用这两个函数后,线程状态分别作何改变?(字*、*东)

类似问题:
> 线程中sleep()和wait()有什么区别?(外派*度)
> Java线程阻塞调用 wait 函数和 sleep 区别和联系(阿*)
> wait和sleep的区别,他们两个谁会释放锁(软*动力、*创)

  • 相同点:一旦执行,当前线程都会进入阻塞状态。
  • 不同点:
    • 声明的位置
      • wait():声明在Object类中。
      • sleep():声明在Thread类中,静态的。
    • 使用的场景不同
      • wait():只能使用在同步代码块或同步方法中。
      • sleep():可以在任何需要使用的场景。
    • 使用在同步代码块或同步方法中
      • wait():一旦执行,会释放同步监视器。
      • sleep():一旦执行,不会释放同步监视器。
    • 结束阻塞的方式
      • wait():到达指定时间自动结束阻塞 或 通过被notify唤醒,结束阻塞。
      • sleep():到达指定时间自动结束阻塞。

(7)单例模式(线程安全)

1、题1

题目描述

手写一个单例模式(Singleton),还要安全的通快递、君科技)

①饿汉式

②安全的懒汉式:

package yuyi04.singleton;

/**
 * ClassName: BankTest
 * Package: yuyi04.singleton
 * Description:
 * 实现线程安全的懒汉式
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/31 0031 10:39
 */
public class BankTest {
    static Bank b1=null;   //为了在main方法中调用的时候,不使用对象来调用(方便一点),这里就设置成静态的
    static Bank b2=null;

    public static void main(String[] args) {
        Thread t1=new Thread(){
            @Override
            public void run() {
                b1=Bank.getInstance(); //将方法返回的对象赋给b1
            }
        };

        Thread t2=new Thread(){
            @Override
            public void run() {
                b2=Bank.getInstance();
            }
        };

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

        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b1==b2);

    }
}

class Bank {
    //私有化构造器
    private Bank() {     //这里不提供实例变量了

    }
    private static volatile Bank instance = null;

    //实现线程安全的方式1
    /*public static synchronized Bank getInstance() { //同步监视器是Bank.class
        if (instance == null) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new Bank();
        }
        return instance;
    }*/

    //实现线程安全的方式2
    /*public static  Bank getInstance() { //同步监视器是Bank.class
        synchronized (Bank.class) {
            if (instance == null) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                instance = new Bank();
            }

        }
        return instance;
    }*/

    //实现线程安全的方式3:相较于方式1和方式2来说,效率更高;为了避免指令重排,需要将instance声明为volatile
    public static  Bank getInstance() { //同步监视器是Bank.class
        if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Bank();
                }

            }
        }
        return instance;
    }
}

③内部类

2、题2

题目描述

手写一个懒汉式的单例模式&解决其线程安全问题,并且说明为什么这样子去解决(5*)

类似问题:
> 手写一个懒汉式的单例模式(北京联合**)

同上。

你可能感兴趣的:(Java基础,java,多线程,知识点梳理总结,企业真题)