JUC核心知识点汇总(持续更新中)

写在前面:本文为个人八股复习所用,整合了其他平台的答案加自己的理解,希望能对大家的八股复习有所帮助,答案可能存在出入,请大家理性食用~~

1. 进程和线程的区别

进程

进程是操作系统资源分配的基本单位,是程序的一次执行过程,进程之间是相互独立的,各自有各自的内存空间,它由操作系统进行调度。

线程

线程是CPU执行调度的基本单位,一个进程在其执行的过程中会产生多个线程,与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

下面来思考这样一个问题:为什么程序计数器虚拟机栈本地方法栈线程私有的呢?为什么方法区线程共享的呢?

1.1. 程序计数器为什么是私有的?

程序计数器主要有下面两个作用:

  1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。

所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置

1.2. 虚拟机栈和本地方法栈为什么是私有的?

  1. 虚拟机栈: 每个 Java 方法在执行之前会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
  2. 本地方法栈: 和虚拟机栈所发挥的作用非常相似,区别是:虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的

1.3. 一句话简单了解堆和方法区

堆和方法区是所有线程共享的资源,其中是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存)方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

2. 线程的生命周期和状态

Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:

  • 新建(New):线程对象被创建出来,但还未调用start()启动的状态。
  • 就绪(Runnable):线程已经被启动,等待系统分配资源来运行。处于就绪状态的线程可能正在等待CPU时间片或等待某些条件满足。使用时间片轮转、优先级队列等调度算法来运行线程。
  • 运行(Running):线程获得CPU资源正在执行任务
  • 阻塞(Blocked):线程在等待某个锁或其他条件,如等待IO完成。
    • 比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
  • 等待(Waiting):线程等待其他线程通知调度器一个特定条件,通常是由于调用了Object.wait()Thread.join()或者LockSupport.park() 等方法。
  • 终止(Terminated):线程执行完毕或者因为异常退出了 run() 方法。

3. 切换线程状态的方法

线程的状态是由JVM根据线程的行为和调度来管理和切换的,并且程序员可以通过合适的方法来影响线程状态的切换。

  1. Thread 类的方法:
    • start():启动一个线程,使其处于就绪状态,等待系统调度执行。
    • sleep(long millis):让当前线程休眠指定的时间,线程状态从运行转为阻塞或超时等待状态。
    • join():等待线程终止,使当前线程进入等待状态。
    • interrupt():中断线程,让线程抛出 InterruptedException 异常,处于终止状态。
  1. Object 类的方法:
    • wait():让当前线程等待,使其进入等待状态,直到其他线程调用相同对象的 notify()notifyAll() 方法唤醒它。
    • notify() 和 notifyAll():用于唤醒一个或所有等待中的线程,使其从等待状态转为就绪状态
  1. LockSupport 类的方法:
    • park() 和 parkNanos(long nanos):阻塞当前线程,使其进入等待状态,可以通过 unpark(Thread thread) 方法唤醒指定线程。
  1. Thread 类的静态方法:
    • yield():提示调度器当前线程愿意放弃当前的 CPU 执行时间,使当前线程从运行状态转为就绪状态,但不一定会立即执行。

4. Thread.sleep() 方法和 Object.wait() 方法对比

共同点:两者都可以暂停线程的执行。

区别

  • sleep() 方法没有释放锁,而 wait() 方法释放了锁
  • wait() 通常被用于线程间交互/通信,sleep()通常被用于暂停执行。
  • wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法。sleep()方法执行完成后,线程会自动苏醒,或者也可以使用 wait(long timeout) 超时后线程会自动苏醒
  • sleep() 是 Thread 类的静态本地方法,wait() 则是 Object 类的本地方法。为什么这样设计呢?下一个问题就会聊到。

5. 为什么 wait() 方法不定义在 Thread 中?

wait()是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象(Object)都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入 Waiting状态,自然是要操作对应的对象(Object)而非当前的线程(Thread)。

类似的问题:为什么 sleep() 方法定义在 Thread 中?

因为 sleep() 是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁。

6. 可以直接调用 Thread 类的 run 方法吗?

new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结:调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行

7. 创建线程的方法

