设为首页 加入收藏

TOP

Java并发篇:6个必备的Java并发面试种子题目(一)
2023-08-06 07:50:01 】 浏览:98
Tags:Java

线程创建和生命周期

线程的创建和生命周期涉及到线程的产生、执行和结束过程。让我们继续深入探索这个主题:

线程的创建方式有多种,你可以选择适合你场景的方式:

继承Thread类: 创建一个类,继承自Thread类,并重写run()方法。通过实例化这个类的对象,并调用start()方法,系统会自动调用run()方法执行线程逻辑。

public class MyThread extends Thread {
    public void run() {
        // 线程逻辑代码
    }
}
// 创建并启动线程
MyThread thread = new MyThread();
thread.start();

实现Runnable接口: 创建一个类,实现Runnable接口,并实现run()方法。通过将实现了Runnable接口的对象作为参数传递给Thread类的构造函数,然后调用start()方法启动线程。

public class MyRunnable implements Runnable {
    public void run() {
        // 线程逻辑代码
    }
}
// 创建并启动线程
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();

实现Callable接口: 创建一个类,实现Callable接口,并实现call()方法。通过创建一个FutureTask对象,将Callable对象作为参数传递给FutureTask构造函数,然后将FutureTask对象传递给Thread类的构造函数,最后调用start()方法启动线程。

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        // 线程逻辑代码
        return 1; 
    }
}
// 创建并启动线程
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();

通过线程池创建线程: 使用Java的线程池ExecutorService来管理线程的生命周期。通过提交Runnable或Callable任务给线程池,线程池会负责创建、执行和终止线程。

ExecutorService executorService = Executors.newFixedThreadPool(5);
executorService.execute(new Runnable() {
    public void run() {
        // 线程逻辑代码
    }
});
executorService.shutdown();

线程的生命周期经历以下几个状态:

  • 创建状态: 通过实例化Thread对象或者线程池来创建线程。此时线程处于新建状态。
  • 就绪状态: 线程被创建后,调用start()方法使其进入就绪状态。在就绪状态下,线程等待系统分配执行的时间片。
  • 运行状态: 一旦线程获取到CPU的时间片,就进入运行状态,执行run()方法中的线程逻辑。
  • 阻塞状态(Blocked/Waiting/Sleeping): 在某些情况下,线程需要暂时放弃CPU的执行权,进入阻塞状态。阻塞状态可以分为多种情况:
  • 中断状态: 可以通过调用线程的interrupt()方法将线程从运行状态转移到中断状态。线程可以检查自身是否被中断,并根据需要作出适当的处理。
  • 终止状态: 线程执行完run()方法中的逻辑或者通过调用stop()方法被终止后,线程进入终止状态。终止的线程不能再次启动。

理解线程的创建和生命周期对于处理并发编程非常重要。通过选择合适的创建方式和正确地管理线程的生命周期,可以确保线程安全、高效地运行,从而优化程序性能。

深入剖析synchronized

synchronized关键字在Java中用于实现线程安全的代码块,在其背后使用JVM底层内置的锁机制。synchronized的设计考虑了各种并发情况,因此具有以下优点:

  • 优点: 由于官方对synchronized进行升级优化,如当前锁升级机制,因此它具有不断改进的潜力。JVM会进行锁的升级优化,以提高并发性能。
    然而,synchronized也有一些缺点:
  • 缺点: 如果使用不当,可能会导致锁粒度过大或锁失效的问题。此外,synchronized只适用于单机情况,对于分布式集群环境的锁机制不适用。

synchronized的锁机制包括以下几个阶段的升级过程:

  • 无锁状态: 初始状态为无锁状态,多个线程可以同时访问临界区。
  • 偏向锁: 当只有一个线程访问临界区时,JVM会将锁升级为偏向锁,以提高性能。在偏向锁状态下,偏向线程可以直接获取锁,无需竞争。
  • (自旋)轻量级锁: 当多个线程竞争同一个锁时,偏向锁会升级为轻量级锁。在轻量级锁状态下,线程会自旋一定次数,尝试获取锁,而不是直接阻塞。
  • 重量级锁: 当自旋次数超过阈值或者存在多个线程竞争同一个锁时,轻量级锁会升级为重量级锁。重量级锁使用了传统的互斥量机制,需要进行阻塞和唤醒操作。

需要注意的是,如果在轻量级锁状态下,有线程获取对象的HashCode时,会直接升级为重量级锁。这是因为锁升级过程中使用的mark头将HashCode部分隐去,以确保锁升级过程的正确性。

底层实现中,synchronized使用了monitor enter和monitor exit指令来进行进入锁和退出锁的同步操作。对于用户来说,这些操作是不可见的。synchronized锁的等待队列存储在对象的waitset属性中,用于线程的等待和唤醒操作。

双重检查单例模式解析

示例代码:

public class Singleton {
    private static volatile Singleton instance;

private Singleton() {
    // 私有构造方法
}

public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();
            }
        }
    }
    return instance;
}
}

为什么需要使用volatile: 虽然synchronized关键字可以确保线程安全,但是如果没有volatile修饰,可能会发生指令重排的问题。volatile关键字的主要作用是防止指令重排,保证可见性和有序性。尽
管在实际工作中很少遇到指令重排导致的问题,但是理论上存在这种可能性,因此使用volatile修饰变量可以避免出现意外情况。

指令重排原因及影响: 指令重排是为了优化程序的执行速度,由于CPU的工作速度远大于内存的工作速度,为了充分利用CPU资源,处理器会对指令进行重新排序。例如在创建一个对象的过程中,通常被拆分为三个步骤:1)申请空间并初始化,2)赋值,3)建立地址链接关系。如果没有考虑逃逸分析,

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇我真的不想再用mybatis和其衍生框.. 下一篇Spring Boot 实现文件断点下载,..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目