进程知识在面试中占到80%的问题!!
进程与线程的概念
进程:操作系统中一个程序的执行周期称为进程。
线程:一个进程执行多个任务。通常来讲,每一个任务就称为一个线程。
DOS系统属于单进程,许多嵌入式系统也是单进程。
无论进程还是线程,最终的目的是最大限度的提高计算机系统资源的使用效率,提高程序的并发性。
进程与线程的比较:
1.与进程相比,线程更加轻量级,创建,撤销一个线程比启动。撤销一个进程开销小得多。一个进程中的所有线程共享此进程的所有资源。
2.没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。
3.进程是操作系统资源调度的基本单位,进程可以独享资源;
线程需要依托进程提供的资源,无法独立申请操作系统资源,是OS任务执行的基本单位。
地址空间和其他资源:每个进程是占有独立的地址空间,包括代码、数据及其他资源。而属于同一个进程中的线程只能共享这些资源,不具有独立的地址空间和其他资源。
通信:进程直接的通信(简称IPC)开销较大且受到诸多限制,必须具有明确的对象或操作接口并采用统一的通信协议;而线程可以直接读写进程数据段来进行通信,需要进程同步和互斥手段的辅助,以保证数据的一致性,开销较少且简单。
切换:进程间的切换开销较大,而线程间的切换开销较小。
控制表:与进程的控制表PCB相似,线程也有自己的控制表TCP,但是TCP中保存线程状态远少于PCB。
高并发:同一时间段进程的线程访问量非常高。(由于线程过多,开销很大,因此需要部署多个服务器,就被称为分布式。)
高并发带来的问题:服务器内存不够用,无法处理新的请求。
DDos攻击:黑客利用僵尸设备同时对某个进程同时发起请求,因此占用正常用户的访问请求。
并发与并行的区别:并发是逻辑上同时发生,是指在某一段时间内运行多个进程;而并行是在物理上同时发生,是指在某一个时间点同时运行多个进程,即进程是轮换执行的。
并行是两个任务同时进行,比如我跟两个网友聊天,左手操控一台电脑,右手操控一台电脑,这样就是并行,所以并行需要多核CPU
并发是指两个任务都请求运行,但是处理器只能接受一个任务,就把这两个任务安排轮流进行,例如我在一台电脑上跟人聊天,两个人同时给我发了消息,我只能回复了A再回复B。
程序与进程的区别==(面)==:
动态性与静态性:程序是静态的,而进程是动态的。进程是一个能独立运行的单位,能与其他线程并发执行,程序不能作为一个独立的运行单位。
临时性与永久性:程序是永久的,而进程是临时的。程序可以作为一种软件资源长期保存,而进程仅是运行的程序,就有一定的声生命周期,会动态的产生和消亡。
组成不同:进程包括了程序、数据和PCB(进程控制块)
程序与进程不一定具有一一对应的关系:因为一方面,一个程序可以由多个进程公用,另一方面,一个进程在其获得中可顺序的执行若干个程序。
线程的五个状态:创建
就绪状态
运行状态
阻塞状态
终止 :线程进入死亡状态的情况主要有两种:1.线程的run()方法执行完毕并退出2.线程运行过程中产生异常。处于死亡状态的线程不具有继续运行的能力,会等待GC回收。
Java多线程的实现:
1.继承Thread类实现多线程
java.lang.Thread是线程操作的核心类。创建一个线程最简单的方法就是直接继承Thread类,然后覆写run()方法(相当于主线程的main()方法),是线程的入口方法。
具有多继承的局限性,业务逻辑写在run方法中,会与进程类耦合,灵活性低,复用性小,违背了开闭原则。
无论哪种方法实现多线程,线程启动一定调用Thread类提供的start方法,而不是run()方法
线程start()方法只能调用一次,多次调用会抛异常java.lang.IllegalThreadStateException
线程调用的顺序于优先级有关。
通过构造方法给Thread重命名:
public Thread(String name) {
init(null, null, name, 0);
}
获取线程名:线程对象名.getName();
重命名:线程对象名.setName("xxx");
class MyThread extends Thread{
String name;
public MyThread(String name) {
this.name=name;
}
public void run(){
for(int i=0;i<10;i++){
System.out.println(this.name+i);
}
}
}
public class ThreadTest {
//主线程
public static void main(String[] args) {
MyThread myThread = new MyThread("线程1");
MyThread myThread1 = new MyThread("线程2");
MyThread myThread2 = new MyThread("线程3");
myThread.start();
myThread2.start();
myThread1.start();
}
}
当调用start方法(Java)时,会判断当前线程是否新创建的且未被调用过,然后进入start0(Java)方法,进行资源调度,系统分配(JVM)会调用到 JVM_StartThread 方法,利用本地方法创建线程(JVM),然后回调run(JAVA的方法)执行线程的具体操作任务
2.实现Runnable接口
因为Thread是类,因此具有单继承局限,因此引入Runnable接口
是代理模式的实现逻辑,因为Thread类也实现了Runnable接口,通过构造方法将Runnable接口对象传给Thread类public Thread(Runnable target)
,调用start方法创建多线程。
使用Runnable接口实现多线程的方法可以实现业务逻辑的复用
public static void main(String[] args) {
MyThread1 myThread = new MyThread1("线程1");
MyThread1 myThread1 = new MyThread1("线程2");
MyThread1 myThread2 = new MyThread1("线程3");
//1.通过Thread的构造方法执行
new Thread(myThread).start();
new Thread(myThread1).start();
new Thread(myThread2).start();
//2.匿名内部类
Runnable runnable=new Runnable() {
@Override
public void run() {
System.out.println("hello,new thread");
}
};
new Thread(runnable).start();
//3.lambda表达式
new Thread(
()-> System.out.println("hello new thread")
).start();
}
3.Thread与Runnable接口的区别
因为Thread是类,因此具有单继承局限
在开发之中使用Runnable还有一个特点:使用Runnable实现的多线程的程序类可以更好的描述出程序共享的概念
使用Thread实现数据共享(产生若干线程进行同一数据的处理操作)
class Thread3 extends Thread{
private static int tick=10;
@Override
public void run() {
while(this.tick>0){
System.out.println(this.getName()+"剩余:"+this.tick--+"张票");
}
}
}
public static void main(String[] args) {
//利用Thread实现数据共享
Thread3 thread1=new Thread3();
thread1.setName("Thread-A");
Thread3 thread2=new Thread3();
thread2.setName("Thread-B");
thread1.start();
thread2.start();
//利用Runnable实现
Runnable runnable=new Runnable() {
private int ticket=10;
@Override
public void run() {
while(ticket>0){
//Thread.currentThread()获取当前线程名
System.out.println(Thread.currentThread()+ "剩余:"+ticket--+"张票");
}
}
};
new Thread(runnable,"线程一").start();
new Thread(runnable,"线程二").start();
}
4.Callable实现多线程
Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是很多时候 需要一些返回值,例如某些线程执行完成后可能带来一些返回结果,这种情况下就只能利用Callable来实现多线程。
Callable中的抽象方法为call(),并且含有返回值,需要通过Future接口的实现类FetureTask类接收Callable对象获得线程执行后的返回值,并且FetureTask类还实现了Runnable接口。
通过Thread类的构造方法接收FetureTask对象启动线程
FetureTask类构造方法 public FutureTask(Callable<V> callable)
超过指定时间则跳过结果(抛异常)继续执行,不会一直等待public V get(long timeout, TimeUnit unit)
获得返回结果public V get()
public class CallableTest implements Callable<String> {
private int tick=10;
@Override
public String call() throws Exception {
while(this.tick>0){
System.out.println(Thread.currentThread().getName()+"剩余:"+this.tick--+"张票");
}
return "票卖完了";
}
public static void main(String[] args) {
Callable<String> callable=new CallableTest();
FutureTask<String> futureTask=new FutureTask<String>(callable);
Thread thread = new Thread(futureTask);
thread.start();
try {
//获取任务返回结果(任务结束)
// String result= futureTask.get();
//超过一秒未返回结果则抛出异常
String result= futureTask.get(1,TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
System.out.println("结束");
}
}
主方法本身就是一个线程,所有的线程都是通过主线程创建并启动的。
线程方法体系图:
sleep方法
线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。
线程休眠会交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁,也就是说如果 当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象
休眠时间使用毫秒作为单位
public static native void sleep(long millis) throws InterruptedException
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+ "执行时间" + LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
new Thread(runnable,"线程2").start();
new Thread(runnable,"线程3").start();
}
所有的代码是依次进入到run()方法中的。
真正进入到方法的对象可能是多个,也可能是一个。进入代码的顺序可能有差异,但是总体的执行是并发执行。
线程让步(yield()方法
暂停当前正在执行的线程对象,并执行其他线程,yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield什么时候让是不确定的,因此整个执行过程是随机的。另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时 间,这一点是和sleep方法不一样的。
public static void main(String[] args) {
//yiled方法 让当前线程回到就绪状态
Runnable runnable= () -> {
for(int i=0;i<3;i++){
Thread.yield();
System.out.println(Thread.currentThread().getName()+"i="+i);
}
};
new Thread(runnable,"线程一").start();
new Thread(runnable,"线程三").start();
new Thread(runnable,"线程二").start();
}
join()方法
在一个线程中调用其他线程对象的join方法,会阻塞当前线程,去执行其他线程的run方法,执行完毕后再开始执行当前线程。
class MyRunnable implements Runnable{
private int tick=1000;
@Override
public void run() {
while(tick>0){
System.out.println(Thread.currentThread().getName()+"tick="+tick--);
}
}
}
public class ThreadMethodJoin {
public static void main(String[] args) {
MyRunnable runnable=new MyRunnable();
//线程
Thread thread=new Thread(runnable,"thread-A");
thread.start();
//在主线程中调用线程的join方法会阻塞主线程,直到调用线程对象的run方法执行完毕,主线程才会继续执行
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程"+Thread.currentThread().getName());//主线程
}
}
(面)sleep()方法和wait()方法的异同。
- 执行两个方法都会让出运行权,即让出CPU。
- 执行两个方法都会让当前线程进入阻塞状态。
- sleep()方法属于Thread类的静态方法,而wait()方法将休眠中的线程唤醒,而执行wait()方法后可以使用notify()方法和notifyAll()方法唤醒等待线程。
- 若线程T拥有对象O的对象锁,当T执行sleep()方法后,线程T将进入对象O的锁池中,但是不会释放对象O的锁,而执行wait()方法后,线程A将进入对象O的等待权,并释放对象O的锁;而执行wait()方法后,线程A将进入对象O的锁的等待池,并释放O的锁,等待被唤醒后进入O的锁池,载等待获取对象O的锁的运行权。
线程停止
多线程中由三种方法停止线程
1.设置标记位
2.使用stop方法强制使线程退出,但是不安全已被废弃
public class FlagTest {
public static void main(String[] args) throws InterruptedException {
//使用标记位方式停止线程
Runnable runnable=new MyRunnable();
Thread thread = new Thread(runnable, "标记线程");
thread.start();
Thread.sleep(3000);//主线程休眠3秒
//修改标记位
((MyRunnable) runnable).setFlag(false);
//使用stop强制停止
// thread.stop();
}
}
class MyRunnable implements Runnable{
private boolean flag=true;
@Override
public void run() {
int i=0;
while(this.flag){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"第"+ ++i+"次执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
3.使用Thread类的方法interrupt中断线程。
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
Runnable runnable=new MyRunnable2();
Thread thread = new Thread(runnable, "标记线程");
thread.start();
Thread.sleep(3000);//主线程休眠3秒
thread.interrupt();
}
}
class MyRunnable2 implements Runnable{
@Override
public void run() {
int i=0;
while(true){
try {
//判断线程的中断情况
boolean interupt=Thread.currentThread().isInterrupted();
//非阻塞情况
if(interupt){
break;
}
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"第"+ ++i+"次执行");
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().isInterrupted());
e.printStackTrace();
return;
}
}
}
}
interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。
如果,线程的当前状 态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么在将 中断标志设置为true后,还会有如下三种情况之一的操作:
如果是wait、sleep以及jion三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个 InterruptedException;此时可以通过异常捕获,决定是否退出线程
线程优先级
线程的优先级指的是,线程的优先级越高越有可能,但仅仅是有可能而已
- 高优先级:public final static int MAX_PRIORITY = 10;
- 中等优先级:public final static int NORM_PRIORITY = 5; (主线程优先级为5)
- 低优先级:public final static int MIN_PRIORITY = 1;
设置优先级:public final void setPriority(int newPriority)
线程是有继承关系的,比如当A线程中启动B线程,那么B和A的优先级将是一样的。
守护线程
java 中有两种线程:用户线程和守护线程。守护线程是一种特殊的线程,它属于是一种陪伴线程。只要当前JVM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当后 一个非守护线程结束时,守护线程才会随着JVM一同停止工作。
主线程main是用户线程,JC垃圾回收器是一种守护线程
设置守护线程public final void setDaemon(boolean on)
设置线程A为守护线程,此语句必须在start方法之前执行
public class ShouHuThread {
public static void main(String[] args) {
//主线程是用户线程
//System.out.println(Thread.currentThread().isDaemon());//falase
Thread thread = new Thread(new DeamonRunn(), "线程-A");
//必须在start前调用
thread.setDaemon(true);
thread.start();
Thread thread1 = new Thread(new DeamonRunn(), "Thread-B");
thread1.start();
//主线程休眠
try {
Thread.sleep(3000);
//B是用户线程,还有主线程是用户线程,当所有用户线程都终止守护线程才会终止
thread1.interrupt();
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//主线程
}
}
class DeamonRunn implements Runnable{
@Override
public void run() {
int i=0;
while(true){
System.out.println(Thread.currentThread().getName()+"调用第"+ ++i+"次");
try {
System.out.println("线程名称:" + Thread.currentThread().getName()
+ ",i=" + i + ",是否为守护线程:"
+ Thread.currentThread().isDaemon());
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程名称:" + Thread.currentThread().getName() + "中断线程 了");
e.printStackTrace();
}
}
}
}