面试_java_Java中的线程与多线程

105 阅读6分钟

Java如何实现(多)线程?

segmentfault.com/a/119000003…

  • 继承Thread类,重写run()方法

    • 首先定义一个类来继承 Thread 类,重写 run 方法。
    • 然后创建这个子类对象,并调用 start 方法启动线程。
    public class MyThread extends Thread {
        public void run() {
            System.out.println("thread run...");
        }
    
        public static void main(String[] args) {
            new MyThread().start();
        }
    }
    
  • 实现Runnable接口,重写run()方法

    • 首先定义一个类实现 Runnable 接口,并实现 run 方法。
    • 然后创建 Runnable 实现类对象,并把它作为 target 传入 Thread 的构造函数中
    • 最后调用 start 方法启动线程。
    public class MyThread implements Runnable {
        public void run() {
            System.out.println("thread run...");
        }
    
        public static void main(String[] args) {
            new Thread(new MyThread()).start();
        }
    }
    
  • 实现Callable接口,重写call方法

    • 首先定义一个类实现 Callable 接口,并实现 call 方法。call 方法是带返回值的。
    • 然后通过 FutureTask 的构造方法,把这个 Callable 实现类传进去。
    • 把 FutureTask 作为 Thread 类的 target ,创建 Thread 线程对象。
    • 通过 FutureTask 的 get 方法获取线程的执行结果。
    class MyThread implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            return new Random().nextInt(100);
        }
    }
    
    public class TestFuture {
        public static void main(String[] args) throws Exception {
            FutureTask<Integer> task = new FutureTask<>(new MyThread());
            new Thread(task).start();
            Integer result = task.get();  // 获取线程得执行结果,阻塞式
            System.out.println(result);
        }
    }
    

方法1与方法2相比更推荐后者,因为java只支持单继承,尽量少用extend。但java支持多实现(接口)

方法2与方法3相比,区别是后者有返回值。

什么时候使用多线程?

高并发场景,或者IO需要很长时间的场景没必要一直等它。




线程中start和run的区别

  • run()就和普通的成员方法一样,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码,没有达到多线程的目的。

  • start()方法用来启动新线程,使其进入就绪状态,新线程一旦得到cpu时间片,就开始执行run()方法。调用一次start()就可以自动实现线程切换,真正实现了多线程




Java线程同步和线程调度的相关方法

  1. wait():调用后线程进入无限等待状态,并释放所持对象的锁
  2. sleep():使一个线程进入休眠状态(堵塞状态),带有对象锁,到了时间,继续执行任务。
  3. notify():唤醒一个处于等待状态的线程,至于唤醒哪个,与优先级有关。
  4. notityAll():唤醒所有处于等待状态的线程,但是并不是将对象的锁给所有的线程,而是让它们去竞争,谁先获取到锁,谁先进入就绪状态。



sleep、wait、suspend 、yield之间有什么区别?

  • sleep()是Thread类中的的静态方法,可以使用在任何代码块。线程调用后让出cpu,但不释放锁,进入阻塞状态。到了时间,继续执行任务。

  • wait() 是 Object类中的普通成员方法,必须在同步方法或同步代码块执行。线程调用后让出cpu,同时释放锁,进入阻塞状态。wait有两种形式,一种是包含固定时长参数,另一种不包含固定时长参数。包含固定时长参数时在等待时长超出或者调用notify(或notifyAll)都会使线程恢复;而不包含固定时长参数的只有调用notify(或notifyAll)才会恢复。恢复后要重新申请对象锁,然后继续执行任务。

  • suspend() 使当前线程阻塞,且不会自动恢复。只有调用resume()才会使当前的线程恢复可执行状态。

  • yield() 会让当前线程结束,放弃cpu使用权,重新进入就绪队列争夺cpu

www.nowcoder.com/questionTer… blog.nowcoder.net/n/8d7e62e45…




你是如何调用wait()方法的,使用if还是循环

用循环。处在等待状态的线程可能会收到错误警告或伪唤醒,如果不在循环中检查等待条件,程序可能会在没有满足条件的时候退出。




如何暂停一条线程?

答:两种方式暂停一条线程,一个是采取Thread类的sleep()方法,一个是在同步代码中使用wait()方法.




如何停止一个正在运行的线程?

  1. 使用stop方法终止,但是这个方法已经过期,不被推荐使用。
  2. 使用interrupt方法终止线程
  3. run方法执行结束,正常退出



在一个对象上两个线程可以调用两个不同的同步实例方法么?

答:不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。所以只有执行完该方法释放对象锁后才能执行其它同步方法。




线程同步和线程互斥的区别

线程同步:当一个线程对共享数据进行操作的时候,在没有完成相关操作时,不允许其它的线程来打断它,否则就会破坏数据的完整性,必然会引起错误信息,这就是线程同步。 线程互斥: 而线程互斥是站在共享资源的角度上看问题,例如某个共享资源规定,在某个时刻只能一个线程来访问我,其它线程只能等待,直到占有的资源者释放该资源,线程互斥可以看作是一种特殊的线程同步。 实现线程同步的方法

  1. 同步代码块:sychronized(对象){} 块
  2. 同步方法:sychronized修饰的方法
  3. 使用重入锁实现线程同步:reentrantlock类的锁又互斥功能,Lock lock = new ReentrantLock(); Lock对象的ock和unlock为其加锁

blog.nowcoder.net/n/55a59284f… zhuanlan.zhihu.com/p/346030596




synchronized 和volatile

  • sychronized 的作用是锁定当前变量/对象/代码块,只有当前线程可以访问,其他线程被阻塞。

  • volatile 本质是告诉 jvm 当前变量在寄存器中的变量是不安全的,需要从内存中读取。它有两个作用:保证内存可见性、禁止指令重排序。

二者区别:

  • synchronized 可以作用于变量、方法、对象;volatile 只能作用于变量。

  • synchronized 线程阻塞,volatile 线程不阻塞。




在 Java 程序中怎么保证多线程的运行安全

线程的安全性问题:

  • 原子性问题(线程切换导致):

    • 含义:一个或者多个操作在 CPU 执行的过程中不被中断。
    • 解决办法:JDK Atomic开头的原子类、synchronized、LOCK
  • 可见性问题(缓存导致):

    • 含义:一个线程对共享变量的修改后,另外一个线程能够立刻看到。缓存导致此问题。

    • 解决办法:synchronized、volatile、LOCK

  • 有序性问题(编译优化导致):

    • 含义:程序执行的顺序按照代码的先后顺序执行。编译优化会导致此问题。
    • 解决办法:Happens-Before 规则



ThreadLocal 是什么

我们知道线程同步机制是指多个线程共享同一个变量,而ThreadLocal为每个线程创建一个单独的变量副本,从而保证线程隔离。

ThreadLocal有一个特别重要的静态内部类ThreadLocalMap,该类才是实现线程隔离机制的关键。get()、set()、remove()都是基于该内部类进行操作,ThreadLocalMap用键值对方式存储每个线程变量的副本,key为当前的ThreadLocal对象,value为对应线程的变量副本。