Java多线程(面试用)

进程和线程的概念与区别

概念:

进程是资源调度的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

区别:

进程是资源分配的最小单位,线程是程序执行的最小单位。

进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。

线程执行开销小,但不利于资源的管理和保护;

线程的状态及转换

新建状态:线程对象被创建后,就进入了新建状态

就绪状态:也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。

运行状态:线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。

阻塞状态:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行

死亡状态:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

wait,notify,notifyAll

用于阻塞和唤醒对象上的线程(是属于Object的方法),也可以使用Lock(Condition)实现类似的效果

wait()的作用是让当前线程进入等待状态,会释放它持有的所有锁

notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程

sleep和wait概念与区别

概念

sleep() 方法使当前线程进入停滞状态(阻塞当前线程),让出 CUP 的使用,目的是不让当前线程独自霸占该进程所获的 CPU 资源,该方法是 Thread 类的静态方法。sleep休眠期间不会释放锁,休眠时间期满后,该线程不一定会立即执行

wait() 方法是 Object 类的,当一个线程执行到 wait() 方法时就进入到一个和该对象相关的等待池中,同时释放对象的锁,使用notify来唤醒锁

区别

sleep() 不释放同步锁,wait() 释放同步锁。

sleep(milliseconds) 可以用时间指定来使他自动醒过来,wait() 可以用 notify() 直接唤起

sleep() 是 Thread 的静态方法,而 wait() 是 Object 的方法。

wait()、notify()、notifyAll() 方法只能在同步控制方法或者同步控制块里面使用,而 sleep() 方法可以在任何地方使用。

Java创建线程的方法

  1. 继承Thread类创建线程

  2. 通过Runnable创建线程

  3. 通过Callable和FutureTask创建线程

使用多线程的原因

  1. 更多的处理核心
  2. 更快的响应时间
  3. 更好的编程模型

对线程优先级的理解

现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程分配到的时间片多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性

在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。

ThreadLocal的理解

ThreadLocal的字面意思是本地线程变量,在Thread类中维护了一个ThreadLocal的成员变量,在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

ThreadLocal内部有一个静态内部类ThreadLocalMap来维护变量的存取,存取的时候以threadLocal自身为key,获取value,保证变量的安全

get: 获取ThreadLocal中当前线程共享变量的值。
set: 设置ThreadLocal中当前线程共享变量的值。
remove: 移除ThreadLocal中当前线程共享变量的值。
initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

什么是死锁?

死锁是两个或两个以上线程永远阻塞的情况

四个条件:互斥,循环等待,请求和保持,不可剥夺

多线程最佳实践

给你的线程起个有意义的名字

避免锁定和缩小同步的范围

多用同步类少用wait 和 notify

多用并发集合少用同步集合

AQS

AbstractQueuedSynchronized 抽象队列式同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch

AQS维护了一个volatile in state(共享资源)和一个FIFO线程等待队列(多线程争夺资源的时候被阻塞时进入这个队列),state的访问方式有三种:getState() setState() compareAndSetState()

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

CAS

compare and swap比较和交换。是一种乐观锁的技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

cas包括三个操作数:需要读写的内存位置(V),进行比较的预期值(A),新写入的值(B),

如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B,否则处理器不做任何操作

无论哪种情况,它都会在CAS 指令之前返回该位置的值

线程池

线程池的好处:降低资源消耗,提高相应速度,提高线程的可管理性

线程的实现原理:

  1. 线程池中判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程
  2. 线程池判断工作队列是否已满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
  3. 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务

Java中的线程池:

FixedThreadPool

创建固定线程数,适用于为了满足资源管理的需求,而限制当前线程数量的应用常见,适合负载比较重的服务器

SingleThreadExecutor

创建使用单个线程,适用于需要保证顺序的执行各个任务,并且在不同时间点,不会有多个线程是活动的

CachedThreadPool

创建一个根据需要创建线程的,大小无界的线程池,适用于执行很多短期异步任务的小程序

ScheduledThreadPoolExecutor

主要用来在给定的延迟之后运行任务,或者定期执行任务

Java中的volatile 变量是什么?

volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程
修改一个共享变量时,另外一个线程能读到这个修改的值。

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存

当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序

Synchronized和volatile的区别

volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的

volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性

volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

偏向锁/轻量级锁/重量级锁

锁共有四种状态:无锁状态,偏向锁,轻量级锁,重量级锁;这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。

是指锁的状态,并且是针对Synchronized,在java5引入锁升级的机制来实现高效的synchronized,者三种锁的状态是通过对象监视器在对象头中的字段来表明的

偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

Lock和Synchronized

尝试非阻塞的获取锁:当前线程尝试获取锁,如果这 个时刻锁没有被其他线程获取到,则成功获取并持有锁

能够中断的获取锁:与Synchronized不同,获取到锁的线程能够响应中断,当获取到的锁进程被中断时,中断异常会被抛出,同时还会被释放

超时获取锁:在指定的截至之前获取锁,如果截止时间到了任然无法获取锁,则返回

原子操作

不可被中断的一个或一系列操作

可以使用锁和循环CAS来实现原子操作



推荐大家看《Java并发编程艺术》
快要面试了,以后有时间会慢慢补充的!以后一定要把Java线程彻底搞明白!!!

猜你喜欢

转载自blog.csdn.net/ifenggege/article/details/84888751