2022年Java应届生面试之多线程基础知识

						1、什么是进程?

进程:(java.exe) 正在运行的程序。(每个进程都有独立的代码和数据空间“进程上下文”,进程间的切换会有较大的开销, 一个进程包含了1----n个线程,进程是资源分配最小的单位

					2、什么是线程?

线程:就进程中负责程序执行的控制单元(执行路径){同一类线程共享代码和数据空间,每个线程都有独立的运行栈和程序计数器(PC),线程切换开销小,
线程是CPU调度的最小单位--------没啥用,可就是会被问到

					3、线程状态

线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

					4、多线程是什么

多线程是指:操作系统能同时运行多个任务(程序)。
在同一程序中有多个顺序流在执行------------被问了N次了

					5、多线程好处

解决了多部分同时运行的问题。

					6、多线程原理

实现多线程是采用一种并发执行机制
并发执行机制原理:简单地说就是把一个处理器划分为若干个短的时间片,每个时间片依次轮流地执行处理各个应用程序,
由于一个时间片很短,相对于一个应用程序来说,就好像是处理器在为自己单独服务一样,从而达到多个应用程序在同时进行的效果

多线程就是把操作系统中的这种并发执行机制原理运用在一个程序中,
把一个程序划分为若干个子任务,多个子任务并发执行,
每一个任务就是一个线程。这就是多线程程序
					7、多线程的弊端

线程太多回到效率的降低
CPU:一次只能运行一个线程,但它在多个线程不断快速切换下,使肉眼错觉是同时完成
上下文切换:当一个线程CPU时间片用完了或被迫暂停,另一个线程来占用CPU;这个过程就叫做上下文切换

					8、如何实现多线程?

在java中想要实现多线程,有两种手段,
1、一种是继承Thread类,
2、实现Runable接口,或者还有一种:实现Callable接口,并与Future、线程池结合使用,
java.lang.Thread类 /θred/
-----------------------------------------------------------------英文一定要会读
在这里继承Thread类的方法是比较常用的一种,如果只想用一条线程,无特殊要求,那么可以用Thread。

public class Demo extends Thread{
    //创建线程的方式:继承Thread类,重写run()方法,调用start方法  开启线程
    public void run(){//run方法线程体
        for (int i = 0;i<50;i++){
            System.out.println("这是run线程" +i);
        }
    }
    public static void main(String[] args) {
        //创建一个线程对象
        Demo deme = new Demo();
        //调用start方法开启线程
        deme.start();
        for (int i = 0;i<50;i++){
            System.out.println("这是main线程" +i);
        }
    }
}

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用的时候被创建。随着调用MitiSay的两个对象的stat方法。
另外两个线程也启动了,这样,整个应用就在多线程下运行。
注意:start()方法的调用后并不是立刻执行多线程代码,而是使得该线程变为可运行状态(Runnable) ,什么时候运行由操作系统决定。

从程序运行的结构可以发现,多线程程序是乱序的,因此,只有乱序执行的代码才有必要设计为多线程。

Thread

sleep()方法调用目的是不让当前线程独占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。
但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

实现java.lang.Runnable接口 /'rʌnəbl/

采用Runnable也是非常常见的一种,我们只需要重写run方法即可。

class Thread2 implements Runnable{
	private String name;
	public Thread2(String name) {
		this.name=name;
	}
	@Override
	public void run() {
		  for (int i = 0; i < 5; i++) {
	            System.out.println(name + "运行  :  " + i);
	            try {
	            	Thread.sleep((int) Math.random() * 10);
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	        }
	}	
}
public class Main {
	public static void main(String[] args) {
		new Thread(new Thread2("C")).start();
		new Thread(new Thread2("D")).start();
	}
}

Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run方法是多线程程序的约定。所有多线程的方法都在run()方法里面。
Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target)构造对象,然后调用Thead对象的start方法来运行多线程代码
实际上所有的多线程代码都是通过运行Thread的start方法来运行的,因此,不管扩展Thread类还是实现Runnable接口来实现多线程,
最终还是通过Thread的对象的API来控制线程的,属熟悉Thread类的API是进行多线程编程的基础

				9ThreadRunnable的区别

如果一个类继承Thread,则不适合资源共享,但是如果实现了Runnable接口的话,则很容易实现资源共享
实现Runable接口比继承Thread类所具有的优势:

1、适合多个相同的程序代码的线程去处理同一个资源
2、可以避免java中的单继承的限制
3、增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4、线程池只能放入实现Runnable或callable类线程,不能直接放入继承Thread类

main方法其实也是一个线程,在java所有线程都是同时启动的,至于什么先执行,完全是看谁先得到CPU资源。
java中,每次运行至少启动了2个线程,一个是main线程,一个是垃圾回收机制。
当使用java命令执行某个类时,实际上会启动一个JVM,每一个JVM实现就在操作系统中启动一个进程。

10、线程状态转换

1、新建状态(new):新创建了一个线程对象
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行的线程池中,
变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态的是线程因为某种原因放弃CPU的使用权,暂停使用。直到线程进入就绪状态,才有机会转到运行状态。
阻塞状态情况分三种:
1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中,(wait会释放持有的锁)
2、同步阻塞:运行的线程在获取对象的同步锁时,,若该同步锁被其他线程占用,JVM会把该线程放入锁池中
3、其他阻塞:运行的线程执行sleep()或 join()方法。或者发出了I/O请求时,JVM 会把该线程设置为阻塞状态
当 sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕时,线程重新转入就绪状态(sleep不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出run方法,该线程生命周期结束

11、线程调度

1、调整线程优先级:Java线程优先级,优先级高的线程会获取较多的运行机会。
java线程的优先级用整数表示,取值范围是1~10。Thread类有以下三个静态常量:

static int MAX_PRIORITY
          线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
          线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
          分配给线程的默认优先级,取值为5。

Thread类的setPriority()getPriority() 方法分别用来设置和获取线程的优先级。
每个线程都有默认的优先级,主线程的默认优先级为Thread。NORM_PRIORITY.

线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有同样的优先级,
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能够移植到各个操作系统中,应该仅仅使用Thread类
有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式

2、线程睡眠: Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠时间,以毫秒为单位。当睡眠结束后,
就转为就绪状态,sleep()平台移植性好。
3、线程等待:Object类中的wait方法,导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()唤醒方法。
这两个唤醒方法也是Object类中的状态,行为等价于调用wait(0)一样。
4、线程让步:Thread。yieid()方法,暂停当前正在折行的线程对象,把执行机会让给相同或者更高优先级的线程
5、线程加入:join方法,等待其他线程终止。在当前线程调用另一个线程的join方法,则当前线程处于阻塞状态,直到另一个进程
运行结束,当前线程在有阻塞转为就绪状态
6、线程唤醒:Object类中的notify方法,唤醒在此对象监视器等待的单个线程。如果所有线程都在此对象等待,则会选择唤醒其中一个
线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中的wait方法,在对象监视器等待,直到当前的线程
放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将已常规方式与在该对象上主动同步的其他所有线程进行
竞争,例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势,类似的方法还有一个notifyAll()
唤醒在此对象监视器上等待所有线程。
注意,Thread中suspend()和resume()两个方法在JDK1.5中已经废除。

12、常用函数说明

1、sleep(long millis):在指定的毫秒数内让当前正在执行的线程休眠(暂停)
join():指等待线程终止
join是Thread类的一个方法,启动线程后直接调用,即join() 作用是:等待该线程终止。这里需要理解的是该线程是指
主线程等待子线程的终止,也就是子线程调用了join方法后面的代码,只有等子线程结束了才能执行。

Thread t = new AThread(); t.start(); t.join();

2、为什么要用Join方法
在很多情况下,主线程生成并启动了子线程,如果子线程里面要进行大量的耗时的运算,主线程往往将于子线程之前结束。
但如果主线程处理完其他事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后,这个时候就要
使用join方法。

3、yieId()暂停当前正在执行的线程对象,并执行其他线程。
Thread。yieId()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
yieId()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yieId()的
目的是让相同优先级的线程之间能适当的轮换执行。但是实际中无法保证yieId()达到让步目的,因为让步的线程还有可能被其他
线程调度程序再次选中。
总结:yieId()从未导致线程转到等待、睡眠、阻塞、状态。在大多数情况下,yieId()将导致线程总运行状态转为可运行状态
但有可能没效果

public class YieidDemo extends Thread {
    public YieidDemo (String name){
        super(name);
    }
    public void run(){
        for (int i = 0;i<=50;i++){
            System.out.println(""+ this.getName() +"-------" + i);
            //当i = 30,该线程就会把CPU使用权让掉,或自己或其他线程执行
            if (i==30){
                this.yield();
            }
        }
    }
    public static void main(String[] args) {
        YieidDemo y1 = new YieidDemo("熊大");
        YieidDemo y2 = new YieidDemo("熊二");
        y1.start();
        y2.start();
        //熊二的线程执行到30时,会让掉,有可能熊大抢到
        //熊二的线程执行到30时,会让掉,不过熊大自己抢上
    }
}

4、sleep()和yieId()的区别
sleep()使当前线程进入停滞状态,所以执行sleep()的线程指定的线程在指定的时间内肯定不会被运行;
yieId()只能是当前线程回到重新可执行状态,所以执行yieId()的线程有可能在进入看执行状态后马上有被执行

sleep方法使当前运行中的线程睡眠一段时间,进入不可运行状态,这段时间长短由程序设定
yieid方法使当前线程让出CPU的使用权,但是让出的时间是不可设定的。实际上,yieId方法对应如下操作:
先检测当前是否有同等优先权的线程且处于可运行状态,如果有,则把CPU的使用权交出去,否则继续运行自己的线程

sleep方法允许较低优先级的线程获得运行机会,但yieId方法不会,yieId方法只会让给优先权相同的线程。所以说优先权低的线程
想要提前只能使用sleep方法,否则没机会

setPriority(): 更改线程的优先级。

MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10

用法:
Thread4 t1 = new Thread4("t1");
Thread4 t2 = new Thread4("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);

5、Obj.wait()与Obj.notify()
必须要与synchronized(Obj)一起使用,也就是wait与notify是针对已经获取Obj锁进行操作。从语法角度来说。
就是Obj.wait(),Obj.motify必须在synchronized(Obj){…}语法块内。
从功能上来说wait就是:线程获取锁对象时主动放弃对象锁,并且本线程休眠。直到其他线程调用对象的notify()唤醒该线程,才能继续获取对象锁。并继续执行。
相应的notify就是对对象锁进行唤醒操作。但notify()调用后,表示马上释放对象锁的,而是在相应的synchronized(){…}语句执行结束,
自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,

6、wait()和sleep()区别
共同点:
1、他们是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
2、他们都可以通过interrupt()方法,打断线程的暂停状态,从而是线程立刻抛出InterruptedExpection

不同点:
1、Thread类的方法:sleep(),yieId()等 Object的方法,wait()和notify()等
2、每个对象都有一个锁来控制同步访问,Synchronized关键字可以和对象的锁交互,来实现线程同步。
sleep方法没有释放锁,而wait方法释放锁。使得其他线程可以使用同步控制块或者方法
3、wait、notify和notifyAll 只能在同步控制方法或者同步控制块里面使用,而sleep方法可以在任意地方使用
sleep()睡眠时,保持对象锁,仍然占有该锁;
wait()睡眠时,释放对象锁

13、线程安全问题

1、当多个线程对同一个共享变量进行操作时,就可能产生线程安全。
解决方法:1、使用内部锁synchronizeed,可以使用同步代码块,如果是实例方法可用this作为锁对象,
如果是静态方法可以使用.class作为锁对象。
2、使用java.util.concurrent包中的锁ReentrantLock。

14、多线程不可见问题的原因和解决方法

1、不可见的原因是每个线程都有自己的工作内存,都是从主内存中拷贝共享变量到副本值。每个线程都在自己的工作内存中操作共享变量
解决方法:
加锁,获取锁后线程会清空工作内存,总主内存中拷贝共享变量最新的值成为副本值,修改后刷新回给主内存,在释放锁。
使用volatile关键字,被volatile修饰的变量会通知其他线程获取之前的值已经失效,线程会加载最新的值到自己的工作内存中。
3、volatile关键字的作用(volatile解决多线程内存不可见的问题。对于一写多读,是可以解决变量同步的问题,不是线程安全问题)

	1、保证被修饰的变量对所有线程可见,在线程修改一个变量之后,新的变量可以被其他线程立即获取。
	2、禁止指令排序,被修饰的变量不会被缓存在寄存器中或者其他的处理器不可见的地方。
	   因为读取volatile修饰的变量总会返回最新写入的值
	3、不会执行加锁操作,不会导致线程阻塞,主要的是被它修饰的变量可以被多个线程共享,
	   多个线程均可对变量执行赋值读取操作。
	4、volatile可以保证单次读写操作的原子性。但不能保证i++这操作的原子性,因为i++本质是读写两个操作

4、synchronize关键字的作用
1、用于java对象、方法、代码块提供线程安全的操作。属于排他悲观锁,也可属于重入锁
2、被synchronize修饰的方法或代码块同一时刻只能由一个线程进行访问。其他线程只能等待当前线程释放锁资源之后才能访问。
3、java中每个对象都有一个monitor监视器对象,加锁就是在竞争monitor。对代码块加锁是通过前后分别加上monitorentermonitorexit
指令实现的,对象是否加锁是通过标记位来判断的。

5、synchronize的内部都有那些区域?
synchronized内部中有六个不同的区域。每个区域的数据都代表着锁不同的状态。

1、ContentionList:锁竞争队列,所有请求的锁线程都会放到竞争队列中。、
2、EntryList:竞争候选列表,在锁竞争队列中获取资格成为候选者、来竞争锁资源的线程都会被移动到候选列表中。
3、WaitSet:等待集合,调用Wait的方法后阻塞的线程将会被放在WaitSet。
4、OnDeck:竞争候选者。在同一时刻最多只有一个线程在竞争锁资源。该线程的状态被称为OnDeck。
5、Owner:竞争到锁资源的线程状态
6、释放锁后的状态

6、简述synchronized的实现原理-------------------这里是实现原理,并非底层原理
1、收到新的锁请求时首先自旋,如果通过自旋没有获取到锁资源,被放入ContentionList(该作法对已经进入队列的线程是不公平的,
体现synchronized的不公平性。

2、为了防止ContentionList尾部的大量线程进行CAS访问影响性能,Owner线程会在释放锁时将ContenttionList的部分线程移动到EntryList指定线程中
一般最先进入的为OnDeck线程。Owner并没有将锁直接传递给OnDeck线程而是把锁竞争的权利交给它。该行为叫竞争切换,牺牲了公平,提高性能

3、获取到锁的OnDeck线程会变为Owner线程,而未获取到的仍然停留在EntryList中,
4、Owner线程在被wait阻塞后会进入WaitSet,直到某个时刻被唤醒再进入EntryList。
5、ContentionList、EntryList、WaitSet均为阻塞状态。
6、当Owner线程执行完毕后,会释放锁资源并变为!Owner状态

7、乐观锁和悲观锁

1、乐观锁:采用的是乐观思想处理数据,每一个读取数据时都认为别人不会修改数据,所以不会上锁。但需要更新时会判断期间数据有没有被修改
,通常会采用写时先读出版本号然后加上锁的方法。具体的过程为:比较当前的版本号是否和上一个的版本号一致,如果不一致,则重新进行读
写、比较操作。Java乐观锁是基于CAS操作实现的,CAS是一种原子性操作,在对数据更新之前,先比较当前数据是否和传入的数据一致,一致则返回更新失败状态。

2、悲观锁:采用的是悲观的思想处理数据,每一个读取数据都认为别人会修改数据,所有每一个都上锁。其他线程将会被阻塞。Java中悲观锁是基于AQS实现
该框架下回采用CAS去获取锁,如果获取不到则会转为悲观锁。

8、自旋锁

自旋锁认为持有锁的线程会在很短的时间内释放锁资源,那么那些等待竞选锁的线程就不需要进行内核态和用户态之间的切换进入阻塞,挂起状态,只需等待
一小段时间即可获取锁(从持有锁的线程释放锁即可立即获取锁),这样就避免了用户线程在内核态之间的切换导致锁的时间消耗。
优点:减少CPU上下文的切换,对应竞争不激烈的代码块或锁的数据非常短的代码来说,性能很高。
缺点:在持有锁时间长的线程和竞争过于激烈时,线程长时间自旋浪费CPU的资源,有复杂锁依赖的情况下不合适使用自旋锁。

9、jdk对synchronized做了哪些优化?

JDK1.6中引入适应自旋、锁消除、锁粗化、轻量级锁以及偏向锁等提高锁效率。
锁可以从偏向锁升级到轻量级锁,在升级到重量级锁,这种过程叫锁膨胀。
JDK1.6默认开启了偏向锁和轻量级锁。可以通过 XX:UseBiasedLocking禁用偏向锁。

10Java中的锁有什么作用?有哪些分类?

1、Java中的锁主要保障多并发情况下数据的一致性。线程必须首先获取锁才能进行操作,可以保证数据安全。
2、从乐观和悲观的角度可以分为乐观锁和悲观锁。
3、从获取资源的公平性可以分为公平锁(synchronized不支持)和非公平锁。
4、从资源共享角度来说可以分为共享锁和排他锁。
5、从锁的状态角度来说可分为偏向锁、轻量级锁、和重量级锁。同时JVM还设计了自旋锁以更快的使用CPU资源

11、公平锁与非公平锁

公平锁指在分配锁前检查是否有线程在排队等待获取该锁。优先将锁分配到排队时间最长的线程。
非公平锁指在分配锁时不考虑线程排队等待的情况,直接尝试获取锁。获取不到就在队列尾等待。
因为公平锁需要在多核情况下维护一个锁线程等待队列,基于该队列进行锁分配。因此效率比非公平锁要低。synchronized是非公平锁

12、锁有哪些状态?

无锁、偏向锁、轻量级锁和重量级锁。
重量级锁:是基于操作系统互斥量实现的,会导致进程在用户态和内核态之间来回的切换,开销大。
synchronized内部是基于监视器实现的,监视器基于底层操作系统实现,因此属于重量级锁,运行效率也不高。
JDK1.6为了减少获取锁和释放锁带来的性能消耗,从而提高性能。引入了轻量级锁和偏向锁。
轻量级锁相对重量级锁而言,核心设计在没有多线程竞争的前提下,减少重量级锁的使用,提高性能。适用于线程交替执行同步代码块的情况下
如果同一时刻有多个线程访问同一个锁,就会造成锁膨胀。升级为重量级锁。
偏向锁用于某个线程获取锁后,消除这个线程锁重入的开销,似乎是这个线程的到了锁的偏袒
偏向锁主要目的
是同一个线程在多次获取某个锁的情况下,尽量减少轻量级锁的执行路径。因为轻量级锁需要多次CAS操作,而偏向锁只需要切换ThreadID时执行一次CAS操作,提高效率。
出现多线程竞争锁时,JVM会自动撤销偏向锁。偏向锁时进一步提高轻量级锁的性能。随着锁竞争越来越激烈,偏向锁可能升级到轻量级锁

轻量级锁可能升级到重量级锁,锁在Java中只会单向升级不会降级。

在对象状态为偏向锁时,Mark Word存储的是偏向锁的线程ID 
当对象状态为轻量级锁时,Mark Word 存储的是指向线程栈中锁记录的指针
当对象状态为重量级锁时,Mark Word存储的是指向堆中的monitor对象的指针
13、如何进行锁优化?

1、减少锁持有的时间
只有要求线程安全时才在程序上加锁,尽量减少同步代码块对锁持有的时间。
2、减少锁粒度
将单个耗时比较长的锁操作,拆分为耗时较少的锁操作,增加锁的并行度,减少同一个锁上的竞争。在减少锁操作竞争后,偏向锁、轻量级锁
的使用率才会高。例如ConcurrentHashMap中的分段锁
3、读写分离
根据不同的应用场景将锁的功能进行分离以应对不同的变化。最常见的锁分离思想就是读写锁,这即保证了线程安全又提高了性能

.说一下 synchronized 底层实现原理

Synchronized的语义底层是通过一个monitor(监视器锁)的对象来完成的。
每个对象有一个监视器锁(monitor)

1、同步方法

方法级的同步是隐式的,无须通过字节码指令来控制,JVM可以从方法常量池的 方法表结构中 的ACC_Synchronized访问标志
得知一个方法是否声明为同步方法。

当方法调用的时,调用指令会检查方法的ACC_Synchironized访问标志是否设置
如果设置了,执行线程就要先持有monitor对象。然后才能执行方法,最后当方法执行完(无论是否正常执行完成)
时释放monitor对象

2、同步代码块										/ˈentə(r)/                 /ˈeksɪt/

同步代码块经过synchronized关键字编译之后,会在同步代码块前后分别形成monitorenter和monitorexit字节码指令
在执行monitorenter指令的时候,首先尝试获取对象的锁
如果这个锁没有被锁定或者当前线程已经拥有了那个锁,锁的计数器就加1,

在执行monitorexit指令时将锁的计数器减1,当减为0的时候就释放锁
如果获取对象锁一直失败,那当前线程就要阻塞等待,指定对象锁被另一个线程释放为止

ThreadLocal的底层原理

1、ThreadLocal是Java中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程的内部。
该线程可以在任意时刻、任意方法中获取缓存的数据
2、ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(不是ThreadLocal对象)中都在一个ThreadLocalMap。
Map的key为ThreadLocal对象,而Map的value为需要缓存的值。
在ThreadLocal源码中
ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);//this指的是ThreadLocal对象
    else
        createMap(t, value);
}
Java死锁如何避免?

造成死锁的原因:
1、一个资源每次只能被一个线程使用
2、一个线程在阻塞等待某个资源时,不释放已占有的资源
3、一个线程已经获得的资源,在未使用完之前,不能被强行剥夺
4、若干个线程形成的头尾相接的循环等待资源
避免:
1、要注意加锁顺序,保证每个线程按同样的顺序进行加锁
2、要注意加锁时限,可以针对锁设置一个超时时间
3、要注意死锁检查,这是一种预防机制,确保在第一时间发现死锁并进行解决

Lock接口   VS    synchronized

Lock接口比同步方法和同步块(synchronized)提供了更具有扩展性的锁操作
1、Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock(参数)方法),可中断的(lockInterruptibly)
可多条件队列的(newCondition()方法)
2、在早期JDK1.5之前,

使用线程池可以提高线程的使用率,不需要反复去创建

线程池的底层工作原理

线程池内部是通过队列+线程实现的,当我们利用线程池执行任务时:
1、如果此时线程池中线程数量小于corePoolSize(核心线程数):
即使线程池中的线程都处于空闲状态,也需要创建新的线程来处理被添加的任务。
2、如果此时线程池中线程数量小于corePoolSize(核心线程数):
但是缓冲队列workQueue未满,那么任务被放入缓冲队列。
3、如果此时线程池中线程数量大于corePoolSize(核心线程数):
缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize(最大线程数),则会新建线程来处理被添加的任务
4、如果此时线程池中线程数量大于corePoolSize(核心线程数):
缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么会通过handle所指定的策略来处理此任务
5、如果此时线程池中线程数量大于corePoolSize(核心线程数):
如果某个线程空闲时间超过keepAliveTime,线程将被终止。这样线程池可以动态的调整池中的线程数
线程池中阻塞队列的作用?为什么是先添加队列,而是不是先创建最大线程?
1、一般队列只能保证作为一个有限长度的缓存区,如果超出了缓冲长度,就无法保留当前的任务了。
阻塞队列通过阻塞可以保留住当前相应继续 加入的任务。
2、阻塞队列可以保证任务队列中 没有任务时 阻塞 获取任务的线程(核心线程数),使得线程进入wait状态,释放CPU资源
3、阻塞队列自带阻塞和唤醒的功能,不需要额外处理。无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活
不至于一直占用CPU资源
*在创建新的线程的时候,是要获取全局锁的,这个时候其他的线程就得阻塞。影响整体效率

SynchronizedReentranLock的区别

synchronized是一个关键字,ReentranLock是一个类
synchronized会自动的加锁,释放锁。ReentrantLock是需要程序员区手动加锁和释放
synchronized是非公平锁,ReentrantLock可以选择公平锁或非公平锁
synchronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标记来标识锁的状态
synchronized底层是有个锁升级的过程

AQS的理解, AQS如何实现可重入锁?

1、AQS是一个Java线程同步的框架,JDK中很多锁工具的核心实现框架
2、AQS中,维护了state变量 和一个线程组成的双向链表队列。其中这个线程队列,是用来给线程排队的。
而state就像一个红绿灯,控制线程排队或者放行。(不同场景、意义不同)
3、在可重入锁这个场景下,state就用来表示加锁的次数。
0标识无锁,每加一个锁。state就加1(在重入的场景,会加很多次),释放锁就减1(加了多少次,就会减多少次)
二分法

如何在两个线程间共享数据?
1、多线程执行同一个Runnable实现类中的代码,此时共享的数据放在Runnable实现类中;
2、如果多个线程执行不同的Runnable实现类中的代码,此时共享数据和操作共享数据的方法封装到一个对象中,
在不同的Runnable实现类中调用操作共享数据的方法

············································································································未完待续

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值