Java并发面试题

1Java中实现多线程有几种方法

  1. 继承Thread类:创建一个新的类并继承Thread类,在类中重写run()方法,run()方法中包含了需要线程执行的代码。

  2. 实现Runnable接口:创建一个类,实现Runnable接口,在该类中实现run()方法,将该类的实例传入Thread类的构造函数中创建一个新线程。

  3. 实现Callable接口:与Runnable接口相似,但是run()方法返回值为void的特点不同,Callable接口的call()方法可以返回执行结果。

  4. 使用Executor框架:Java5之后引入了Executor框架,可以通过它来创建线程池,管理多个线程的执行。

  5. 使用线程池:线程池是一组线程的集合,可以重复使用,避免了频繁地创建和销毁线程,从而提高了系统的性能。

  6. 使用同步器:同步器是用于实现多线程协作的机制,常见的同步器有Semaphore、CountDownLatch、CyclicBarrier、Phaser等。

总的来说,Java中实现多线程的方法比较多,选择哪种方法取决于具体的应用场景和需求。

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

  1. 等待线程自行完成:如果线程是在执行完毕后自行停止的,则可以等待线程完成。如果线程是通过循环或其他方式无限循环运行的,可以通过修改条件来终止循环,让线程自然地退出。

  2. 使用标志位:在线程中设置一个标志位,当需要停止线程时,将标志位设置为True,并在线程中检查标志位的值。如果标志位为True,则线程将退出。需要注意的是,必须使用同步机制确保标志位的可见性和正确性。

  3. 使用interrupt方法中断线程可以使用Thread.interrupt()方法来中断一个正在运行的线程。该方法会向线程发送一个中断信号,如果线程正在阻塞或等待,则会抛出一个InterruptedException异常。可以在异常处理程序中执行清理操作,并使用return语句退出线程。下面是一个简单的示例:
public class MyThread extends Thread {
    public void run() {
        try {
            while (!Thread.interrupted()) {
                // 线程执行的代码
            }
        } catch (InterruptedException e) {
            // 中断异常处理
            // 执行清理操作
            return; // 退出线程
        }
    }
}

// 中断线程
MyThread thread = new MyThread();
thread.start();
thread.interrupt();

3、notify()和notifyAll()有什么区别? 

  1. notify()方法只会随机唤醒一个正在等待该对象锁的线程。如果有多个线程在等待该对象锁,则只有一个线程会被唤醒。

  2. notifyAll()方法会唤醒所有正在等待该对象锁的线程。如果有多个线程在等待该对象锁,则所有线程都会被唤醒。

因此,notify()方法的使用场景通常是在多个线程之间存在竞争关系,只有一个线程需要被唤醒而notifyAll()方法通常用于多个线程之间协作完成某个任务的场景,需要唤醒所有等待的线程来协同工作。notify可能会导致死锁,而notifyAll则不会

需要注意的是,notify()和notifyAll()方法都必须在同步块中调用,且只有在持有该对象锁时才能调用。否则会抛出IllegalMonitorStateException异常。

 4、sleep()和wait() 有什么区别?

sleep()和wait()方法都可以暂停线程的执行,但是它们的使用场景和作用是不同的。主要的区别如下:

  1. wait()方法是Object类的方法,而sleep()方法是Thread类的方法。

  2. wait()方法释放线程持有的锁,而sleep()方法不会释放锁。

  3. wait()方法需要被唤醒后才能继续执行,而sleep()方法在指定时间后会自动继续执行。

  4. wait()方法必须在同步块中调用,而sleep()方法可以在任意地方调用。

由于wait()方法会释放锁,通常用于多个线程之间的协作例如生产者消费者模型中的等待和通知。而sleep()方法通常用于控制线程的执行时间,如线程需要等待一段时间后再执行下一步操作。

需要注意的是,调用wait()方法或sleep()方法都会抛出InterruptedException异常。如果线程在等待过程中被中断,会抛出该异常并清除中断标志。可以在catch块中进行异常处理,并执行适当的清理操作。

5、volatile 是什么?可以保证有序性吗? 

volatile是Java中的一种关键字,用于修饰变量。volatile关键字的作用是强制线程从主内存中读取变量的值,而不是使用线程本地的缓存值。

1.volatile可以保证可见性,即当一个线程修改了volatile变量的值后,其他线程可以立即看到这个修改后的值。这是因为volatile变量的值会被立即写入主内存,并且当其他线程读取该变量时,会从主内存中读取最新的值。

