线程和进程的概念
答案一:进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间。一个进程可以启动多个线程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
进程是运行着的应用程序,线程是进程的一个执行序列,一个进程包括多个线程,而线程也可以被称为轻量级的进程。进程是资源分配的基本单位,线程是资源调度的基本单位
答案二:线程是指程序在执行过程中,能够执行程序代码的一个执行单元。
进程是指一段正在执行的程序。而线程有时也被称为轻量级进程,它是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段,数据段和堆空间)及一些进程级的资源(例如打开的文件),但是各个线程拥有自己的栈空间。
为什么要使用多线程?
1、可以减少程序的响应时间。
2、与进程相比,线程的创建和切换开销更小。
3、使用多线程能简化程序的结构,使程序便于理解和维护。
同步机制
Java语言包含两种内在的同步机制:同步块和volatile变量。这两种机制的提出都是为了实现代码线程的安全性。其中volatile变量的同步性较差(但有时它更简单并且开销更低),而且其使用也容易出错。
锁
锁的原理
1、Java中每个对象都有一个内置锁。
2、当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁,锁定对象,在对象上锁定或者在对象上同步。
3、当程序运行到synchronized同步方法或代码块时该对象锁才起作用。
4、一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronize方法或代码块,直到该锁被释放。
要点
1、只能同步方法,而不能同步变量和类。
2、每个对象只有一个锁;
3、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。
5、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6、线程睡眠时,它所持的任何锁都不会释放。
7、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
线程和进程
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多个进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
线程的三种特性
参考:https://www.cnblogs.com/xiaoqisfzh/p/5959013.html
原子性:即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
对于可见性,java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中,因此可以保证可见性。
有序性:即程序执行的顺序按照代码的先后顺序执行。在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
在Java里面,可以通过volatile关键字来保证一定的“有序性”(具体原理在下一节讲述)。另外可以通过synchronized和lock来保证有序性,很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
Join方法
线程实例的join方法可以使得一个线程在另一个线程结束后再执行,即也就是说**使得当前线程可以阻塞其他线程**。
thread.join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的join方法,直到线程A执行完毕后,才会继续执行线程B。
join方法使用示例
public class JoinExample
{
public static void main(String[] args) throws InterruptedException
{
Thread t = new Thread(new Runnable()
{
public void run()
{
System.out.println("First task started");
System.out.println("Sleeping for 2 seconds");
try
{
Thread.sleep(2000);
} catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("First task completed");
}
});
Thread t1 = new Thread(new Runnable()
{
public void run()
{
System.out.println("Second task completed");
}
});
//在t执行完毕后t1执行
t.start(); // Line 15
t.join(); // Line 16
t1.start();
}
}
阻塞的情况分三种
(1)等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”,进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒。
(2)同步阻塞:运行的线程在获得对象的同步锁时,若该同步锁被别的线程占用,则jvm会把线程放入“锁池”中。
(3)其他阻塞:运行sleep()或者join()方法,或者发出了I/0请求时,JVM会把该线程设置为阻塞状态,当sleep()状态超时时,join()等待线程终止或者超时,或者i/o处理完毕,线程重新转入就绪状态。
面试题
1 sleep和wait方法的区别。
1、来自不同的类:sleep方法来自Thread类,而wait来自Object类。
sleep是Thread的静态方法,谁调用的谁去睡觉。即使在a线程里调用b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要去b的代码中调用sleep。
2、锁:sleep方法不会释放锁;而wait方法会释放锁,使得其他线程可以使用同步控制块或者方法,当前线程处于等待池。
解析:
- sleep不出让锁资源;wait是进入线程等待池等待,释放锁。但这个方法都会释放CPU资源,(CPU资源和锁资源不是一个东西)其他线程可以占用CPU。
- 一般wait方法不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll 唤醒等待中的所有线程,才会进入就绪队列等待OS分配系统资源。
- sleep可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt强行打断。
3、使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。
线程的资源有不少,但应该包含CPU资源和锁资源这两类。
sleep方法会让出CPU资源,但是不会释放锁资源,其他线程可以利用CPU时间片,但如果其他线程要获取sleep拥有的锁才能执行,则会因为无法获取锁而不能执行,继续等待。
2 同步块synchronized和Lock接口。
其中Lock接口最大的优势是它为读和写提供两个单独的锁。
相同点:两种方式都是锁机制。
不同点:
- 1 synchronized是关键字,Java语言内部提供;Lock是接口
- 2 synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁。lock异常时不会自动释放锁,所以需要在finally中实现释放锁。
- 3 synchronized是非中断锁,必须等待线程执行完成释放锁; Lock是可中断锁,
- 4 lock可以使用读锁提高多线程读效率。
- 5 synchronized会线程执行完会自动释放锁,而lock必须在finally里手动释放锁。
3 synchronized和volatile的区别
参考:https://www.cnblogs.com/cpcpc/archive/2011/09/05/2167930.html
1、volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取,而不是去缓存读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
2、volatile仅能使用在变量级别;synchronized则可以使用在变量,方法,和类级别的。
3、volatile仅能实现变量的修改可见性,并不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性。
4、volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
5、volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
4 sleep和yield方法的区别
1、都能使当前处于运行状态的线程放弃CPU,把运行的机会给其他线程。
2、sleep方法会给其他线程运行机会,但是不考虑优先级;yield方法只会给相同优先级或者更高优先级的线程运行机会。
3、调用sleep方法后,线程进入计时阻塞状态;调用yield方法后,线程进入就绪状态,所以有可能该线程再次被调度。
5 实现多线程的方式
1、继承Thread java.lang.Thread
2、实现Runnable接口 java.lang.Runnable
3、实现Callable接口 java.util.concurrent.Callable
分析:其实实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已。因此可以将实现Runnable接口和实现Callable接口归为一种方式。这种方式与继承Thread方法之间的主要区别。
采用实现Runnable、Callable接口的方式
1、线程类只是实现了Runnable接口或者Callable,还还可以继承其他类。
2、在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
采用继承Thread类的方式创创建多线程
劣势:因为线程类已经继承了Thread类,所以不能再继承其他类。
6 进程和线程的区别
1、地址空间和其他资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其他进程是不可见的。
2、通信:进程间通信,线程间可以直接读写进程数据段来进行通信--需要进程同步和互斥手段的辅助,保证数据的一致性。
3、调度和切换:线程上下文切换比进程上下文切换的速度快。
4、在多线程OS中,进程不是一个可执行的实体。
7 同步方法和同步代码块的区别
1、同步方法默认用this或者当前类class对象作为锁
2、同步代码块选择任意一段代码块来加锁,比同步方法要更细的颗粒度。
颗粒度:锁的级别,比如行级,表级。
8 什么是死锁?
死锁是指多个进程因竞争资源而造成的一种僵局(互相等待)。
死锁产生的4个条件:
互斥条件:
不剥夺条件:
请求和保持条件;
循环等待:
9 线程的生命周期(待续)
生命周期的五种状态
1、新建:创建线程实例对象,new Thread(),该线程处于新建状态。
2、就绪:线程实例对象调用start方法,则该线程处于就绪状态。
特点:被分配了指令计数器和工作空间,处于就绪队列等待CPU资源,随时可能被调用。
3、运行:该线程获得CPU资源,运行run方法体。此时除非该线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
4、死亡:主线程的main方法执行完或其他的线程的run方法执行完,线程死亡。
特点:不占用处理机,资源全部释放。
5、阻塞:
特点:线程不占用处理机;释放资源入口。把线程栈的地址给寄存器,之后阻塞的线程将会被放置在内存或外存(缓冲区)。
10 run方法和start方法有什么区别。
1、线程类调用start方法,此时该线程处于就绪状态,意味着这个线程可以被JVM来调度执行。如果该线程被调度的话,JVM会自动调用run方法来完成实际操作。
2、如果直接调用run方法,则会当成普通类对象,程序中只有一个主线程,只是对象调用方法的含义,没有线程的概念。
3、只有线程类调用start方法才能真正达到多线程的目的。
补充:
1、调用run方法,则此时没有线程的概念,只是被当做一个普通对象来调用普通的方法,需要顺序执行,还要等待run方法执行完才能继续执行下面的代码;
2、调用start方法,实现多线程,则多个线程异步执行,不需要等待前一个线程run方法体执行完而直接继续执行下面的代码。
11 多线程同步的实现方法有哪些?
1、synchronized关键字:包括synchronized方法和synchronized块。
2、wait方法与notify方法。
3、Lock接口的lock方法。
12 终止线程的方法有哪些?
可以使用stop方法和suspend方法来终止线程的执行。
stop方法:会释放已经锁定的所有监视资源。
suspend:容易发生死锁。
死锁指的是两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,如果无外力作用,他们都将无法推进。
13 什么是守护线程?
1、Java提供了两种线程:守护线程和用户线程。
2、当所有用户线程执行完毕的时候,JVM自动关闭。但是守护线程却不独立于JVM,守护线程一般是由操作系统或者用户自己创建的。
3、守护线程的作用是为其他线程的运行提供服务,比如说GC线程。
4、用户线程和守护线程几乎一样,唯一的不同之处就在于如果用户线程已经全部退出运行,只剩下守护线程存在了,JVM也就退出了。
14 join方法的作用是什么?
在Java语言中,join方法的作用是让调用该方法的线程在执行完run方法后,再执行join方法后面的代码。
简单来说,就是将两个线程合并,用于实现同步功能。
15 你知道多线程?能讲讲吗?
答题角度:什么是线程,什么是多线程,线程的状态
答案:
16 什么是线程安全?
如果代码中所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的、
17 为什么wait,notify,notifyAll这些方法不在Thread类里面?
答题方向:说明为什么把这些方法放在Object类里是有意义的,还有不把它放在Thread类里的原因。
答案:Java提供的锁是对象级的而不是线程级额,每个对象都有锁,通过线程获得。如果线程需要等待某些锁你那么调用对象中的wait方法就有意义了。如果wait方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。
简单的说,由于wait、notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
18 属于Thread类的方法
1、Thread类的方法:sleep静态方法,run,start,getPriority,yield,join
2、Object类的方法:wait ,notify,notifyAll