何为进程?
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行程序,是一个进程从创建、运行到消亡的过程。
在Java中,当我们启动main函数时其实就是启动了一个JVM的进程,而main函数所在的线程就是这个进程中的一个线程,也称主线程。
何为线程?
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
一个java程序的运行是main线程和多个其他线程同时运行。
一个进程中可以有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器、虚拟机栈和本地方法栈。
线程是进程划分更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定。因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
堆和方法区
堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象,方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄露、死锁、线程不安全等。
线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。
认识线程死锁
例如:线程A持有资源1,线程B持有资源2,它们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态
如何预防和避免线程死锁
预防—破坏死锁产生的必要条件即可:
避免死锁:
共同点:两者都可以暂停线程的执行
区别:
为什么wait()方法不定义在Thread中?
wait()是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入WAITING状态,自然要操作对应的对象(Object)而非当前的线程。
类似的:为什么sleep()方法要定义在Thread?
:因为sleep()是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁。
new一个Thread,线程进入了新建状态。调用start()方法,会启动一个线程并使线程进入就绪状态,当分配到时间片后就可以开始运行了。start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这才是真正的多线程工作。而如果直接执行Thread类的run方法,会把run()方法当成一个main线程下的普通方法去执行,并不会在某个下线程中执行它,所以这并不是多线程工作。
调用start()方法方可启动线程并使线程进入就绪状态,直接执行run()方法的话不会以多线程的方式执行。
在java中,volatile关键字可以保证变量的可见性,如果我们将变量声明为volatile,这就指示JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。volatile关键字能保证数据的可见性,但不能保证数据的原子性。
在Java中,volatile关键字除了可以保证变量的可见性,还有一个重要的作用就是防止JVM的指令重排序。如果我们将变量声明为volatile,在对这个变量进行读写操作的时候,会通过插入特定的内存屏障的方式来禁止指令重排序。
什么是悲观锁
悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题,所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。也就是说,共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其他线程。Java中 synchronized和 ReentrantLock等独占锁就是悲观锁思想的实现。高并发的场景下,激烈的锁竞争会造成线程阻塞,大量阻塞线程会导致系统的上下文切换,增加系统的性能开销。并且,悲观锁还可能会存在死锁问题,影响代码的正常运行。
什么是乐观锁
乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其他线程修改了。
高并发场景下,乐观锁相比悲观锁来说,不存在锁竞争造成线程阻塞,也不会有死锁的问题,在性能上往往会更胜一筹。但是,如果冲突频繁发生(写占比非常多的情况),会频繁失败和重试,这样同样会非常影响性能,导致CPU飙升。
理论上来说:
如何实现乐观锁
乐观锁一般会使用版本号机制或CAS算法实现,CAS算法相对来说更多一些。
乐观锁存在哪些问题
synchronized是什么?有什么用?
如何使用synchronized
给当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
synchronized void method(){
//业务代码
}
给当前类加锁,会作用于类的所有对象实例,进入同步代码前要获得当前class的锁
这是因为静态成员不属于任何一个实例对象,归整个类所有,不依赖于类的特定实例,被类的所有实例共享
静态synchronized方法和非静态synchronized方法之间的调用互斥吗? 不互斥。如果一个线程A调用一个实例对象的非静态synchronized方法,而线程B要调用这个实例对象所属类的静态synchronized方法,是允许的,不会发生互斥。因为,访问静态synchronized方法占用的锁是当前类 的锁,而访问非静态synchronized方法占用的锁是当前实例对象 的锁。
synchronized(object) //表示进入同步代码库前要获得给定对象的锁
synchronized(类.class) //表示进入同步代码前要获得给定class的锁
构造方法可以用synchronized修饰吗
synchronized 和 volatile 有什么区别
synchronized关键字和volatile关键字是两个互补的存在,而不是对立的存在!
ReentrantLock是什么?
ReentrantLock实现了Lock接口,是一个可重入且独占式的锁,和synchronized关键字类似。不过,ReentrantLock更灵活、更强大,增加了轮询、超时、中断、公平锁和非公平锁等高级功能。
ReentrantLock里面有一个内部类Sync,Sync继承了AQS,添加锁和释放锁的大部分操作实际上都是在Sync中实现的。Sync有公平锁和非公平锁两个子类。ReentrantLock默认使用非公平锁。
公平锁和非公平锁的区别
synchronized和ReentrantLock有什么区别?
可中断锁和不可中断锁有什么区别
ThreadLocal有什么用?
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决?JDK中自带的ThreadLocal类正是为了解决这样的问题。ThreadLocal类主要解决的是让每个线程绑定自己的值。
ThreadLocal内存泄露问题是怎么导致的
ThreadLocalMap中使用的key为ThreadLocal的弱引用,而value是强引用。所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,key会被清理掉,而value不会被清理掉。
什么是线程池
线程池就是管理一系列线程的资源池,当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。
为什么要用线程池
池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率
线程池提供了一种限制和管理资源的方式。每个线程池还维护一些基本统计信息,例如已完成任务的数量。
线程池的好处:
如何创建线程池
Future类有什么用
Future类是异步思想的典型运用,主要用在一些需要执行耗时任务的场景,避免程序一直原地等待耗时任务执行完成,执行效率太低。具体来说是这样的:当我们执行某一耗时的任务时,可以将这个耗时任务交给一个子线程去异步执行,同时我们可以干点其他的事情,不用傻傻等待耗时任务执行完成。等我们的事情干完后,我们再通过Future类获取到耗时任务的执行结果。这样一来,程序的执行效率就明显提高了。
这其实就是多线程中经典的Future模式,你可以将其看做是设计模式,核心思想是异步调用,主要用在多线程领域,并非java语言独有。
在java中,Future类只是一个泛型接口,其中定义了5个方法,主要包括四个功能:
简单理解就是:我有一个任务,提交给了Future来处理,任务执行期间我自己可以去做任何想做的事,并且,在这期间我还可以取消任务以及获取任务的执行状态。一段时间之后,我就可以Future那里直接获取任务执行结果。