2. 禁止进行指令重排序 用 volatile 修饰共享变量会在读、写共享变量时加入不同的屏障,阻止其他读写操作越过屏障,从而达到 阻止重排序的效果。 
* **volatile 变量写**加的屏障是阻止上方其它写操作越过屏障排到 **volatile 变量写**之下
  * **volatile 变量读**加的屏障是阻止下方其它读操作越过屏障排到 **volatile 变量读**之上

  * volatile 读写加入的屏障只能防止同一线程内的指令重排

3.* 起因:多线程下,不同线程的**指令发生了交错**导致的共享变量的读写混乱
* 解决:用悲观锁或乐观锁解决,volatile 并不能解决原子性 

需要注意的是,虽然volatile可以保证可见性,但是它并不能保证线程安全。如果多个线程同时对一个volatile变量进行写操作,就有可能会出现竞态条件,从而导致数据的不一致性。因此,在涉及到多线程并发访问的情况下,需要考虑线程安全问题。 

 6、Thread 类中的start() 和 run() 方法有什么区别?

在Thread类中,start()和run()方法是用来启动线程的两种方式,它们的作用是不同的。

start()方法是启动一个新线程,并让这个新线程执行run()方法中的代码。start()方法会在新线程中启动一个独立的调用栈,这个新线程会在start()方法调用后立即返回,执行run()方法的任务则会在新线程中异步执行。

run()方法是线程的执行体,其中包含了线程所要执行的任务代码。当直接调用run()方法时,会在当前线程中执行run()方法中的代码,而不会创建新的线程。这种方式虽然可以执行run()方法中的任务,但不会启动一个新线程,因此不会体现出多线程的并发特性。

7、为什么wait, notify 和 notifyAll这些方法不在thread类里面? 

wait(), notify()和notifyAll()是Object类中的方法,而不是Thread类中的方法。这是因为这些方法涉及到Java中的对象锁机制,而不仅仅是线程。

当一个线程需要进入一个同步代码块时,它必须先获得锁。如果锁已经被其他线程持有,那么该线程将会被阻塞,直到锁被释放。wait()方法使得线程释放该对象锁,并且暂停该线程的执行,直到另一个线程发出notify()或notifyAll()信号。

明显的原因是 JAVA 提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需 要等待某些锁那么调用对象中的wait() 方法就有意义了。如果 wait() 方法定义在 Thread 类中,线程正在 等待的是哪个锁就不明显了。简单的说,由于wait notify notifyAll 都是锁级别的操作,所以把他们 定义在Object 类中因为锁属于对象。

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

在Java中,wait()和notify()是Object类中定义的两个方法,用于实现线程的同步和协作。

wait()方法可以使一个线程处于等待状态,直到另一个线程调用notify()或notifyAll()方法来唤醒它。

notify()方法用于唤醒处于等待状态的线程。

这两个方法必须在同步块中使用,这是因为它们需要获取对象的监视器锁(也称为对象锁),而同步块是获取对象锁的唯一途径

如果在非同步块中调用wait()或notify()方法,会抛出IllegalMonitorStateException异常,因为没有获取对象锁。

在同步块中使用wait()和notify()方法可以确保线程安全,避免多个线程同时访问共享资源造成数据不一致的问题。同时,同步块还可以防止因为线程执行顺序不确定而导致的死锁问题。

9、Java中interrupted 和 isInterruptedd方法的区别? 

在Java中,interrupted()和isInterrupted()方法都是用于处理线程中断的方法,但它们之间有一些不同。

interrupted()方法是一个静态方法,用于检查当前线程是否被中断,并且会清除中断状态。当调用interrupted()方法时,它会检查当前线程是否被中断,如果线程被中断,则返回true,并且会将中断状态重置为false。

isInterrupted()方法是一个实例方法,用于检查线程对象是否被中断,但是它不会清除中断状态。当调用isInterrupted()方法时,它会检查线程对象的中断状态,如果线程被中断,则返回true,否则返回false。同时,isInterrupted()方法不会改变线程的中断状态。

总的来说,interrupted()方法更适用于在多线程中处理中断状态,因为它可以清除中断状态,避免中断状态对其他线程的影响。而isInterrupted()方法更适用于判断线程的中断状态,不会改变线程的中断状态,可以用于判断线程是否应该继续执行。

10、Java中synchronized 和 ReentrantLock 有什么不同? 

