java入门多线程一文通
创始人
2025-05-28 16:41:29

一、面试经典

1.为什么使用多线程及其重要

为了使用户体验更好,服务的相应速度更快。现如今硬件不断发展,软件要求也逐渐提高,都是为了一个字:快。

2.进程、线程、管程(monitor 监视器)

3.多线程并行和并发的区别

①并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行(需要多核CPU)
②并发是指两个任务都请求运行,而处理器只能接收一个任务,就是把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行(秒杀系统)

4.wait和sleep的区别?

wait是Object类中的方法,表示释放该线程执行权,需用Object中的notify|notifyAll唤醒。而sleep只是“阻塞”当前线程指定时间,不释放执行权,是Thread类中的方法。

5.synchronized和lock的区别?

(1). 原始构成a. synchronized是关键字属于JVM层面monitor对象,每个java对象都自带了一个monitor,需要拿到monitor对象才能做事情monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖monitor对象,只能在同步块或方法中才能调用wait/notify等方法),进入monitorexit:退出b. lock是api层面的锁,主要使用ReentrantLock实现
(2). 使用方法a. synchronized不需要用户手动释放锁,当synchronized代码完成后系统会自动让线程释放
对锁的占用b. ReentrantLock则需要用户手动释放锁若没有主动释放锁,就有可能会导致死锁的现象
(3). 等待是否可中断?a. synchronized不可中断,除非抛出异常或者正常运行完成b. ReentrantLock可中断(设置超时时间tryLock(long timeout,TimeUnit unit),调用interrupt方法中断)
(4). 加锁是否公平a. synchronized非公平锁b. ReentrantLock两者都可以,默认是非公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁
(5). 锁绑定多个Conditiona.synchronized没有b.ReentrantLock用来实现分组唤醒需要唤醒线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个\要么多个

6.多线程的实现方式

①基础Tread类

public class ThreadDemo {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();for (int i = 0; i < 100; i++) {System.out.println("我是线程"+Thread.currentThread().getName()+"输出功能:"+i);}}
}class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("我是线程"+Thread.currentThread().getName()+"输出功能:"+i);}}
}

②实现Runnable接口

public class RunnableDemo {public static void main(String[] args) {MyThreadRunnable myThreadRunnable = new MyThreadRunnable();Thread t = new Thread(myThreadRunnable);t.start();for (int i = 0; i < 100; i++) {System.out.println("我是线程"+Thread.currentThread().getName()+"输出功能:"+i);}}}class MyThreadRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("我是线程"+Thread.currentThread().getName()+"输出功能:"+i);}}
}

两种实现多线程方式的区别

	(1).查看源码a.继承Thread:由于子类重写了Thread类的run(),当调用start()时,直接找子类的run()方法b.实现Runnable:构造函数中传入了Runnable的引用,成员变量记住了它,start()调用run()方法时内部判断成员变量Runnable的引用是否为空,不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法(2).继承Threada.好处是:可以直接使用Thread类中的方法,代码简单b.弊端是:如果已经有了父类,就不能用这种方法(3).实现Runnable接口a.好处是:即使自己定义的线程类有了父类也没有关系,因为有了父类可以实现接口,而且接口
可以多现实的b.弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,
代码复杂

③Callable接口

(1)Callable接口中的call方法和Runnable接口中的run方法的区别

  • 是否有返回值(Runnable接口没有返回值 Callable接口有返回值)
  • 是否抛异常(Runnable接口不会抛出异常 Callable接口会抛出异常)

(2)Future接口概述

  • FutureTask是Future接口的唯一的实现类
  • FutureTask同时实现了Runnable、Future接口。它既可以作为Runnable被线程执行,又可以作为Futrue得到Callable的返回值
public class ThreadCallDemo {public static void main(String[] args) {CallThread callThread = new CallThread();FutureTask futureTask = new FutureTask(callThread);new Thread(futureTask).start();try {Object sum = futureTask.get();System.out.println(sum);} catch (Exception e) {e.printStackTrace();}}
}class CallThread implements Callable{@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 0; i < 100; i++) {System.out.println(i);sum += i;}return sum;}
}

