java 线程

2021-03-28  本文已影响0人  向梦而来

Java 线程

简述线程、进程、程序的基本概念?

程序

程序,是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。

进程

进程,是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。

线程

线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。每个线程默认使用 1024KB 的内存,所以一个 Java 进程是无法开启大量线程的。

三者之间的关系

线程有什么优缺点?

1)好处

2)坏处

你了解守护线程吗?它和非守护线程有什么区别?

Java 中的线程分为两种:守护线程(Daemon)和用户线程(User)。

唯一的区别是:程序运行完毕,JVM 会等待非守护线程完成后关闭,但是 JVM 不会等待守护线程。比如,JVM 的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线程时,Java 虚拟机会自动离开。

什么是多线程上下文切换?

多线程会共同使用一组计算机上的 CPU ,而线程数大于给程序分配的 CPU 数量时,为了让各个线程都有执行的机会,就需要轮转使用 CPU 。

不同的线程切换使用 CPU 发生的切换数据等,就是上下文切换。

Java 中用到的线程调度算法是什么?

假设计算机只有一个 CPU ,则在任意时刻只能执行一条机器指令,每个线程只有获得 CPU 的使用权才能执行指令。

有两种调度模型:分时调度模型和抢占式调度模型。

什么是线程饥饿?

饥饿,一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。

Java 中导致饥饿的原因:

线程的生命周期?

线程一共有五个状态,分别如下:

整体如下图所示:

Thread 的线程状态

如何结束一个一直运行的线程?

一般来说,有两种方式:

所以,方式一和方式二,并不是冲突的两种方式,而是可能根据实际场景下,进行结合。

一个线程如果出现了运行时异常会怎么样?

如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放。

创建线程的方式及实现?

Java 中创建线程主要有三种方式:

具体的每种方式的代码实现,可以看看 《Java创建线程的四种方式》

start 和 run 方法有什么区别?

如何使用 wait + notify 实现通知机制?

在 Java 发展史上,曾经使用 suspend、resume 方法对于线程进行阻塞唤醒,但随之出现很多问题,比较典型的还是死锁问题。

解决方案可以使用以对象为目标的阻塞,即利用 Object 类的 wait 和 notify方法实现线程阻塞。

具体的实现,看看 《Wait / Notify通知机制解析》 文章。

Thread类的 sleep 方法和对象的 wait 方法都可以让线程暂停执行,它们有什么区别?

notify 和 notifyAll 有什么区别?

当一个线程进入 wait 之后,就必须等其他线程 notify/notifyAll 。

为什么 wait, notify 和 notifyAll 这三方法不在 Thread 类里面?

一个很明显的原因是 Java 提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。

由于 wait,notify 和 notifyAll 方法都是锁级别的操作,所以把它们定义在 Object 类中,因为锁属于对象。

为什么 wait 和 notify 方法要在同步块中调用?

为什么你应该在循环中检查等待条件?

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。

所以,我们不能写 if (condition) 而应该是 while (condition) ,特别是 CAS 竞争的时候。示例代码如下:

synchronized (obj) {
     while (condition does not hold) {
       obj.wait(); // (Releases lock, and reacquires on wakeup)
     }
 ... // Perform action appropriate to condition
}

sleep、join、yield 方法有什么区别?

1)sleep 方法
在指定的毫秒数内,让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有synchronized 同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常。
sleep 方法,可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。

2)yield 方法
yield 方法和 sleep 方法类似,也不会释放“锁标志”,区别在于:

3)join 方法

/**
 * 存在两个线程:主线程和线程t
   Join,这里所说的调用方就是主线程,主线程调用线程t的Join方法,导致主线程阻塞,
      直到t线程执行完毕,才返回到主线程中。
      简单理解,在主线程中调用t.Join(),也就是在主线程中加入了t线程的代码,
      必须让t线程执行完毕之后,主线程(调用方)才能正常执行。
 *
 */
public class JoinThreadTest {

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(){
            @Override
            public void run() {
                for(int i =0;i<100;i++){
                    System.out.println("a:" + i);
                }
            }
        };
        t.start();
        
        bmethod();
        // 注意观察执行顺序
        t.join();
        cmethod();

    }
    
    public static void bmethod(){
        System.out.println(":bmethod:");
    }
    
    public static void cmethod(){
        System.out.println(":cmethod:");
    }

}

线程的 sleep 方法和 yield 方法有什么区别?

为什么 Thread 类的 sleep 和 yield 方法是静态的?

Thread 类的 sleep 和 yield 方法,将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。