synchronizedReentrantLock都是Java中的锁机制,都可以用来保证同一时间只有一个线程访问共享资源。但它们之间也有一些不同之处,下面是一些比较重要的区别:

  1. synchronized是Java内置的关键字,而ReentrantLock是一个类,是通过Java API实现的。

  2. synchronized关键字只有一种使用方式,即加锁代码块或方法。ReentrantLock提供了更丰富的API,可以实现更灵活的加锁方式,如可重入锁、公平锁、可中断锁、定时锁等。

  3. synchronized关键字在释放锁时,会自动释放掉它所占有的所有锁。而ReentrantLock则需要手动调用unlock()方法来释放锁,如果没有正确释放锁,就可能会导致死锁。

  4. ReentrantLock相比synchronized关键字,在性能上可能有一些优势,但具体优劣取决于具体的使用场景和实现方式。

综上所述,synchronizedReentrantLock都是Java中非常重要的锁机制,各有其优缺点和适用场景。对于大部分情况,建议使用synchronized关键字,因为它更简单、更易于使用,而对于一些复杂的场景,可以使用ReentrantLock来实现更高级的加锁方式。

11、有三个线程T1,T2,T3,如何保证顺序执行?

  1. 使用join()方法:在每个线程中使用join()方法,让前一个线程等待后一个线程执行完后再执行。示例代码如下:
Thread t1 = new Thread(() -> {
    // 线程 T1 的代码
});
Thread t2 = new Thread(() -> {
    // 线程 T2 的代码
});
Thread t3 = new Thread(() -> {
    // 线程 T3 的代码
});

t1.start();
t1.join();

t2.start();
t2.join();

t3.start();
t3.join();
  1. 使用synchronized关键字:使用synchronized关键字对共享资源进行加锁,保证每个线程执行时,前面的线程已经执行完毕。示例代码如下:
Object lock = new Object();

Thread t1 = new Thread(() -> {
    synchronized (lock) {
        // 线程 T1 的代码
    }
});
Thread t2 = new Thread(() -> {
    synchronized (lock) {
        // 线程 T2 的代码
    }
});
Thread t3 = new Thread(() -> {
    synchronized (lock) {
        // 线程 T3 的代码
    }
});

t1.start();
t2.start();
t3.start();
  1. 使用CountDownLatch类:CountDownLatch是Java并发包提供的一种同步工具,可以使一个或多个线程等待其他线程完成操作后再继续执行。示例代码如下:
CountDownLatch latch1 = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);

