前言
今天隔离第二天还是依然按照正常上班八点半起床,九点开始坐在电脑面前看视频,要问看的什么视频,当然是学习视频了,学习SpringCloud,学习如何构建一个SpringCloud项目,学习资源来自大B站,项目实践放在了我的GitHub https://github.com/linglongchen/Leyou,学习资源链接如下:bilibili.com/video/av54216146?p=102
好了言归正传,今天早上接到了一个HR的电话简单面试,又是惨不忍睹,没办法自己能力太差,问了几个问题如下:
1、对于线程池有没有了解?
2、说下进程和线程的区别?
3、线程安全如何处理?
4、多线程如何解决生产者和消费者问题?
5、4的二进制是多少?
第三个问题直接懵掉了,自己没遇到过啊,然后就gg,第四个问题更蠢,居然忘记了十进制转二进制,而且等挂完电话我突然想到第三个问题可以说通过消息队列来处理啊,跟我了解的分布式事务的解决思想是一样的啊,此处想捶死自己!!!
针对以上问题那么今天的任务就是搞懂线程,搞懂多线程并发问题!!!
1、首先了解进程、线程的概念和区别
图中矩形框代表的是进程,矩形中的线条代表的是线程,此进程中包含了三个线程,因此一个进程可以启动多个线程
,这就是线程与进程最基本的关系。
放在内存中去解释的话,每个进程有两队独立的内存空间,进程中的所有线程共享这个进程的内存空间
。
放在操作系统中去解释的话,线程是操作系统进行调度的最小单位
,进程是系统进行资源调度与分配的基本单位
以上就是对线程以及进程的区别和解释,这是我能想到的最直观的表达方式了。
2、创建线程
知道了线程与进程的概念和区别,现在就要创建线程了,在Java中创建线程有下列方法:
-
继承Thread类,重写run方法
具体代码实例如下:
/**
* @author Administrator
*/
public class MyThreadTest extends Thread {
int count;
@Override
public void run() {
while (true) {
System.out.println("创建线程:"+count);
if (count == 10) {
return;
}
count++;
}
}
public static void main(String[] args) {
MyThreadTest myThreadTest = new MyThreadTest();
myThreadTest.start();
}
}
在该方法中如何我的MyThread类已经是其它类的子类,那么久无法再继承Thread类,Java只能单继承的特性,因此这时候可以通过多实现来解决这个问题。
- 实现Runnable接口
/**
* @author Administrator
*/
public class MyRunnableTest implements Runnable {
int count;
@Override
public void run() {
while (true){
System.out.println("Runnable方法线程创建:"+count);
if (count==10){
return;
}
count++;
}
}
public static void main(String[] args) {
new Thread(new MyRunnableTest()).start();
}
}
通过实现Runnable接口的方法能够使我们的一个类种包含所有的代码,有利于封装。
以上就是创建线程的两种方式,你们平时用哪种最多呢???
3、多线程问题
我们通过以上方法中可以看到我们创建了多个线程,而多线程就是为了解决问题而实现的,举个简单的例子一个项目一个人干需要十天干完,那么是个人一起干就可以一天干完,这就是多线程的好处,但是多线程的问题也是存在的,会造成内存的消耗,每次创建线程以及销毁线程都是需要占用内存的。
但是其中最重要的问题就是线程安全的问题,多个线程之间对于数据的访问会出现问题,比如A线程访问修改了数据B,C线程也访问了数据B,此时A线程拿到的B数据就不是自己期望的数据,造成了数据的紊乱。
针对以上问题呢就牵扯到了线程安全问题了,对于线程安全问题先说下自己锁了解的关于线程安全的知识。
1、synchronized关键字
2、volatile关键字
3、lock锁
4、ReentrantLock锁
5、ConcurrentHashMap
-
synchronized关键字
对于synchronized关键字的理解,同一时刻只能有一个线程执行该代码,保证了多线程并发安全。 synchronized关键字有一下几个特点: 1、解决了多线程之间访问资源的同步性,保证它修饰的代码块或者方法在同一时刻只能有一个线程执行。 2、synchronized保证了方法或代码块的原子性、可见性、一致性、可重入性(可以获取自己的内部锁)。 3、synchronized会造成线程的阻塞。 4、synchronized是Java的关键字,依赖于JVM实现的。 5、synchronized对于锁的状态无法判断,因为在JVM层面实现的,对于锁的判断是通过解析成字节码进行判断是否加锁。 6、若线程执行发生异常,则JVM会让线程释放锁。 7、这是一种悲观锁的体现。
对于底层的实现原理,后面再做总结。
-
volatile关键字
volatile关键字的作用是: 1、保证数据的可见性 2、禁止指令重排序 3、volatile的本质是告诉JVM当前变量在寄存器中的值是不确定的,需要从主存中取。 4、volatile不会造成线程的阻塞。 5、volatile标记的变量不会被编译器优化。
对于volatile的理解可以通过经典的双重校验锁实现对象单例,代码如下:
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
这些都是以前记录在笔记上的知识,没想到今天又重新温习了,真棒!对于volatile关键字也做过实现原理的总结,后面也一起再做吧!
-
lock锁
对于lock锁平时接触不多,主要是为了对比和synchronized的区别是做的一些总结,精彩如下: 1、首先lock是一个类,它不是Java层面的关键字,是一个可以new的类! 2、使用lock锁时必须释放锁,不然容易造成死锁。 3、对于锁的状态是可以判断的。 4、lock获取锁的方式有多种,其中可以尝试获得锁,线程可以不用一直等待 5、底层是靠volatile和CAS操作实现的。 6、这是一种乐观锁的体现。
自己平时接触这部分知识不多就不过多牵扯了,以免造成误导。
-
ReentrantLock锁
对于这个的知识自己接触也不多,积累的知识也是为了跟synchronized做比较。总结如下: 1、ReentrantLock是Java的API,需要通过lock上锁,unlock释放锁。 2、不释放锁会造成死锁。 3、ReentrantLock相比于synchronized增加了一些高级功能:等待可中断、公平锁、锁绑定多个条件 4、实现原理是通过循环调用CAS来实现的加锁。
后面可以对synchinized 、lock、ReentrantLock列一个表格的总结。
-
ConcurrentHashMap
教练这个我熟悉!!对于ConcurrentHashMap在一次涉及到线程池的项目中踩过坑,了解了该功能以及实现原理。总结如下: 1、ConcurrentHashMap线程安全,支持并发操作 2、采用synchronized+CAS+HashEntry+红黑树来保证线程安全(jdk1.8之后) 3、ConcurrentHashMap每次加锁的时候锁住的是一个segment,这样保证一个segment是安全的,也就实现了全局的安全。 4、ConcurrentHashMap的结构为数组+链表+红黑树(jdk1.8之后) 5、segment数组是不可扩容的,但是segment的内部数组可以扩容。
以上就是对与线程安全知识的总结,不全面,主要是自己平时的积累,希望对你们有帮助。
4、线程池问题
对于线程池问题直接列几个问题,或许就明白了,但是想要理解还是要自己去实践的。
1、为什么要用线程池?
线程的创建和销毁花销比较大,频繁的创建和销毁线程会大量消耗系统资源,导致系统资源不足,因此为了避免不必要
的消耗系统资源,我们可以维持一定量的线程池,在使用期间保持线程的存活,减少过多的销毁,当业务处理结束统一
关闭线程。
2、几种常见的线程池?
1、ThreadPoolExcutor:指定线程数的线程池
2、fixedThreadPool:启动固定数的线程池
3、CacheThreadPool:按需分配的线程池
4、ScheduledThreadPoolExcutor:定时定期执行任务的线程池
3、线程池有哪几种工作队列?
1、ArrayBlockingQueue:基于数组的有界阻塞队列,按照FIFO
2、LinkedBlockingQueue:基于链表的阻塞队列,也是按照FIFO
3、SynchronousQueue:不存储元素的阻塞队列
4、PriorityBlockingQueue:具有优先级的无限阻塞队列
4、多线程如何解决生产者和消费者模式?
在多线程中生产者生产数据给消费者消费,多个生产者同时进行,多个消费者同时进行过消费,在这样的思想下我们可以创立一个缓冲区来存放生产者生产的数据,缓冲区满时则不再生产,消费者从缓冲区中取数据,缓冲区数据为0时则不再消费。
下面高能解决问题的方式:
1、通过Java中Object的wait和notify方法
2、Lock和Condition机制
3、通过阻塞队列,如ArrayBlockingQueue、LinkedBlockingQueue。
4、通过管道的方式,如NIO的存取方式。