博客
关于我
Java并发编程核心面试题小总结
阅读量:120 次
发布时间:2019-02-26

本文共 7401 字,大约阅读时间需要 24 分钟。

Java并发编程常见问题解析

近期结合自己所学的多线程JUC相关知识点,对Java并发编程几个常见问题进行了总结和思考。以下是对几大问题的详细解答。

一、Java如何开启线程?怎么保证线程安全?

Java如何开启线程?

Java开启线程的方式主要有以下四种:

  • 继承Thread类,重写run方法:通过继承 Thread 类并重写 run 方法,可以创建线程。
  • 实现Runnable接口,实现run方法:通过实现 Runnable 接口并提供 run 方法,可以创建线程。这种方式比继承 Thread 类更具有扩展性。
  • 实现Callable接口,通过FutureTask创建线程:这种方式支持返回值,适用于需要获取线程执行结果的场景。
  • 使用线程池开启线程:线程池是对上述方法的一种优化,支持线程复用、控制最大并发数以及线程管理。
  • 线程安全的保证方法

    在Java中,线程安全的常用方法包括:

  • 使用synchronized关键字:通过JVM提供的锁机制,确保共享资源的访问同步。
  • 使用JUC提供的Lock(如ReentrantLock、ReadWriteLock):这些锁比synchronized更细粒度,支持多级锁和公平锁。
  • 二、synchronized和Volatile有什么区别?volatile能不能保证线程安全?

    synchronized和Volatile的区别
    • synchronized:是JVM层面的关键字,用于加锁,保证线程进入同一区域的同步执行。
    • Volatile:是JVM层面的可见性关键字,主要用于保证共享变量的可见性,适用于多线程读/写共享变量的场景。
    Volatile的性质
    • 可见性:确保其他线程能够及时感知共享变量的变化。
    • 原子性:不具备原子性,可能导致数据不一致。
    • 禁止指令重排:防止指令执行顺序优化可能导致的线程安全问题。
    Volatile能不能保证线程安全?

    Volatile无法保证线程安全,因为它不具备原子性。例如,多个线程同时修改同一个volatile变量可能导致数据不一致。

    DCL单例为什么要加volatile?

    为了防止指令重排,确保单例的初始化线程安全。例如,双重锁加volatile可以防止初始化时的竞态条件和指令重排问题。

    三、Java线程锁机制是怎样的?偏向锁、轻量级锁、重量级锁有什么区别?

    锁的机制

    Java的锁机制基于对象的Markword记录锁状态。根据资源竞争的剧烈程度,锁会自动升级:

  • 无锁:默认状态,无锁。
  • 偏向锁:初次进入锁区域的线程自动获得偏向锁,后续锁升级为重量级锁。
  • 轻量级锁:在轻量级锁区域内竞争,升级为重量级锁。
  • 重量级锁:全局锁,竞争度高,资源竞争严重。
  • 锁升级机制

    锁升级是根据资源竞争的严重程度进行的:

  • 无锁→偏向锁:初次进入锁区域的线程自动获得偏向锁。
  • 偏向锁→轻量级锁:在轻量级锁区域内,线程进入同一锁区域后升级为轻量级锁。
  • 轻量级锁→重量级锁:在轻量级锁区域内,多次出现竞争,锁升级为重量级锁。
  • 四、AQS的理解及其实现可重入锁

    AQS的理解

    AQS(AbstractQueuedSynchronizer)是Java线程的同步框架,用于实现许多锁的基础设施。ReentrantLock的可重入性基于AQS实现。

    AQS的实现机制

    AQS维护了一个信号量state和一个双向链表队列。state用于控制线程排队或放行,具体意义根据使用场景而定。在可重入锁中,state用于表示加锁的次数(0表示无锁状态)。

    五、有A、B、C三个线程,如何保证三个线程同时执行?如何在并发情况下保证三个线程依次执行?如何保证三个线程有序交错进行?

    同时执行三个线程

    可以通过JUC的信号量工具类CountDownLatch或CyclicBarrier实现。例如:

  • CountDownLatch:用于等待多个线程完成后,传递信号继续执行。
  • CyclicBarrier:允许线程在多次执行后继续等待,适用于循环任务。
  • 线程依次执行

    可以利用共享变量的可见性实现线程的有序执行。例如:

    static volatile int ticket = 1;public static void main(String[] args) {    Thread t1 = new Thread(() -> {        while (true) {            if (ticket == 1) {                try {                    Thread.sleep(100);                    System.out.println("a");                    ticket = 2;                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    });    Thread t2 = new Thread(() -> {        while (true) {            if (ticket == 2) {                try {                    Thread.sleep(100);                    System.out.println("b");                    ticket = 3;                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    });    Thread t3 = new Thread(() -> {        while (true) {            if (ticket == 3) {                try {                    Thread.sleep(100);                    System.out.println("c");                    ticket = 1;                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    });    t1.start();    t2.start();    t3.start();}
    线程有序交错进行

    可以使用Semaphore信号量或JUC的Condition实现。例如:

    private static Semaphore s1 = new Semaphore(1);private static Semaphore s2 = new Semaphore(1);private static Semaphore s3 = new Semaphore(1);public static void main(String[] args) {    try {        s1.acquire();        s2.acquire();    } catch (InterruptedException e1) {        e1.printStackTrace();    }    new Thread(() -> {        while (true) {            try {                s1.acquire();                System.out.println("A");                s2.release();            } catch (InterruptedException e) {                e.printStackTrace();            }            try {                Thread.sleep(500);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }).start();    new Thread(() -> {        while (true) {            try {                s2.acquire();                System.out.println("B");                s3.release();            } catch (InterruptedException e) {                e.printStackTrace();            }            try {                Thread.sleep(500);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }).start();    new Thread(() -> {        while (true) {            try {                s3.acquire();                System.out.println("C");                s1.release();            } catch (InterruptedException e) {                e.printStackTrace();            }            try {                Thread.sleep(500);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }).start();}

    六、如何对一个字符串快速进行排序?

    使用Fork/Join框架实现

    Fork/Join框架适合对大字符串进行排序。具体实现步骤如下:

  • 创建ForkJoinPoolForkJoinPool pool = new ForkJoinPool();
  • 资源类继承RecursiveTask:创建一个递归任务类MyTask,重写compute方法实现排序。
  • 提交任务ForkJoinTask(task).submit();
  • 获取结果task.get();
  • 例如:

    public class MergeTest {    private static int MAX = 100;    private static int inits[] = new int[MAX];    static {        Random r = new Random();        for (int index = 1; index <= MAX; index++) {            inits[index - 1] = r.nextInt(1000);        }    }    public static void main(String[] args) throws Exception {        long beginTime = System.currentTimeMillis();        ForkJoinPool pool = new ForkJoinPool();        MyTask task = new MyTask(inits);        ForkJoinTask
    taskResult = pool.submit(task); try { int[] ints = taskResult.get(); System.out.println(Arrays.toString(ints)); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(System.out); } long endTime = System.currentTimeMillis(); System.out.println("耗时=" + (endTime - beginTime)); } static class MyTask extends RecursiveTask
    { private int source[]; public MyTask(int source[]) { this.source = source; } @Override protected int[] compute() { int sourceLen = source.length; if (sourceLen > 2) { int midIndex = sourceLen / 2; MyTask task1 = new MyTask(Arrays.copyOf(source, midIndex)); task1.fork(); MyTask task2 = new MyTask(Arrays.copyOfRange(source, midIndex, sourceLen)); task2.fork(); return joinInts(task1.join(), task2.join()); } else { if (sourceLen == 1 || source[0] <= source[1]) { return source; } else { int targetp[] = new int[sourceLen]; targetp[0] = source[1]; targetp[1] = source[0]; return targetp; } } } private static int[] joinInts(int array1[], int array2[]) { int destInts[] = new int[array1.length + array2.length]; int array1Len = array1.length; int array2Len = array2.length; for (int index = 0, array1Index = 0, array2Index = 0; index < destInts.length; index++) { int value1 = array1Index >= array1Len ? Integer.MAX_VALUE : array1[array1Index]; int value2 = array2Index >= array2Len ? Integer.MAX_VALUE : array2[array2Index]; if (value1 < value2) { array1Index++; destInts[index] = value1; } else { array2Index++; destInts[index] = value2; } } return destInts; } }}

    以上就是对Java并发编程常见问题的总结和思考,希望对理解Java并发编程有所帮助。

    转载地址:http://yxsu.baihongyu.com/

    你可能感兴趣的文章
    NoSQL数据库概述
    查看>>
    Notadd —— 基于 nest.js 的微服务开发框架
    查看>>
    NOTE:rfc5766-turn-server
    查看>>
    Notepad ++ 安装与配置教程(非常详细)从零基础入门到精通,看完这一篇就够了
    查看>>
    Notepad++在线和离线安装JSON格式化插件
    查看>>
    notepad++最详情汇总
    查看>>
    notepad++正则表达式替换字符串详解
    查看>>
    notepad如何自动对齐_notepad++怎么自动排版
    查看>>
    Notes on Paul Irish's "Things I learned from the jQuery source" casts
    查看>>
    Notification 使用详解(很全
    查看>>
    NotImplementedError: Cannot copy out of meta tensor; no data! Please use torch.nn.Module.to_empty()
    查看>>
    NotImplementedError: Could not run torchvision::nms
    查看>>
    nova基于ubs机制扩展scheduler-filter
    查看>>
    Now trying to drop the old temporary tablespace, the session hangs.
    查看>>
    nowcoder—Beauty of Trees
    查看>>
    np.arange()和np.linspace()绘制logistic回归图像时得到不同的结果?
    查看>>
    np.power的使用
    查看>>
    NPM 2FA双重认证的设置方法
    查看>>
    npm build报错Cannot find module ‘html-webpack-plugin‘解决方法
    查看>>
    npm build报错Cannot find module ‘webpack/lib/rules/BasicEffectRulePlugin‘解决方法
    查看>>