什么是多线程

Posted 好多个码农

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是多线程相关的知识,希望对你有一定的参考价值。

多线程的了解

大家都了解进程吧,例如你打开了多个软件,每个软件都有一个相应的进程,而且是相互独立的

所以什么是线程?

进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。

那么什么是多线程?

首先需要了解什么是串行和并行以及它们的区别是什么?

串行,其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子:当我们下载多个文件时,在串行中它是按照一定的顺序去进行下载的,也就是说,必须等下载完A之后才能开始下载B,它们在时间上是不可能发生重叠的。

并行:下载多个文件,开启多条线程,多个文件同时进行下载,这里是严格意义上的,在同一时刻发生的,并行在时间上是重叠的。

多线程可以提高程序的效率。

它们之间的区别:

并发是指一个处理器同时处理多个任务。
并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。

串行是指程序从上往下的同步执行,即如果第一行代码执行没有结束,第二行代码就只能等待第一行执行结束后才能结束。
并发是逻辑上的同时发生,而并行是物理上的同时发生。

多线程的创建方式是什么?

    继承 Thread类
    实现 Runable接口
    实现 Callable接口

    三种方式比较:

        Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
        Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
        Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
        当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程
        Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现


线程安全:线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题

解决方案

方式一:同步代码块

使用同步监视器(锁)
Synchronized(同步监视器){
//需要被同步的代码
}
说明:

    操作共享数据的代码(所有线程共享的数据的操作的代码)(视作卫生间区域(所有人共享的厕所)),即为需要共享的代码(同步代码块,在同步代码块中,相当于是一个单线程,效率低)
    共享数据:多个线程共同操作的数据,比如公共厕所就类比共享数据
    同步监视器(俗称:锁):任何一个的对象都可以充当锁。(但是为了可读性一般设置英文成lock)当锁住以后只能有一个线程能进去(要求:多个线程必须要共用同一把锁,比如火车上的厕所,同一个标志表示有人)

Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(window。class)来充当唯一锁

方式二:同步方法
使用同步方法,对方法进行synchronized关键字修饰
将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

总结:1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
2.非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身。继承自Thread.class

方式三:lock()锁方法

总结:Synchronized与lock的异同?

相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器
lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())

优先使用顺序:
LOCK>同步代码块>同步方法
判断线程是否有安全问题,以及如何解决:

1.先判断是否多线程
2.再判断是否有共享数据
3.是否并发的对共享数据进行操作
4.选择上述三种方法解决线程安全问题
线程的死锁问题:

线程死锁的理解:僵持,谁都不放手,一双筷子,我一只你一只,都等对方放手(死锁,两者都进入阻塞,谁都吃不了饭,进行不了下面吃饭的操作)
出现死锁以后,不会出现提示,只是所有线程都处于阻塞状态,无法继续


死锁的解决办法:

    1.减少同步共享变量
    2.采用专门的算法,多个线程之间规定先后执行的顺序,规避死锁问题
    3.减少锁的嵌套。

java线程面试题

1. 并行和并发有什么区别?

    并行(Parallel):指两个或者多个事件在同一时刻发生,即同时做不同事的能力。例如垃圾回收时,多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
    并发(Concurrent):指两个或多个事件在同一时间间隔内发生,即交替做不同事的能力,多线程是并发的一种形式。例如垃圾回收时,用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个CPU上。

2. 线程和进程的基本概念、线程的基本状态以及状态之间的关系?

    一个线程是进程的一个顺序执行流程。一个进程中的全部线程共享同一个堆空间。线程本身有一个供程序执行时的栈,一个进程中可以包含多个线程。
    线程的基本状态:新建、就绪、运行状态、阻塞状态、死亡状态
    新建状态:利用NEW运算创建了线程对象,此时线程状态为新建状态,调用了新建状态线程的start()方法,将线程提交给操作系统,准备执行,线程将进入到就绪状态。
    就绪状态:由操作系统调度的一个线程,没有被系统分配到处理器上执行,一旦处理器有空闲,操作系统会将它放入处理器中执行,此时线程从就绪状态切换到运行时状态。
    运行状态:线程正在运行的过程中,碰到调用Sleep()方法,或者等待IO完成,或等待其他同步方法完成时,线程将会从运行状态,进入到阻塞状态。
    死亡状态:线程一旦脱离阻塞状态时,将重新回到就绪状态,重新向下执行,最终进入到死亡状态。一旦线程对象是死亡状态,就只能被GC回收,不能再被调用。

3. 守护线程是什么?

    守护线程又称为后台线程,它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件
    正常创建的线程都是普通线程,或称为前台线程,守护线程与普通线程在使用上没有什么区别,但是他们有一个最主要的区别是在于进程的结束中。当一个进程中所有普通线程都结束时,那么进程就会结束。如果进程结束时还有守护线程在运行,那么这些守护线程就会被强制结束
    在 Java 中垃圾回收线程就是特殊的守护线程

4. 创建线程有哪几种方式?

    继承Thread类(真正意义上的线程类),是Runnable接口的实现。
    实现Runnable接口,并重写里面的run方法。
    使用Executor框架创建线程池。Executor框架是juc里提供的线程池的实现。

5. sleep() 和 wait() 有什么区别?

    类的不同:sleep() 来自 Thread,wait() 来自 Object。
    释放锁:sleep() 不释放锁;wait() 释放锁。
    用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒。

6. 线程的 run() 和 start() 有什么区别?

    start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。
    run() 可以重复调用,而 start() 只能调用一次。
    第二次调用start() 必然会抛出运行时异常

7. 创建线程池有哪几种方式?

    newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;
    newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
    newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
    newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;
    newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()类似,创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
    newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;
    ThreadPoolExecutor():是最原始的线程池创建,上面1-3创建方式都是对ThreadPoolExecutor的封装。

8. 在 Java 程序中怎么保证多线程的运行安全?

    使用安全类,比如 Java. util. concurrent 下的类。
    使用自动锁 synchronized。
    使用手动锁 Lock。

9. 什么是死锁?怎么防止死锁?

    当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
    防止死锁方法:
        尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
        尽量使用 Java. util. concurrent 并发类代替自己手写锁。
        尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
        尽量减少同步的代码块。

10. synchronized 和 volatile 的区别是什么?

    volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。
    volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
    volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。

11. synchronized 和 Lock 有什么区别?

    synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
    synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
    通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

12. synchronized 和 ReentrantLock 区别是什么?

    ReentrantLock 使用起来比较灵活,但是必须有释放锁的配合动作;
    ReentrantLock 必须手动获取与释放锁,而 synchronized 不需要手动释放和开启锁;
    ReentrantLock 只适用于代码块锁,而 synchronized 可用于修饰方法、代码块等。

13. 为什么使用线程池?

由于创建和销毁线程都需要很大的开销,运用线程池就可以大大的缓解这些内存开销很大的问题;可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存
 

 


 

以上是关于什么是多线程的主要内容,如果未能解决你的问题,请参考以下文章

什么是多线程

什么是多线程 多进程

什么是多线程中的上下文切换?

Redis是多线程还是单线程?看完“手撕”面试官

java面试 啥是多线程

什么是多线程