(3) 注意

  • get( )方法建议放在最后一行,防止线程阻塞(一旦调用了get( )方法,不管是否计算完成都会阻塞)
  • 一个FutureTask,多个线程调用call( )方法只会调用一次
  • 如果需要调用call方法多次,则需要多个FutureTask

④线程池

此文不做记录,见后续学习笔记。

二、线程基本设置

1.设置线程名

  • void setName(String name):将此线程的名称更改为等于参数 name
  • String getName( ):返回此线程的名称
  • 通过构造函数设置线程名称:
    -Thread(String name):通过带参构造进行赋值/Thread(Runnable target , String name)
  • static Thread currentThread​( )返回对当前正在执行的线程对象的引用
  • 注意:要是类没有继承Thread,不能直接使用getName( ) ;要是没有继承Thread,要通过Thread.currentThread得到当前线程,然后调用getName( )方法
      //FileWriterMyThread my1 = new MyThread();MyThread my2 = new MyThread();//void setName(String name):将此线程的名称更改为等于参数 namemy1.setName("线程1");my2.setName("线程2");my1.start();my2.start();

2.线程优先级(setPriority)

线程有两种调度模型:

  • 分时调度模式:所有线程轮流使用CPU的使用权,平均分配每个线程占有CPU的时间片
  • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些 [ Java使用的是抢占式调度模型 ]

Thread类中设置和获取线程优先级的方法

  • public final void setPriority(int newPriority):更改此线程的优先级
  • public final int getPriority():返回此线程的优先级
  • a. 线程默认优先级是5;线程优先级范围是:1-10; b. 线程优先级高仅仅表示线程获取的CPU时间的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
    例子:下面不一定是线程2先执行,只是先执行得
      ThreadPriority tp1 = new ThreadPriority();ThreadPriority tp2 = new ThreadPriority();ThreadPriority tp3 = new ThreadPriority();tp1.setName("线程1");tp2.setName("线程2");tp3.setName("线程3");//设置正确的优先级tp1.setPriority(5);tp2.setPriority(10);tp3.setPriority(1);tp1.start();tp2.start();tp3.start();

3.线程控制(sleep、join、setDeamon)

①static void sleep(long millis):使当前正在执行的线程停留(暂停执行)指定的毫秒数 (休眠线程)

② void join():当前线程暂停,等待指定的线程执行结束后,当前线程再继续 (相当于插队加入)

③void join(int millis):可以等待指定的毫秒之后继续 (相当于插队,有固定的时间)

④void yield():让出cpu的执行权(礼让线程)

⑤void setDaemon​(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出(守护线程)(相当于象棋中的帅,要是帅没了,别的棋子都会没用了)

说明:

  • 守护线程是区别于用户线程哈,用户线程即我们手动创建的线程,而守护线程是程序运行的时候在后台提供一种通用服务的线程。垃圾回收线程就是典型的守护线程
  • 守护线程拥有自动结束自己生命周期的特性,非守护线程却没有。如果垃圾回收线程是非守护线程,当JVM 要退出时,由于垃圾回收线程还在运行着,导致程序无法退出,这就很尴尬。这就是为什么垃圾回收线程需要是守护线程
  • t1.setDaemon(true)一定要在start( )方法之前使用

在这里插入图片描述

相关内容

热门资讯

抢先看!2026南京夫子庙白鹭... 现代快报讯(记者 卢河燕/文 赵杰/摄)乙巳蛇年渐近尾声,丙午马年的脚步悄然临近。作为南京年味的“顶...
原创 桂... 冬至到了,桂林的街头巷尾早已飘起诱人的香气——这是一年中最温暖的团圆时刻,家家户户的厨房里蒸腾着糯香...
16岁深圳烤鸡少年承认用“肉宝... 近日,16岁深圳烤鸡少年“少年烤鸡(小陈)”被曝使用“肉宝王”调味,引发食品安全担忧。12月22日,...
简单“手相学”,一分钟学会! 1.生命线 生命线——即生命纹。从大拇指与食指中间的掌边开始,往掌底走的纹路。生命纹的长短并不代表寿...