1.多线程基础

本文详细介绍了Java中的多线程基础知识,包括进程与线程的关系、并发与并行的区别、多线程的使用原因及挑战。还讨论了线程的生命周期、wait()和sleep()方法的区别、线程的创建方法、线程池的实现,以及悲观锁和乐观锁的概念及其应用场景。此外,文章深入解析了ReentrantLock的实现原理。
摘要由CSDN通过智能技术生成

1.什么是进程和线程?
2.说说进程和线程的关系,区别和优缺点?
3.说说并发和并行的区别?
4.说说为什么要使用多线程?(两方面去说)
5.说说多线程编程(并发编程)能干什么?可能会带来什么问题?
6.说说线程的生命周期和状态?
7.说说wait()和sleep()方法区别的共同点?
8.为什么执行start()中会执行Runnable接口的run()?为什么不能直接执行run()呢?
9.如何创建java线程?两种方法
10.java线程池有哪些实现?FixedThreadPool底层是用的什么任务队列?ArrayBlockingQueue和LinkedBlockingQueue有什么区别?
11.什么是悲观锁?什么是乐观锁?它们各自的应用场景是什么?(数据拿到的时候是否被别的线程修改)
12.介绍一下reentrantlock?并讲一下它是怎么实现的?


2.3.1 什么是进程和线程?

Q:什么是进程?
A:进程是系统执行程序的一次过程系统执行一个程序进程从创建,运行,到消亡的一个过程

Q:什么是线程?
A:线程是比进程更小的一个执行单位。一个进程可以产生多个线程,同一个进程产生的线程共享该线程的资源(堆和方法区),而且线程自己有自己独有的资源(程序计数器,虚拟机栈,本地native方法栈)。这些线程之间的切换开销要比进程间的切换小的多得多

是存放java对象的地方,也是垃圾回收的主要地区。
方法区存着已被虚拟机加载的类信息,常量和静态变量之类的信息。

程序计数器
作用1:字节码解释器(翻译.class文件的工具)可以告诉线程下一个执行的指令在哪,然后修改程序计数器的值来实现指令的跳转。
作用2:可以保存当前线程的位置,当线程发生切换的时候切换回来可以继续往下执行。
程序计数器私有主要是为了线程切换回来后能够回到原来的位置。如果共有的话,被修改就会导致错误。

虚拟机栈(方法栈)和本地native方法栈存储的都是方法里局部变量,操作数,出口位置等信息。只是对应的方法性质不同。
native方法是由c/c++方法实现能和操作系统直接交互的方法。

2.3.2 说说进程和线程的关系,区别和优缺点?

1.线程是更小的执行单位。
2.进程与进程之间基本上是独立的,但是同一个进程中的线程与线程之间会相互联系
3.线程切换速度快,开销小;但是线程的资源和进程比起来没有那么好管理和保护

2.3.3 说说并发和并行的区别?

并行是同一时刻,有多个任务一起执行。
并发是同一时间段,有多个任务要执行。

2.3.4 说说为什么要使用多线程?

硬件上来讲:现在的计算机都是多核心的,如果只用一个线程进行计算任务,那么核心其余的线程资源就被浪费了。所以用多线程可以把其他的核心利用起来。

业务需求上讲:现在的系统的并发量要求都很高,而多线程是高并发系统的基础,所以要使用多线程。

2.3.5 说说多线程编程(并发编程)能干什么?可能会带来什么问题?

并发编程(多线程编程)可以充分利用系统资源,提高程序运行效率(前提是使用正确的情况下)。

可能会带来 内存泄漏,上下文切换,死锁 等问题。

内存泄漏是申请的内存资源已经不再使用,但是无法被释放,导致可利用的内存变少。(具体在jvm总结)

上下文切换:线程在执行完时间片后把自己现在状态保存一下,然后当它再拿到时间片后加载之前工作的状态然后继续往下,这个过程就是上下文切换。

线程死锁:多个线程同时别阻塞,而它们都在等待其它线程释放自己想要的资源。这样僵持着,就产生了线程死锁。

比如线程1获得了资源2,想请求资源1.但是线程2获得了资源1,想请求资源2.所以两个线程就一直等待资源,就产生了线程死锁。

产生线程死锁的四个必要条件:
1.互斥条件。资源在同一个时刻只能被一个线程使用。
2.请求和保持条件。线程在请求自己想要的资源,但该资源被其它线程使用,该线程阻塞。并保持着自己持有的资源不放。
3.不剥夺条件。当线程还没使用完自己持有的资源时,不能被其他线程所抢占。只能该线程把资源自己释放。
4.循环等待条件。各线程之间形成了一种头尾相接循环等待资源的关系

如何避免死锁?
破坏四个必要条件之一即可。
1.互斥条件无法破坏。
2.破坏请求和保持条件:一次性申请所有的资源,刚开始线程申请所有资源时就不会被阻塞。
3.破坏不剥夺条件:占用部分资源的线程,如果进一步申请其它资源但申请不到,则可以把自己所有的资源都释放。
4.破坏循环等待条件:按某种顺序有序地申请资源

2.3.6 说说线程的生命周期和状态?

java线程的6种状态
NEW,RUNNABLE,WAITING,TIME_WAITING,BLOCKED,TERMINATED(terminated)

java线程的生命周期是在6个不同的状态之间来回切换的。

.start() .wait() .sleep(time)或.wait(time) .run()执行完毕

当线程创建后处于NEW状态,线程调用start()方法(Thead.start())后处于RUNNABLE状态