7.1. 继承Thread类

  • 定义一个类,继承自 Thread 类。
  • 重写 run() 方法,该方法包含线程的代码逻辑。
  • 创建该类的实例,并调用 start() 方法启动线程。

示例代码:

//1.继承Thread类
public class Client {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("继承Thread类创建线程");
    }
}

7.2. 实现Runnable接口

  • 定义一个类,实现 Runnable 接口。
  • 实现 run() 方法,该方法包含线程的代码逻辑。
  • 创建 Runnable 实例,将其作为参数传递给 Thread 类的构造函数。
  • 调用 Thread 实例的 start() 方法启动线程。

示例代码:

//2.实现Runnable接口
public class Client {
    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

class MyRunnable  implements Runnable {

    @Override
    public void run() {
        System.out.println("实现Runnable接口创建线程");
    }
}

7.3. 实现Callable接口(带返回值的线程)

  • 定义一个类,实现 Callable 接口,并指定泛型为希望返回的类型
  • 实现 call() 方法,该方法包含线程的代码逻辑,并返回一个值
  • 创建 Callable 实例,并使用 ExecutorService 的 submit() 方法提交任务。
  • 调用 Future 对象的 get() 方法获取线程执行结果。
import java.util.concurrent.*;

class MyCallable implements Callable<String> {
    public String call() {
        // 线程执行的代码逻辑
        return "MyCallable is running";
    }
}

public class Client {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //Executors 类:这是一个工厂类,提供了创建不同类型线程池的静态方法
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(new MyCallable());

        try {
            String result = fu.get();//获取线程执行结果
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }finally {
            service.shutdown();//关闭线程池
        }
    }
}

汇总:

public class Client {
    public static void main(String[] args) {
        //1.继承Thread类
        Thread t1 = new Thread() {
            @Override
            public void run() {
                System.out.println("继承Thread类创建线程");
            }
        };
        t1.start();

        //2.实现Runnable接口
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("实现Runnable接口");
            }
        });
        t2.start();

        //3.实现Callable接口
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(new Callable<String>(){
            @Override
            public String call() throws Exception {
                return null;
            }
        });
        try {
            String result = future.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }finally {
            executor.shutdown();//关闭线程池
        }
    }
}

8. 面试题:双线程交替打印数字

思路:

双线程交替打印有两种思路,一种是设置共享变量,控制当前线程应该打印哪个数,另一种是通过锁的互斥机制实现。第一种方法具备通用性,适用于各种线程同步问题,其原理是多个线程持有同一个锁,每个线程内部判断是否轮到自己执行,否则就等待;第二种方式只适用于两个线程交替执行的任务,其原理是通过wait和notify来实现线程间的通信,但两个线程都实现同一个Runnable接口,在Runnable接口中控制执行流程。

方法一(通用性方法):

