0
点赞
收藏
分享

微信扫一扫

备战秋招------多线程

非凡兔 2022-02-18 阅读 86

什么是线程什么是进程在上一篇已经提到,不在赘述

1.请简要描述线程与进程的关系,区别及优缺点?

从JVM的角度来理解

JVM就是一个进程,而里面的main就是主线程。可以从图中看出,一个进程包含多个线程,线程共享区域有堆和方法区(1.8以后叫元空间),线程私有区域有虚拟机栈,本地方法栈,程序计数器。进程和线程最大的区别就是进程之间相互独立不会影响,可以共享内存资源,而线程之间有自己共享区和私有区域,会相互影响,但是线程的优点就是开销小,线程就是轻量级的进程。

2.线程的五种状态(六种)
操作系统层面

1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread;

2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就.绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
⒉同步阻塞―线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞―通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时. join()等待线程终止或者超时.或者I/O处理完毕时,线程重新转入就绪状态。
5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

Java层面

1)NEW

2)RUNNABLE(运行状态,可运行状态,阻塞状态)

3)BLOCKED

4)WAITING

5)TIMED_WATING

6)TERMINATED

 线程创建之后它将处于 NEW(新建) 状态,调⽤ start() ⽅法后开始运⾏,线程这时候处于 READY(可运行) 状态。可运⾏状态的线程获得了 cpu 时间片(timeslice)后就处于 RUNNING(运行) 状态。当线程执⾏ wait() ⽅法之后,线程进⼊ WAITING(等待)状态。进⼊等待状态的线程需要依靠 其他线程的通知才能够返回到运⾏状态,而TIME_WAITING(超时等待) 状态相当于在等待状态 的基础上增加了超时限制,比如通过 sleep(long millis)⽅法或 wait(long millis)方法可以将 Java 线程置于 TIMED_WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状 态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进⼊到 BLOCKED(阻塞) 状态。线程在执⾏行Runnable 的 run() 方法之后将会进⼊到 TERMINATED(终止) 状态。

我们可以看到两种情况下:从操作系统来看,调用wait(),sleep(),join(),IO阻塞会进入阻塞状态;从Java层面看会进入WAITING或者TIMED_WAITING状态

3.什么是上下文切换?

其实就是一个CPU只能操作一个线程,每个线程获得操作时会获取时间片,当正在操作的线程时间片用完了,同时另外一个线程也获得了时间片,这时候CPU就会进行调度给另外一个线程使用。在切换之前会保存之前线程的状态,保证下次能正常切换到该状态。保存再到加载的过程就是上下文切换。
 

4.什么是死锁?死锁产生的条件?怎么避免?

死锁:线程A已经获取资源2,线程B也获取资源1,这时候线程A和线程B分别去获取资源1和资源2,由于资源1和资源2都没有被释放,造成线程A和线程B相互等待的状态,这个现象就叫做死锁。

死锁产生的条件是什么呢?

1.互斥条件:该资源任意时刻只能被一个线程占用

2.请求和保持条件:线程A请求资源1造成阻塞,但是资源2保持不释放

3.不剥夺条件:资源1或者资源2不能被剥夺,只能由所占用的线程释放

4.循环等待条件:形成头尾相连的等待条件

怎么避免?

破坏其中一个条件就行

1.破坏互斥条件:这个无法做到,因为使用锁本身就是利用互斥条件

2.破坏请求和保持条件:一次性申请所以资源

3.破坏不剥夺条件:当请求另一个资源造成阻塞时,自动释放所占用的线程资源

4.破坏循环等待条件:按照顺序申请

5.sleep()和wait()区别?

1)最重要的区别:sleep()不会释放锁,使用sleep()之后,会放弃对CPU的使用权,让给其他线程,等到睡眠时间到达,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级;wait()会释放锁,当线程调用wait()方法时该线程会释放锁,进入等待线程池,wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程,唤醒之后重新进入队列竞争锁

2)sleep()方法可以在任何地方调用;而wait()方法只能在同步代码块或同步方法中使用(即使用synchronized关键字修饰的);

3)sleep()方法是Thread类中的静态方法;而wait()方法是Object类中的方法;

6.为什么我们调⽤ start() 方法时会执行 run() 方法,为什么我 们不能直接调⽤用run() 方法?

当我们new一个Thread时,调用start()方法,线程就会进入就绪状态等待CPU调度,当调用run()方法时线程进入运行状态,此时调用的时多线程下的run()方法,而直接调用run()方法其实它只是一个main函数里的普通方法。

7.synchronized 关键字最主要的三种使用方式

1.修饰实例方法

public class SynchronizedDemo1 { 
	public synchronized void method() { 
			System.out.println("synchronized 方法"); 
	} 
}

2.修饰静态方法

public class SSynchronizedDemo2{
	public static synchronized void method(){
		System.out.println("synchronized 静态方法");
	}
}		

3.修饰代码块

public class SynchronizedDemo3 { 
	public void method() { 
		synchronized (this) { 
			System.out.println("synchronized 代码块"); 
		} 
	} 
}

8.哪些地方可以用volatile(单例模式)?具体可参考之前写的volatile

public class SingletonDemo03 {
    private static volatile SingletonDemo03 instance =null;//注意加了volatile!
    private SingletonDemo03(){
        System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo()");
    }
    public static SingletonDemo03 getInstance(){
        //DCL Double Check Lock双端检锁机制
        if(instance==null){
            synchronized(SingletonDemo.class){
                if(instance==null){
                    instance=new SingletonDemo03();
                }
            }
        }
        return instance;
    }
 
    public static void main(String[] args) { 
        //单线程
       // System.out.println(SingletonDemo.getInstance()==SingletonDemo.getInstance());
       // System.out.println(SingletonDemo.getInstance()==SingletonDemo.getInstance());
 
        //多线程
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                SingletonDemo03.getInstance();
            }, String.valueOf(i)).start();
        }
    }
}

instance采⽤ volatile 关键字修饰也是很有必要的, instance=new SingletonDemo03(); 这段代码其实是分为三步执行:

1. 为 instance分配内存空间

2. 初始化 instance

3. 将 instance指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不 会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用getInstance () 后发现 instance不为空,因此返回 instance,但此时 instance还未被初始化。 

举报

相关推荐

0 条评论