Thread t1 = new Thread(() -> {
    // 线程 T1 的代码
    latch1.countDown();
});
Thread t2 = new Thread(() -> {
    try {
        latch1.await();
        // 线程 T2 的代码
        latch2.countDown();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
Thread t3 = new Thread(() -> {
    try {
        latch2.await();
        // 线程 T3 的代码
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});

t1.start();
t2.start();
t3.start();

12、SynchronizedMap和ConcurrentHashMap有什么区别?

SynchronizedMap和ConcurrentHashMap都是用于多线程并发访问的Map实现,但它们有几个重要的区别:

  1. 线程安全性:SynchronizedMap使用同步块来保证线程安全性,而ConcurrentHashMap使用一些更高效的技术来支持高并发环境下的线程安全性。

  2. 性能:由于ConcurrentHashMap采用了一些高效的并发技术,因此在高并发环境下比SynchronizedMap具有更好的性能。

  3. 对迭代器的支持:ConcurrentHashMap支持在Map被修改的同时进行迭代,而SynchronizedMap不支持这种操作,如果在迭代过程中对Map进行修改,将会抛出ConcurrentModificationException异常。

  4. 可伸缩性:ConcurrentHashMap支持更高的并发级别,因此在处理更多的并发访问时,它比SynchronizedMap具有更好的可伸缩性。

综上所述,如果需要在高并发环境下使用Map,建议使用ConcurrentHashMap,如果并发级别较低,对性能要求不是很高,也可以使用SynchronizedMap。

ConcurrentHashMap是Java中线程安全的Map实现,与SynchronizedMap相比,它具有以下优势:

  1. 更高的并发性能:ConcurrentHashMap采用了一种基于分段锁的并发控制机制,在不同的段(Segment)上加锁,多线程可以同时访问不同的段,从而提高了并发性能。而SynchronizedMap则是对整个Map对象进行同步,多线程只能串行访问,性能较低。

  2. 更低的锁竞争:ConcurrentHashMap中的分段锁只对同一段上的并发操作进行同步,不同段之间的操作则不会产生竞争,从而减少了锁的竞争,提高了并发性能。而SynchronizedMap则需要对整个Map对象进行同步,多个线程之间竞争同一把锁,导致锁的竞争激烈。

  3. 更好的扩展性ConcurrentHashMap在初始化时可以指定分段的数量,因此可以根据实际需求灵活调整并发度。而SynchronizedMap则没有这种灵活性,只能对整个Map对象进行同步。

综上所述,ConcurrentHashMap相比于SynchronizedMap具有更高的并发性能、更低的锁竞争以及更好的扩展性,因此在多线程环境下,更推荐使用ConcurrentHashMap。

13、什么是线程安全

线程安全是指当多个线程同时访问一个共享资源时,不会出现数据的不一致或者不可预期的结果。线程安全是多线程编程的重要概念之一,也是一个软件系统的质量要求之一。

在多线程编程中,多个线程同时访问共享资源可能会出现以下问题:

  1. 数据竞争:多个线程同时读写共享数据,导致数据不一致或者数据被破坏。

  2. 死锁:多个线程互相等待对方释放锁,导致线程无法继续执行。

  3. 活锁:多个线程都在等待其它线程执行完毕,导致程序无法正常执行。

  4. 饿死:某个线程由于竞争资源失败而无法执行,一直处于等待状态,无法得到资源,导致线程无法执行。

为了避免这些问题,需要对共享资源进行同步控制,保证多个线程之间对共享资源的访问是互斥的。通常采用的方法包括使用锁、信号量、原子变量等并发控制手段。

一个线程安全的程序应该能够在多线程环境下正常运行,并且能够正确地处理并发访问共享资源的情况,避免出现数据竞争等问题,保证程序的正确性和稳定性。

14、Thread类中的yield方法有什么作用?  

在Java中,Thread类中的yield()方法是用来暂停当前线程并允许其他线程运行的。调用yield()方法将使当前线程进入就绪状态并让出CPU资源,使得其他具有相同或更高优先级的线程有机会运行。但是,调用yield()方法并不保证当前线程会立即停止运行,因为它仅是对调度器的建议,而不是强制性的指令。

yield()方法的作用是让具有相同优先级的线程能够公平竞争CPU资源,从而提高程序的整体执行效率。但是,过多地使用yield()方法可能会导致线程间的竞争变得更加激烈,因此需要谨慎使用。通常情况下,应该使用更高效的线程同步机制来实现线程间的协作和互斥。

15、Java线程池中submit() 和 execute()方法有什么区别? 

在Java中,线程池是一种重要的多线程编程技术,它可以提高程序的性能和效率。线程池中有两个常用的方法submit()和execute(),它们都可以将任务提交到线程池中,但是它们有一些区别。

submit()方法是ThreadPoolExecutor类中定义的方法,它接受一个实现了Callable接口的任务作为参数,并返回一个Future对象,表示异步计算的结果。使用submit()方法可以让线程池执行异步任务,并可以通过返回的Future对象来获取任务的执行结果。

execute()方法也是ThreadPoolExecutor类中定义的方法,它接受一个实现了Runnable接口的任务作为参数,并且没有返回值。使用execute()方法可以让线程池执行一个任务,但是无法获得任务的执行结果。

因此,使用submit()方法可以获取任务的执行结果,而使用execute()方法则只能让线程池执行一个任务,而无法获得任务的执行结果。一般来说,如果需要获取任务的执行结果,则应该使用submit()方法,否则可以使用execute()方法。

 

 16、说一说自己对于 synchronized 关键字的了解

synchronized是Java中的一个关键字,用于实现线程之间的同步,保证多个线程访问共享资源时的互斥性和可见性。

synchronized可以用在方法、代码块、静态方法等不同的场景中,用法略有不同:

  1. 方法级别:synchronized修饰方法时,将整个方法体作为同步代码块,锁住的是当前对象(this)或指定的对象,同一时刻只有一个线程可以执行该方法。

  2. 代码块级别:synchronized修饰代码块时,需要指定锁住的对象,只有持有该对象锁的线程才能进入同步代码块执行。

  3. 静态方法级别:synchronized修饰静态方法时,锁住的是该类的Class对象,同一时刻只有一个线程可以执行该静态方法。

synchronized的底层实现基于Java对象头中的标记字段,当一个线程尝试获取锁时,如果锁已经被占用,那么该线程就会被阻塞,直到锁被释放。在多线程场景中,使用synchronized可以避免线程之间的竞争,保证数据的一致性和正确性。

需要注意的是,synchronized虽然可以实现线程之间的同步,但是也会影响程序的性能,因为每个线程在执行synchronized代码块时都会进行锁的竞争,这会带来额外的开销。因此,在使用synchronized时,需要根据实际情况进行合理的使用和优化,以避免影响程序性能。

17、说说自己是怎么使用 synchronized 关键字,在项目中用到了 吗synchronized关键字最主要的三种使用方式:

修饰实例方法 : 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法 : 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例 对象,是类成员( static 表明这是该类的一个静态资源,不管 new 了多少个对象,只有一份)。所以如 果一个线程A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象, 因为访问静态 synchronized 方法占用的锁 是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
修饰代码块 : 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
总结 synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上 锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因 为JVM 中,字符串常量池具有缓存功能!

 

18、什么是线程安全?Vector是一个线程安全类吗? 

 

线程安全是指在多线程并发访问时,对共享的数据进行操作时,不会出现数据不一致或者数据冲突的问题。换句话说,线程安全的代码能够保证在多线程并发访问时,任何一个线程都可以正确地处理共享的数据,而不会干扰其他线程的数据操作。

Vector是一个线程安全的类。Vector是一个基于数组实现的动态数组,它实现了List接口,而且所有的方法都是synchronized的,所以它的所有操作都是线程安全的。这意味着多个线程可以同时对Vector进行读取、写入和修改操作,而不会出现数据冲突或数据不一致的问题。因此,Vector是一个适用于多线程环境下的数据结构,可以确保数据的线程安全性。不过需要注意的是,由于Vector的所有方法都是同步的,所以在高并发的情况下可能会影响性能,所以在选择使用数据结构时,需要根据实际情况来选择最适合的数据结构。

20、常用的线程池有哪些?

Java中常用的线程池有以下几种:

  1. FixedThreadPool:固定线程数的线程池。在创建线程池时指定线程数,当有任务提交时,线程池中的线程就会执行任务。如果所有线程都在处理任务,新的任务就会被暂时放置在任务队列中,直到有线程空闲出来。

  2. CachedThreadPool:缓存线程数的线程池。线程池中的线程数会根据任务的数量自动调整,当有任务提交时,如果有空闲的线程可以处理任务,就会使用空闲的线程;如果没有空闲线程,就会创建新的线程来处理任务。当线程空闲一段时间后,如果没有新的任务提交,就会自动销毁线程。

  3. SingleThreadPool:单线程的线程池。只有一个线程的线程池,用来处理顺序执行的任务,保证所有任务按照提交的顺序依次执行。如果该线程由于异常退出,会重新创建一个新的线程来代替原来的线程。

  4. ScheduledThreadPool:定时任务的线程池。可以用来执行定时任务,可以设置延迟时间或者固定的时间间隔执行任务。

除了以上几种常见的线程池,Java中还提供了WorkStealingPool和ForkJoinPool等线程池,用来处理特定类型的任务。在使用线程池时需要根据任务的类型、数量和执行时的要求来选择最适合的线程池。

21、简述一下你对线程池的理解

线程池是一种重要的多线程技术,它是一组线程的集合,其中的线程可以重复使用,减少了线程的创建和销毁的开销,提高了多线程程序的效率。线程池维护着一个线程队列,等待着新的任务到来,一旦有任务到来,就会从线程队列中取出一个线程,将任务分配给它执行。

线程池可以有效地管理和控制线程的数量和生命周期,避免了因为线程过多而导致的系统性能下降和资源耗尽的问题同时也避免了因为线程创建和销毁的频繁操作而带来的系统开销。在实际应用中,线程池可以提高程序的响应速度和吞吐量,同时也可以避免因为线程数量不当而导致的系统崩溃或者资源不足的问题。

在使用线程池时,需要注意线程的数量和线程池的类型,不同类型的线程池适用于不同类型的任务。需要合理地设置线程池的参数,包括核心线程数、最大线程数、任务队列的容量等,以保证线程池的最优性能。另外,在处理异常和资源释放等方面也需要注意,避免出现线程泄漏或者资源浪费等问题。

 

 22、Java程序是如何执行的

 

Java程序执行的过程可以分为编译和运行两个阶段:

  1. 编译阶段:Java源代码通过Java编译器(javac)编译成字节码文件(.class文件),字节码文件是一种平台中立的中间代码,可以在任何支持Java虚拟机(JVM)的平台上运行。Java编译器会对Java源代码进行语法分析、语义检查、字节码生成等一系列操作,最终生成可供JVM解释执行的字节码文件。

  2. 运行阶段:Java字节码文件通过JVM解释执行,JVM是一个虚拟的计算机,它能够在任何支持Java虚拟机的平台上解释执行Java字节码文件。JVM会对字节码文件进行解析、验证、编译和执行等一系列操作,最终将Java程序转换为计算机能够理解和执行的机器码指令,执行Java程序的结果会输出到控制台、文件或者图形界面等输出设备上。

在运行Java程序时,JVM会先加载并初始化程序的入口类,然后根据程序的逻辑结构依次执行各个方法和语句,将程序的执行结果输出到控制台或者其他设备上。在程序执行过程中,JVM会自动管理内存、线程、异常处理等一系列复杂的操作,保证Java程序的正确性和稳定性。

25、 讲一下 synchronized 关键字的底层原理

synchronized 是 Java 中用于实现线程安全的关键字,它可以用于修饰方法或者代码块。底层原理与对象锁息息相关,具体如下:

  1. 对象锁:Java 中的每个对象都会有一个锁(也称为监视器锁或互斥锁),线程在执行 synchronized 修饰的方法或代码块时,需要先获取该对象的锁,如果该锁已被其他线程占用,那么该线程将进入阻塞状态,直到其他线程释放该锁。

  2. 修饰方法:当一个方法被 synchronized 修饰时,该方法的锁就是当前对象,即 this 关键字指向的对象。多个线程同时访问该方法时,只有一个线程可以获取到该对象的锁,其他线程将进入阻塞状态,直到获取到该对象的锁。

  3. 修饰代码块:当一个代码块被 synchronized 修饰时,需要指定一个对象作为锁。多个线程访问该代码块时,只有一个线程可以获取到该对象的锁,其他线程将进入阻塞状态,直到获取到该对象的锁。

  4. 重入性:当一个线程获取到某个对象的锁时,它可以再次获取该对象的锁而不会被阻塞,这就是重入性。

  5. 内存可见性:使用 synchronized 关键字还可以保证内存可见性,即一个线程对共享变量的修改对其他线程可见。

总之,synchronized 关键字底层的实现原理是基于对象锁的,通过获取对象的锁来实现线程的同步。由于 synchronized 关键字是 Java 中实现线程安全的一种简单易用的方式,所以在实际开发中被广泛使用。

 

26、 为什么要用线程池?

使用线程池有以下几个优点:

降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系
统的稳定性,使用线程池可以进行统一的分配,调优和监控。

 

27、 实现Runnable接口和Callable接口的区别 

实现Runnable接口和Callable接口的主要区别在于以下两点:

  1. 返回值类型不同:Runnable接口的run()方法没有返回值,而Callable接口的call()方法可以返回一个泛型类型的结果。

  2. 抛出异常不同:Runnable接口的run()方法不能抛出已检查异常,而Callable接口的call()方法可以抛出已检查异常,但需要在方法声明时进行声明或者捕获异常。

另外,Callable接口在使用时需要通过Future对象来获取结果,而Runnable接口没有这个功能。Future对象可以用来控制任务的状态,获取任务的结果等。

需要注意的是,在Java 5及以上的版本中,可以使用线程池的submit方法来提交Callable任务,从而获得返回值。而Runnable任务没有这个功能,因为它没有返回值。

综上所述,实现Runnable接口和Callable接口的主要区别在于返回值类型和抛出异常的不同,以及Callable需要使用Future对象来获取结果的功能

28、 执行execute()方法和submit()方法的区别是什么呢? 

1) execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
2) submit() 方法用于提交需要返回值的任务。线程池会返回一个 future 类型的对象,通过这个 future 对象可以判断任务是否执行成功 ,并且可以通过 future get() 方法来获取返回值, get() 方法会阻塞当前 线程直到任务完成,而使用 get long timeout TimeUnit unit 方法则会阻塞当前线程一段时间 后立即返回,这时候有可能任务没有执行完。

29、 如何创建线程池 

 

《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过
ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽 的风险**
Executors 返回线程池对象的弊端如下:
FixedThreadPool SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM
CachedThreadPool ScheduledThreadPool : 允许创建的线程数量为Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM
方式一:通过构造方法实现

 

方式二:通过 Executor 框架的工具类 Executors 来实现 我们可以创建三种类型的
ThreadPoolExecutor
FixedThreadPool : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。
当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在
一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
SingleThreadExecutor 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线
程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
CachedThreadPool 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数
量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新
的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复
用。
对应 Executors 工具类中的方法如图所示:

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值