public static void main(String[] args){
    public static int state = 0;
    public static Object lock = new Object();

    @Test
    public void testThread() {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (lock) {
                        while (true) {
                            while (state % 2 != 0)
                                lock.wait();
                            System.out.println(Thread.currentThread().getName() + ":" + state);
                            state++;
                            Thread.sleep(1000);
                            lock.notifyAll();
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (lock) {
                        while (true) {
                            while (state % 2 != 1)
                                lock.wait();
                            System.out.println(Thread.currentThread().getName() + ":" + state);
                            state++;
                            Thread.sleep(1000);
                            lock.notifyAll();
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t1.start();
        t2.start();
}

方法二:

public static void main(String[] args){
    Runnable runnable = new MyRunnable();
    Thread t1 = new Thread(runnable);
    Thread t2 = new Thread(runnable);
    t1.start();
    t2.start();
}

class MyRunnable implements Runnable {
    private static int i = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notify();
                System.out.println(Thread.currentThread().getName() + ": " + i++);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                try {
                    wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

9. 面试题:交替打印ABC

9.1. 方法一:使用synchronized和wait/notify

synchronized是Java中的一个关键字,用于实现对共享资源的互斥访问。wait和notify是Object类中的两个方法,用于实现线程间的通信。wait方法会让当前线程释放锁,并进入等待状态,直到被其他线程唤醒。notify方法会唤醒一个在同一个锁上等待的线程。

我们可以使用一个共享变量state来表示当前应该打印哪个字母,初始值为0。当state为0时,表示轮到A线程打印;当state为1时,表示轮到B线程打印;当state为2时,表示轮到C线程打印。每个线程在打印完字母后,需要将state加1,并对3取模,以便循环。同时,每个线程还需要唤醒下一个线程,并让自己进入等待状态。

public class PrintABC {
 
    // 共享变量,表示当前应该打印哪个字母
    private static int state = 0;
 
    // 共享对象,作为锁和通信的媒介
    private static final Object lock = new Object();
 
    public static void main(String[] args) {
        // 创建三个线程
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 循环100次
                    for (int i = 0; i < 100; i++) {
                        // 获取锁
                        synchronized (lock) {
                            // 判断是否轮到自己执行
                            while (state % 3 != 0) {
                                // 不是则等待
                                lock.wait();
                            }
                            // 打印字母
                            System.out.println("A");
                            // 修改状态
                            state++;
                            // 唤醒下一个线程
                            lock.notifyAll();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
 
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 100; i++) {
                        synchronized (lock) {
                            while (state % 3 != 1) {
                                lock.wait();
                            }
                            System.out.println("B");
                            state++;
                            lock.notifyAll();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
 
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 100; i++) {
                        synchronized (lock) {
                            while (state % 3 != 2) {
                                lock.wait();
                            }
                            System.out.println("C");
                            state++;
                            lock.notifyAll();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
 
        // 启动三个线程
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

9.2. 方法二:使用ReentrantLock和Condition

ReentrantLock是Java中的一个类,用于实现可重入的互斥锁。Condition是ReentrantLock中的一个接口,用于实现线程间的条件等待和唤醒。ReentrantLock可以创建多个Condition对象,每个Condition对象可以绑定一个或多个线程,实现对不同线程的精确控制。

我们可以使用一个ReentrantLock对象作为锁,同时创建三个Condition对象,分别绑定A、B、C三个线程。每个线程在打印字母之前,需要调用对应的Condition对象的await方法,等待被唤醒。每个线程在打印字母之后,需要调用下一个Condition对象的signal方法,唤醒下一个线程。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
 
public class PrintABC {
 
    // 共享变量,表示当前应该打印哪个字母
    private static int state = 0;
 
    // 可重入锁
    private static final ReentrantLock lock = new ReentrantLock();
 
    // 三个条件对象,分别绑定A、B、C三个线程
    private static final Condition A = lock.newCondition();
    private static final Condition B = lock.newCondition();
    private static final Condition C = lock.newCondition();
 
    public static void main(String[] args) {
        // 创建三个线程
        Thread threaA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 循环100次
                    for (int i = 0; i < 100; i++) {
                        // 获取锁
                        lock.lock();
                        try {
                            // 判断是否轮到自己执行
                            while (state % 3 != 0) {
                                // 不是则等待
                                A.await();
                            }
                            // 打印字母
                            System.out.println("A");
                            // 修改状态
                            state++;
                            // 唤醒下一个线程
                            B.signal();
                        } finally {
                            // 释放锁
                            lock.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
 
        Thread threaB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 100; i++) {
                        lock.lock();
                        try {
                            while (state % 3 != 1) {
                                B.await();
                            }
                            System.out.println("B");
                            state++;
                            C.signal();
                        } finally {
                            lock.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
 
        Thread threaC = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 100; i++) {
                        lock.lock();
                        try {
                            while (state % 3 != 2) {
                                C.await();
                            }
                            System.out.println("C");
                            state++;
                            A.signal();
                        } finally {
                            lock.unlock();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
 
        // 启动三个线程
        threaA.start();
        threaB.start();
        threaC.start();
    }
}

  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java JUCJava Util Concurrent)是Java平台的一个并发编程库,提供了一些并发编程的工具和框架。以下是Java JUC的一些重要知识点: 1. Lock接口和ReentrantLock类:提供了一种比Java的synchronized关键字更灵活、可定制化的同步机制。 2. Condition接口:可以和Lock接口一起使用,提供了一种等待通知机制,可以让线程在等待某个条件成立时挂起,直到被其他线程唤醒。 3. Semaphore类:提供了一种信号量机制,可以限制某些资源的并发访问量,保证程序的稳定性。 4. CountDownLatch类:提供了一种倒计时锁机制,可以让某个线程在其他线程都完成后再执行。 5. CyclicBarrier类:提供了一种栅栏机制,可以让多个线程在某个点上进行同步,等待所有线程都到达后再同时执行。 6. Executor框架:提供了一种线程池机制,可以更好地管理线程,提高程序的性能和稳定性。 7. CompletableFuture类:提供了一种异步编程机制,可以让程序在等待某些操作的同时继续执行其他操作,提高程序的并发性能。 这些都是Java JUC的重要知识点,掌握它们可以帮助开发者更好地编写高并发、高性能的程序。 ### 回答2: Java JUCJava Util Concurrency)是Java并发编程的工具类库,提供了一些多线程编程的辅助工具和数据结构,主要包括锁、原子变量、并发容器、线程池等。 首先,Java JUC提供了多种类型的锁,如ReentrantLock、ReadWriteLock等。这些锁可以用来控制对共享资源的访问,保证线程的安全性。通过使用锁,可以实现线程的互斥访问和公平竞争访问,防止资源的并发访问导致的数据不一致的问题。 另外,Java JUC还提供了一些原子变量,比如AtomicInteger、AtomicLong等。原子变量是线程安全的,可以保证对其操作的原子性。通过使用原子变量,可以避免多线程环境下对共享变量的竞争导致的数据错乱问题。 并发容器也是Java JUC的重要组成部分,如ConcurrentHashMap、ConcurrentLinkedQueue等。这些并发容器是线程安全的,可以在多线程环境下安全地处理数据。通过使用并发容器,可以提高多线程程序的性能和并发访问的效率。 最后,Java JUC还提供了线程池的支持,通过线程池可以实现线程的复用、统一管理和调度。线程池可以减少线程的创建和销毁的开销,并且可以控制并发线程的数量,避免因为线程数过多导致系统资源耗尽的问题。 总之,Java JUC知识点涵盖了锁、原子变量、并发容器和线程池等多个方面,可以帮助程序员更好地进行多线程编程,提高程序的性能和并发访问的效率。 ### 回答3: Java JUCjava.util.concurrent)是Java用于处理多线程并发编程的工具包。它提供了一套强大的并发编程工具和类,帮助开发者更加方便地编写高效、稳定的多线程程序。 Java JUC包含了以下几个重要的知识点: 1. 锁机制:Java JUC提供了多种类型的锁机制,包括ReentrantLock、StampedLock等,用于实现线程同步和互斥访问共享资源。通过使用锁机制,可以确保多个线程之间的数据一致性和线程安全性。 2. 阻塞队列:Java JUC提供了多种类型的阻塞队列,如ArrayBlockingQueue、LinkedBlockingQueue等。阻塞队列是一种特殊的队列,当队列为空或者已满时,插入和删除操作会被阻塞,直到满足条件后再继续执行。 3. 线程池:Java JUC的线程池机制可以重用线程,减少线程的创建和销毁开销,提高系统的性能和资源利用率。通过ThreadPoolExecutor类,可以方便地创建和管理线程池,并根据实际需求调整线程池的大小和线程池线程的执行方式。 4. 原子操作:Java JUC提供了一系列原子类,如AtomicInteger、AtomicLong等,用于支持对共享变量进行原子操作,以避免线程竞争和数据不一致的问题。原子类提供了一系列原子性的方法,保证了多线程环境下的安全访问。 5. 并发容器:Java JUC提供了一些线程安全的并发容器,如ConcurrentHashMap、CopyOnWriteArrayList等,用于在多线程环境下安全地处理数据结构。这些并发容器支持高并发读写操作,提供更好的性能和可伸缩性。 总之,Java JUC提供了一组强大的并发编程工具和类,能够帮助开发者更好地处理多线程编程的并发性和线程安全性问题。通过熟练掌握和应用这些知识点,可以编写出高效、稳定的多线程程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值