(在操作系统看来只有RUNNABLE这一个状态,但是从jvm细分的话它可以分为READY和RUNNING这两种状态,线程执行start()方法后先进入READY状态,当该线程被操作系统调度,拿到cpu的时间片时,进入RUNNING状态。当线程时间片执行完后,则回到READY状态

当线程执行同步方法时,没获得锁便进入BLOCKED状态。获得锁后,则回到RUNNABLE状态。线程执行wait()方法后进入WAITING状态,该线程需要被其他线程通知才能回到RUNNABLE状态。线程执行sleep(time)或wait(time)方法后进入TIME_WAITING状态,当这个time结束后自动回到RUNNABLE状态

当RUNNABLE接口的run()方法执行完后,线程进入TERMINATED状态(terminated)。

2.3.7 说说wait()和sleep()方法区别的共同点?

区别:wait()释放了锁(对应对象上的锁),sleep()没有释放锁。
相同:都可以用于暂停线程

wait()执行后进入WAITING状态,线程不会自己被唤醒,需要其他线程**调用同一个对象上的notify()和notifyAll()**才能把该线程唤醒。
而wait(time)和sleep(time)进入的是TIME_WAITING状态,在时间结束后,线程会自动的被唤醒,进入RUNNABLE状态。

2.3.8 为什么执行start()中会执行Runnable接口的run()?为什么不能直接执行run()呢?

执行start()后,意味着线程进入了RUNNABLE的状态,start()中再执行run()表示是用多线程的方式执行
而如果直接执行run()的话,它就被当做main主线程下普通的方法,它不会以多线程的方式执行

9.如何创建java线程?两种方法

1.实现一个线程类,继承Thread父类,并重写run()方法。
使用:创建该线程类的对象,然后调用该对象的start()方法。

2.实现一个Runnable类,也就是实现Runnable接口的类,并实现run()方法。
使用:用Thread类创建线程,并把Runnable对象作为参数传入。调用Thread对象的start()方法。
这种方法通常用匿名内部类实现,写在一起

10.java线程池有哪些实现?FixedThreadPool底层是用的什么任务队列?ArrayBlockingQueue和LinkedBlockingQueue有什么区别?

java线程池的实现类有ThreadPoolExecutorScheduledThreadPoolExecutor

可以用Executors来创建线程池(底层都是用ThreadPoolExecutor的构造方法)。ScheduledThreadPoolExecutor可以定时进行任务调度

FixedThreadPool底层使用的workQueue是LinkedBlockingQueue,它是由链表形式组成的阻塞队列(和ArrayBlockingQueue比较)。

1.LinkedBlockingQueue如果创建时不指定队列大小,则默认是Integer.MAX_VALUE,说明默认为无界序列。而当入队速度大于出队速度时就容易溢出。所以使用时最好指定队列大小。

2.它把入队的数据封装到一个链表Node节点中进行管理。它通过两个reentrantlock分别实现了入队putLock和出队tackLock操作,两个操作可以同时进行。因为分别用的是两个不同的锁。

3.它对两个reentrantlock锁各自有一个Condition,可以休眠wait()和唤醒notify()线程。

和ArrayBlockingQueue的区别:
1.ArrayBlockingQueue创建时必须加上初始大小。它可以不用,但是最好指定初始大小。
2.ArrayBlockingQueue入队和出队的元素不需要额外创建对象,采用数组方式存储。而它需要额外创建一个Node对象。当短时间要创建非常多的Node时,对GC会产生很大影响
3.ArrayBlockingQueue入队和出队都借助同一个reentrantlock实现。而它不是,它的入队putLock和出队takeLock是分别由两个reentrantlock来实现的,所以它们可以一起操作,提高并发效率

11.什么是悲观锁?什么是乐观锁?它们各自的应用场景是什么?(数据拿到后是否被别的线程修改)

悲观锁:就是把情况往坏了想,就认为数据在拿到时都已经被别的线程修改了,所以拿到资源时都要加锁,以独占的方式来使用资源
如synchronized和reentrantlock都是悲观锁。

乐观锁:就是把情况往好了想,就认为数据都还没有被别的线程修改,但是在更新前都会判断这个数据是否被修改,没有被修改的话它才进行操作
如用版本号实现或CAS操作实现的。

使用场景:悲观锁适合用于多写的场景,因为多写时数据很容易被修改乐观锁适合用于多读场景,因为多读的时候数据基本不被修改,就不用再去加锁了。

12.介绍一下reentrantlock?并讲一下它是怎么实现的?

reentrantlock是基于AQS同步器实现的一个以独占方式使用资源可重入锁

它的实现原理AQS同步器的实现是一样的

是用一个同步状态state和两个双向链表分别构成了同步队列和等待队列实现的。

state为0说明还没有获得该资源,为1说明表示该线程已经获得了该资源,如果下次还是这个线程获得了该资源,则state为2。直到为0时该线程才把该资源释放。

有两个队列,一个是同步队列,当有线程正在使用资源时,其它线程过来则进入同步队列。一个是等待队列,这个是当Condition不符合时,则休眠阻塞在该等待队列当中。当该等待队列中线程被唤醒后则加入同步队列

之所以reentrantlock可以指定是公平锁还是非公平锁:
原因是当为公平锁时,把想要获取资源的线程加入同步队列中。同步队列的头部元素通过CAS操作修改state来获得资源。
当为非公平锁时,把想要获取资源的线程直接让它与同步队列的头部元素用CAS操作一起竞争修改state来获取资源

https://www.nowcoder.com/discuss/610951?type=post&order=time&pos=&page=1&channel=-1&source_id=search_post_nctrack

搜索 reentrantlock

看图理解:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值