sleep(0) 有什么用途?

Thread#sleep(0) 方法,并非是真的要线程挂起 0 毫秒,意义在于这次调用 Thread#sleep(0) 方法,把当前线程确实的被冻结了一下,让其他线程有机会优先执行。Thread#sleep(0) 方法,是你的线程暂时放弃 CPU ,也就是释放一些未用的时间片给其他线程或进程使用,就相当于一个让位动作

你如何确保 main 方法所在的线程是 Java 程序最后结束的线程?
可以使用 Thread 类的 #join() 方法,来确保所有程序创建的线程在 main 方法退出前结束。

interrupted 和 isInterrupted 方法的区别?

1)interrupt 方法

Thread#interrupt() 方法,用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。

注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出 InterruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。

2)interrupted

Thread#interrupted() 静态方法,查询当前线程的中断状态,并且清除原状态。如果一个线程被中断了,第一次调用 #interrupted() 方法则返回 true ,第二次和后面的就返回 false 了。

3)isInterrupted

Thread#isInterrupted() 方法,查询指定线程的中断状态,不会清除原状态。

什么叫线程安全?

线程安全,是编程中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。

Servlet 是线程安全吗?
Servlet 不是线程安全的,Servlet 是单实例多线程的,当多个线程同时访问同一个方法,是不能保证共享变量的线程安全性的。

SpringMVC 是线程安全吗?
不是的,和 Servlet 类似的处理流程。

单例模式的线程安全性?
首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法,我总结一下:

//当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调
//用,单例类的唯一实例将被创建。如果使用饿汉式单例来实现负载均衡器
//LoadBalancer类的设计,则不会出现创建多个单例对象的情况,可确保单例对象的唯一性。
class EagerSingleton {   
    private static final EagerSingleton instance = new EagerSingleton();   
    private EagerSingleton() { }   

    public static EagerSingleton getInstance() {  
        return instance;   
    }     
}
//懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例
//化,这种技术又称为延迟加载(Lazy Load)技术,即需要的时候再加载实例,为
//了避免多个线程同时调用getInstance()方法,我们可以使用关键字synchronized
class LazySingleton {   
    private static LazySingleton instance = null;   

    private LazySingleton() { }   

    synchronized public static LazySingleton getInstance() {   
        if (instance == null) {  
            instance = new LazySingleton();   
        }  
        return instance;   
    }  
}
class LazySingleton {   
    private volatile static LazySingleton instance = null;   

    private LazySingleton() { }   

    public static LazySingleton getInstance() {   
        //第一重判断  
        if (instance == null) {  
            //锁定代码块  
            synchronized (LazySingleton.class) {  
                //第二重判断  
                if (instance == null) {  
                    instance = new LazySingleton(); //创建单例实例  
                }  
            }  
        }  
        return instance;   
    }  
}

多线程同步和互斥有几种实现方法,都是什么?

1)线程同步

线程同步,是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

线程间的同步方法,大体可分为两类:用户模式和内核模式。顾名思义:

2)线程互斥

线程互斥,是指对于共享的进程系统资源,在各单个线程访问时的排它性。

怎么检测一个线程是否拥有锁?

调用 Thread#holdsLock(Object obj) 静态方法,它返回 true 如果当且仅当当前线程拥有某个具体对象的锁。代码如下:

10 个线程和 2 个线程的同步代码,哪个更容易写?

从写代码的角度来说,两者的复杂度是相同的,因为同步代码与线程数量是相互独立的。

但是同步策略的选择依赖于线程的数量,因为越多的线程意味着更大的竞争,所以你需要利用同步技术,如锁分离,这要求更复杂的代码和专业知识。

在多线程环境下,SimpleDateFormat 是线程安全的吗?

不是,非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如将 SimpleDateFormat 限制在 ThreadLocal 中

如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐 joda-time 库。

什么是Java Timer 类?

java.util.Timer ,是一个工具类,可以用于安排一个线程在未来的某个特定时间执行。Timer 类可以用安排一次性任务或者周期任务。

java.util.TimerTask ,是一个实现了 Runnable 接口的抽象类,我们需要去继承这个类来创建我们自己的定时任务并使用 Timer 去安排它的执行。

目前有开源的 Qurtz 可以用来创建定时任务。

你有哪些多线程开发良好的实践?

并发编程和并行编程有什么区别?

并发(Concurrency)和并行(Parallellism)是:

同步和异步有何异同,在什么情况下分别使用他们?

如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。

当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

上一篇 下一篇

猜你喜欢

热点阅读