#进程线程 概念
一、程序:程序是数据与指令的集合。程序是静态的
二、进程:进程就是正在运行的程序。进程是动态的
三、线程:线程是系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的最小执行单位
线程与进程的关系:
一个系统OS中可以有多个进程,一个进程中可以包含一个线程(单线程程序),
也可以包含多个线程(多线程程序)
怎么理解资源独立分配。--面试
同一个进程内的多个线程可以共享这个进程的资源和内存的同时,又有自己独立的内存空间.
#并行 串行 并发
CPU:电脑的核心处理器,类似于“大脑”
串行:是指同一时刻一个CPU只能处理一件事,类似于单车道
并行:相对来说资源比较充足,多个CPU可以同时处理不同的多件事,类似于多车道
并发:相对来说资源比较紧缺,多个进程同时抢占公共资源,比如多个进程抢占一个CPU
#线程的 同步与异步
异步:是多个线程抢占资源,不排队,效率高,但是数据不安全
同步:每次只有一个线程独占资源,排队,效率低但是安全,synchronized也被称作同步关键字
#线程有几种状态?它们是怎么转换的?--面试
1、新建状态:进行资源的分配
2、就绪状态:将创建好的线程对象加入到就绪队列中,等待系统选中,这个选择我们是控制不了的
3、执行状态:就绪队列中的线程被系统选中了,正在执行
4、阻塞状态:线程在执行中遇到了问题:
锁阻塞、休眠阻塞、等待阻塞…问题解决后再加入到就绪队列中
5、终止状态:线程成功执行完毕,释放资源
##如何启动一个新线程、调用start和run方法的区别?(必会)///如何创建、启动 Java 线程
线程对象调用run方法不开启线程。run方法只是thread的一个普通方法,仅仅是对象调用方法。还是在主线程中执行。
线程对象调用start方法可以开启线程,并且使得线程进入就绪状态;之后让jvm在开启的线程中调用run方法使线程进入运行状态,run结束后线程终止
线程对象调用run方法不开启线程,调用start方法可以启动线程,并且使得线程进入就绪状态,而run方法只是thread的一个普通方法,还是在主线程中执行。
线程的run()方法和start()方法有什么区别?
启动一个线程需要调用 Thread 对象的 start() 方法,调用线程的 start() 方法后,线程处于可运行状态,
此时它可以由 JVM 调度并执行,这并不意味着线程就会立即运行
run() 方法是线程运行时由 JVM 回调的方法,无需手动写代码调用直接调用线程的 run() 方法,相当于
在调用线程里继续调用方法,并未启动一个新的线程
#线程的挂起
正在运行中的线程,由于CPU分配的时间片用完,所以需要保存当前线程运行的各项状态信息,直到CPU下次再在就绪队列中选中这个线程,恢复线程然后继续执行这个线程
多线程如果线程挂住了怎么办?
#线程的 同步与异步
异步:是多个线程抢占资源,不排队,效率高,但是数据不安全
同步:每次只有一个线程独占资源,排队,效率低但是安全,synchronized也被称作同步关键字
##同步和异步有何异同,在什么情况下分别使用他们?举例说明。
答:如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
同步和异步有何异同,分别在什么情况下使用?
同步:发送一个请求,等待返回,然后再发送下一个请求
异步:发送一个请求,不等待返回,随时可以再发送下一个请求
使用场景
1.如果数据存在线程间的共享,或竞态条件,需要同步。如多个线程同时对同一个变量进行读和写的操作
2.当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,
就可以使用异步,提高效率、加快程序的响应
#线程相关的基本方法?(必会) 等睡醒让中止守护
线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等
1.线程等待(wait)
使线程处于等待状态,只有等另外线程的通知或被中断才会返回。
需要注意的是调用 wait()方法后,会释放对象的锁。因此,
wait 方法一般用在同步方法或同步代码块中。
2.线程睡眠(sleep)
使线程处于睡眠状态
与 wait 方法不同的是 sleep 不会释放当前对象的锁,
//sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法会导致当前线程进入 WATING
6.线程唤醒(notify)
唤醒在监视器上等待的一个线程。如
果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并
在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视
器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,
被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。
6.1类似的方法还有 notifyAll() :唤醒在监视器上等待的所有线程。
3.线程让步(yield) 一 哦 的
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争
CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU
时间片,但这又不是绝对的,有的操作系统对 线程优先级并不敏感。状态.
4.线程中断(interrupt)
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)
interrup方法用于向线程发行一个通知信号,会影响该线程内部的一个中断标识位,这个线程本身并不会因为调用了interrup方法而改变状态。
5.Join 等待其他线程终止 ——————面试问过
join方法用于等待其它线程终止;如果当前线程中调用另一个线程的join方法,则当前线程转为阻塞状态,等到 另一个线程结束,当前线程再由阻塞状态转为就绪状态
7.后台守护线程:setDaemon 赛ce dei 门 setDaemon方法用于定义一个守护线程,也叫作“服务线程”
为用户线程提供公共服务,在没有用户线程可服务时会自动离开
#什么是守护线程?赛ce dei 门
Java线程分为用户线程和守护线程。
守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。
注意:
1.setDaemon(true) 必须在 start() 之前设置,否则会抛出IllegalThreadStateException异常,该线程仍默认为用户线程,继续执行
2.守护线程创建的线程也是守护线程
3.守护线程不应该访问、写入持久化资源,如文件、数据库,因为它会在任何时间被停止,导致资源未释放、数据写入中断等问题
##请说出你所知道的线程同步的方法。 等睡醒
wait():使一个线程处于等待状态,并且释放所持有的对象的lock;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException 异常;
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM 确定唤醒哪个线程,而且不是按优先级;
notifyAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
##wait()和sleep()的区别?(必会) 锁位捕来
2.关于锁的释放:
wait():在等待的过程中会释放锁;
sleep():在等待的过程中不会释放锁
3.位置:
wait():必须在同步代码块中使用;
sleep():可以在任何地方使用;
4.是否需要捕获异常
wait():不需要捕获异常;
sleep():需要捕获异常
1. 来自不同的类
wait():来自Object类;
sleep():来自Thread类;
sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep 不会释放对象锁。wait 是Object 类的方法,对此对象调用wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
#Threadloal/线程共享资源 的原理(高薪常问)
ThreadLocal:为共享变量在每个线程中创建一个副本,每个线程都可以访问自己内部的副本变量。通过threadlocal保证线程的安全性。
其实在ThreadLocal类中有一个静态内部类ThreadLocalMap(其类似于Map),用键值对的形式存储每一个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本。
ThreadLocal 本身并不存储值,它只是作为一个 key保存到ThreadLocalMap中,但是这里要注意的是它作为一个key用的是弱引用,因为没有强引用链,弱引用在GC的时候可能会被回收。这样就会在ThreadLocalMap中存在一些key为null的键值对(Entry)。因为key变成null了,我们是没法访问这些Entry的,但是这些Entry本身是不会被清除的。如果没有手动删除对应key就会导致这块内存即不会回收也无法访问,也就是内存泄漏。
使用完ThreadLocal之后,记得调用remove方法。 在不使用线程池的前提下,即使不调用remove方法,线程的"变量副本"也会被gc回收,即不会造成内存泄漏的情况。
1.18 synchronized 和 volatile 的区别是什么?(高薪常问)
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
1.19synchronized 和 Lock 有什么区别? (高薪常问)
首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可);
Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
多线程
##什么是多线程
Java中并发运行多个流程的能力
##多线程实现方案/创建线程有几种方式?
1.继承Thread类并重写 run 方法创建线程
2.实现Runnable接口并重写 run 方法。
3..实现 Callable接口并重写 call 方法,创建线程。
4.使用线程池创建(使用java.util.concurrent.Executor接口)
###Runnable和Callable的区别?(必会)
1、Runnable 接口 run 方法无返回值;
Callable 接口 call 方法有返回值,支持泛型
2、Runnable 接口 run 方法 只 能抛出运行时异常,且无法捕获处理;
Callable 接口 call 方法 允许 抛出异常,可以获取异常信息
##多线程数据安全隐患--面试
###如何理解线程的安全与不安全
线程安全:多个线程并发执行时,仍旧能够保证数据的正确性。
线程不安全:多个线程并发执行时,不能能够保证数据的正确性。
###导致(多)线程不安全的因素有哪些?
多个线程存在并发执行。
多个线程并发执行时存在共享数据集(临界资源)。
多个线程在共享数据集上的操作不是原子操作。
###如何保证线程安全呢?
1、基于CAS算法实现非阻塞同步(基于CPU硬件技术支持)。
1、通过加锁对 共享资源 进行限制访问(例如:syncronized,Lock)。
2、取消共享,每个线程一个对象实例
加锁synchronized 上锁,具体怎么实现的:在哪加,怎么加。--面试问过
前提:锁对象必须唯一!!!
一、同步代码块【常用】
将可能出现安全问题的代码放入同步代码块中,同步代码块的格式是
1)synchronized关键字在描述一些 静态方法时默认的对象锁是 方法所在类的类名.class/当前类的class对象
public static String 方法名{
synchronized(唯一的对象锁,即方法所在类的类名.class){
可能出现数据安全问题的所有代码
}
}
2)synchronized关键字在描述一些 非静态方法时默认的对象锁是 this/当前方法所在类的对象
public String 方法名{
synchronized(唯一的对象锁,即this){
可能出现数据安全问题的所有代码
}
}
二、 可能出现多线程安全问题的 方法的返回值类型 前加上synchronized
public static synchronized String method(){
}
public synchronized String method(){
}
说一下 synchronized 底层实现原理?(高薪常问)//Synchronized 关键字是怎么用的
Synchronized作为Java中一个关键字,它可以修饰静态方法(锁为方法所在类的Class对象),实例方法(锁为方法所在类的实例对象),方法内代码块(同步代码块,锁对象自己选)。
synchronized可以保证代码块或者方法在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
锁
同步锁、死锁、乐观锁、悲观锁 (高薪常问)
1、同步锁/排他锁:
在同一时间内只允许一个线程访问 共享资源。Java 中可以使用 synchronized 关键字来取得一个对象的同步锁。
Synchronized作为Java中一个关键字,它可以修饰静态方法(锁为方法所在类的Class对象),实例方法(锁为方法所在类的实例对象),方法内代码块(同步代码块,锁对象自己选)。
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
并发环境下,操作同步资源之前需要给同步资源先加锁,这把锁就是存在于Java对象头中,Java里的Synchronized就是基于对象头中的Mark Word(标记字段)、Klass Pointer(类型指针)对象实现同步的。
1)同步代码块底层采用monitorenter、monitorexit指令显式的实现(了解)。
2)同步方法底层则使用ACC_SYNCHRONIZED标记符隐式的实现(了解)。
修饰普通方法
修饰静态方法
指定对象,修饰代码块
特点:
阻塞未获取到锁、竞争同一个对象锁的线程
获取锁无法设置超时
无法实现公平锁
控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll()
锁的功能是 JVM 层面实现的
在加锁代码块执行完或者出现异常,自动释放锁
原理:
同步代码块是通过 monitorenter 和 monitorexit 指令获取线程的执行权
同步方法通过加 ACC_SYNCHRONIZED 标识实现线程的执行权的控制
2、死锁:
就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。陷入了无限的等待中
两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个
进程都陷入了无限的等待中
如何避免死锁呢?
避免在一个线程中同时获取多个锁。
避免在一个锁中获取其他的锁资源。
3) 考虑使用定时锁来替换内部锁机制,如lock.tryLock(timeout)。
如何确保 N 个线程可以访问 N 个资源同时又不导致死锁?
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制
线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不
会出现死锁了
3、乐观锁:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间有没有人去更新这个数据;
乐观锁适合读多写少的场景,不加锁能够使其并发读操作的性能大幅提升。
4、悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞,直到它拿到锁
悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
锁的优化策略
1、读写分离
2、分段加锁
3、减少锁持有的时间
4.多个线程尽量以相同的顺序去获取资源 不能将锁的粒度过于细化,不然可能会出现线程的加锁和释放次数过多,反而效 率不如一次加一把大锁。
#数据库 锁
##什么是数据库锁?
当多个用户同时存取同一数据时,多个事务同时存取同一数据。若对并发操作不加控制 就可能会存储和读取不正确的数据。 所以要加锁。
当事务在对某个数据对象进 行操作前,先向系统发出请求,对其加锁。加锁后事务就对该数据对象有了一定 的控制,在该事务释放锁之前,其他的事务不能对此数据对象进行更新操作。
##基本锁类型:
锁包括行级锁和表级锁
1.行锁和表锁 概念 背下来
表锁:访问数据库的时候,锁定整个表数据,防止并发错误。
行锁:访问数据库的时候,锁定整个行数据,防止并发错误。
2.行锁 和 表锁 的区别:粒冲病快死
表锁:锁定粒度大,锁冲突概率高, 开销小,并发度最低;加锁快,不会出现死锁;
行锁:锁定粒度小,锁冲突概率低,开销大,并发度最高 ;加锁慢,会出现死锁;
与表锁完全相反
表级锁分为读锁和写锁。
##读锁和写锁 阿萨
一个事务给数据加上读锁/共享锁(S锁),可以读取数据但不能修改数据;
其他事务只能再对数据加读锁,不能加写锁;
共享锁(S锁)又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
一个事务给数据加上写锁/排他锁(X锁),可以读取数据也可以修改数据;
其他事务不能再对数据加任何锁
若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。