> Java > java지도 시간 > 본문

Java 멀티스레딩 지식 포인트에 대한 간략한 요약

WBOY
풀어 주다: 2022-10-12 14:58:11
앞으로
1232명이 탐색했습니다.

이 기사에서는 멀티스레딩과 관련된 문제를 주로 소개하는 java에 대한 관련 지식을 제공합니다. 프로세스는 여러 스레드를 동시에 실행할 수 있으며, 각 스레드는 서로 다른 작업을 병렬로 수행합니다. 스레드는 프로세스의 기초입니다. 단일 시퀀스의 제어 흐름을 함께 살펴보겠습니다. 모두에게 도움이 되기를 바랍니다.

Java 멀티스레딩 지식 포인트에 대한 간략한 요약

추천 학습: "java 비디오 튜토리얼"

Java는 멀티 스레드 프로그래밍에 대한 기본 지원을 제공하므로 멀티 스레드 애플리케이션을 쉽게 개발할 수 있습니다.

Java에서 우리에게 가장 친숙한 스레드는 메인 스레드, 즉 메인 스레드입니다.

프로세스는 여러 스레드를 동시에 실행할 수 있으며, 각 스레드는 서로 다른 작업을 병렬로 수행합니다. 스레드는 프로세스의 기본 단위이며 제어 흐름의 단일 시퀀스입니다. 모든 "데몬이 아닌 스레드"가 실행을 완료할 때까지 프로세스가 실행됩니다. Java의 일반적인 데몬 스레드는 다음과 같습니다. 가비지 수집 스레드,

다음은 동시성과 병렬성의 차이점에 대한 간략한 설명입니다.

동시성: 동시에 실행되는 여러 작업이 있습니다.

병렬: 동시에 동시에 실행되는 여러 작업이 있습니다.

다중 스레드는 작업을 효율적으로 실행하고 CPU 리소스를 합리적으로 활용하며 멀티 코어 CPU 성능을 완벽하게 구현합니다. 그러나 멀티스레딩은 프로그램이 항상 효율적으로 실행되는 것을 허용하지 않습니다. 멀티스레드 전환, 스레드 교착 상태, 스레드 예외 및 기타 문제로 인해 발생하는 오버헤드는 단일 스레드 개발보다 멀티스레드 개발을 더 어렵게 만듭니다. 따라서 개발 효율성을 높이기 위해서는 자바 멀티스레딩 관련 지식을 학습하는 것이 필요하다.

1 멀티 스레드 생성

공식 문서 Thread(Java Platform SE 8)(oracle.com)의 java.lang.Thread 설명에 따르면 스레드를 생성하는 방법에는 크게 두 가지가 있음을 알 수 있습니다.

새 실행 스레드를 만드는 방법에는 두 가지가 있습니다. 하나는 클래스를 Thread의 하위 클래스로 선언하는 것입니다. 이 하위 클래스는 Thread 클래스의 실행 메서드를 재정의해야 합니다.

스레드를 생성하는 또 다른 방법은 Runnable 인터페이스를 구현하는 클래스를 선언하는 것입니다. 그러면 해당 클래스가 run 메서드를 구현합니다. 그런 다음 클래스의 인스턴스를 할당하고 Thread를 생성할 때 인수로 전달하고 시작할 수 있습니다.

볼 수 있습니다. 스레드를 생성하는 방법에는 두 가지가 있습니다.

  • Thread 클래스를 상속하는 클래스를 선언합니다. 이 하위 클래스는 run 메서드를 재정의한 다음 이 하위 클래스의 인스턴스를 생성할 수 있습니다. 작업을 수행하기 위해 스레드를 시작합니다.

  • Runnable 인터페이스를 구현하고 run 메서드를 구현하는 클래스를 선언합니다. 이 클래스의 인스턴스는 매개변수로 Thread 인스턴스에 할당되며, Thread 인스턴스는 스레드를 생성하고 시작하는 데 사용됩니다. Callable 및 FutureTask 사용, 스레드 풀 등과 같은 스레드를 생성하는 다른 방법은 아무것도 아닙니다. 이상 이 확장을 기반으로 소스코드를 보면 FutureTask도 Runnable 인터페이스를 구현하고 있는 것을 알 수 있다.

  • Thread 클래스를 상속하는 메서드를 사용하여 스레드를 생성하는 코드:
/**
 * 使用继承 Thread 类的方法创建线程
 */
public class CreateOne {
    public static void main(String[] args) {
        Thread t = new MySubThread();
        t.start();
    }
}
class MySubThread extends Thread {
    @Override
    public void run() {
        // currentThread() 是 Thread 的静态方法,可以获取正在执行当前代码的线程实例
        System.out.println(Thread.currentThread().getName() + "执行任务");
    }
}
// ================================== 运行结果
Thread-0执行任务
로그인 후 복사

Runnable 인터페이스를 구현하는 메서드를 사용하여 스레드를 생성하는 코드:

/**
 * 使用实现 Runnable 接口的方法创建线程
 */
public class CreateTwo {
    public static void main(String[] args) {
        RunnableImpl r = new RunnableImpl();
        Thread t = new Thread(r);
        t.start();
    }
}
class RunnableImpl implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "执行任务");
    }
}
// ================================== 运行结果
Thread-0执行任务
로그인 후 복사

1.1 어느 것이 더 좋나요?

그렇지만 쓰레드를 생성하는 방법은 두 가지가 있는데, 실제 개발에서는 다음과 같은 이유로 Runnable 인터페이스를 구현하는 방식을 사용하는 것이 더 좋습니다. Thread의 run 메소드를 보면 다음과 같습니다.

// Thread 实例的成员变量 target 是一个 Runnable 实例,可以通过 Thread 的构造方法传入
private Runnable target;
// 如果有传入 Runnable 实例,那么就执行它的 run 方法
// 如果重写,就完全执行我们自己的逻辑
public void run() {
    if (target != null) {
        target.run();
    }
}
로그인 후 복사

소스를 보면 알 수 있습니다. 위의 코드를 보면 Thread 클래스가 실행 작업을 정의하는 본체가 아니라 Runnable이 실행 작업의 내용을 정의하고 Thread가 실행을 호출함으로써 스레드와 작업의 분리를 실현한다는 것을 알 수 있습니다.

스레드와 작업의 분리로 인해 작업을 실행해야 할 때 스레드를 생성하고 실행 후 스레드를 삭제하는 대신 스레드를 재사용할 수 있어 시스템 오버헤드가 너무 많이 발생합니다. 이는 스레드 풀의 기본 아이디어이기도 합니다.

또한 Java는 단일 상속만 지원합니다. Thread를 상속하여 멀티스레딩을 사용하는 경우 상속을 통해 기능을 확장해야 하는데 이는 상당히 번거로운 일입니다.

2 시작 및 실행 메소드

위에서 볼 수 있듯이 Thread 클래스나 Runnable 인터페이스의 run 메소드를 통해 작업을 정의하고 이를 통해 스레드를 생성하고 시작합니다. Thread의 시작 메소드. run 메서드를 통해서는 스레드를 시작하고 생성할 수 없습니다. 이는 일반적인 메서드일 뿐이며, 이 메서드를 직접 호출하면 실제로는 작업을 수행하는 스레드일 뿐입니다.

// 将上面的代码修改一下,查看执行结果
public class CreateOne {
    public static void main(String[] args) {
        Thread t = new MySubThread();
        t.run();
        //t.start();
    }
}
// ===================== 执行结果
main执行任务
로그인 후 복사

start 메소드의 소스 코드 보기:

// 线程状态,为 0 表示还未启动
private volatile int threadStatus = 0;
// 同步方法,确保创建、启动线程是线程安全的
public synchronized void start() {
    // 如果线程状态不为 0,那么抛出异常——即线程已经创建了
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
// 将当前线程添加到线程组
    group.add(this);
    boolean started = false;
    try {
        // 这是一个本地方法
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}
// 由本地方法实现,只需要知道,该方法调用后会创建一个线程,并且会执行 run 方法
private native void start0();
로그인 후 복사

위 소스 코드에서 알 수 있는 점:

스레드 생성 및 시작은 스레드로부터 안전합니다.

  • start() 메소드는 반복적으로 호출할 수 없습니다. 그렇지 않으면 예외가 발생합니다.

  • 3 스레드를 중지하는 방법

  • 스레드는 끝없이 실행되지 않습니다. 일반적으로 스레드 중지 조건은 다음과 같습니다.

run 메소드 실행 종료

  • 线程发生异常,但是没有捕获处理

  • 除此之外,我们还需要自定义某些情况下需要通知线程停止,例如:

    用户主动取消任务

    任务执行时间超时、出错

    出现故障,服务需要快速停止

    ...

    为什么不能直接简单粗暴的停止线程呢?通过通知线程停止任务,我们可以更优雅地停止线程,让线程保存问题现场、记录日志、发送警报、友好提示等等,令线程在合适的代码位置停止线程,从而避免一些数据丢失等情况。

    令线程停止的方法是让线程捕获中断异常或检测中断标志位,从而优雅地停止线程,这是推荐的做法。而不推荐的做法有,使用被标记为过时的方法:stop,resume,suspend,这些方法可能会造成死锁、线程不安全等情况,由于已经过时了,所以不做过多介绍。

    3.1 通知线程中断

    我们要使用通知的方式停止目标线程,通过以下方法,希望能够帮助你掌握中断线程的方法:

    /**
     * 中断线程
     */
    public class InterruptThread {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(() -> {
                long i = 0;
                // isInterrupted() 检测当前线程是否处于中断状态
                while (i < Long.MAX_VALUE && !Thread.currentThread().isInterrupted()) {
                    i++;
                }
                System.out.println(i);
            });
            
            t.start();
            // 主线程睡眠 1 秒,通知线程中断
            Thread.sleep(1000);
            t.interrupt();
        }
    }
    // 运行结果
    1436125519
    로그인 후 복사

    这是中断线程的方法之一,还有其他方法,当线程处于阻塞状态时,线程并不能运行到检测线程状态的代码位置,然后正确响应中断,这个时候,我们需要通过捕获异常的方式停止线程:

    /**
     * 通过捕获中断异常停止线程
     */
    public class InterruptThreadByException {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(()->{
                long i = 0;
                while (i < Long.MAX_VALUE) {
                    i++;
                    try {
                        // 线程大部分时间处于阻塞状态,sleep 方法会抛出中断异常 InterruptedException
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // 捕获到中断异常,代表线程被通知中断,做出相应处理再停止线程
                        System.out.println("线程收到中断通知   " + i);
                        // 如果 try-catch 在 while 代码块之外,可以不用 return 也可以结束代码
                        // 在 while 代码块之内,如果没有 return / break,那么还是会进入下一次循环,并不能正确停止
                        return;
                    }
                }
            });
            t.start();
            Thread.sleep(1000);
            t.interrupt();
        }
    }
    // 运行结果
    线程收到中断通知   10
    로그인 후 복사

    以上,就是停止线程的正确做法,此外,捕获中断异常后,会清除线程的中断状态,在实际开发中需要特别注意。例如,修改上面的代码:

    public class InterruptThreadByException {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(()->{
                long i = 0;
                while (i < Long.MAX_VALUE) {
                    i++;
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        System.out.println("线程收到中断通知   " + i);
                        //  添加这行代码,捕获到中断异常后,检测中断状态,中断状态为 false
                        System.out.println(Thread.currentThread().isInterrupted());
                        return;
                    }
                }
            });
            t.start();
            Thread.sleep(1000);
            t.interrupt();
        }
    }
    로그인 후 복사

    所以,在线程中,如果调用了其他方法,如果该方法有异常发生,那么:

    将异常抛出,而不是在子方法内部捕获处理,由 run 方法统一处理异常

    捕获异常,并重新通知当前线程中断,Thread.currentThread().interrupt()

    例如:

    public class SubMethodException {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(new ExceptionRunnableA());
            Thread t2 = new Thread(new ExceptionRunnableB());
            t1.start();
            t2.start();
            Thread.sleep(1000);
            t1.interrupt();
            t2.interrupt();
        }
    }
    class ExceptionRunnableA implements Runnable {
        @Override
        public void run() {
            try {
                while (true) {
                    method();
                }
            } catch (InterruptedException e) {
                System.out.println("run 方法内部捕获中断异常");
            }
        }
        public void method() throws InterruptedException {
            Thread.sleep(100000L);
        }
    }
    class ExceptionRunnableB implements Runnable {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                method();
            }
        }
        public void method()  {
            try {
                Thread.sleep(100000L);
            } catch (InterruptedException e) {
                System.out.println("子方法内部捕获中断异常");
                // 如果不重新设置中断,线程将不能正确响应中断
                Thread.currentThread().interrupt();
            }
        }
    }
    로그인 후 복사

    综上,总结出令线程正确停止的方法为:

    使用 interrupt() 方法通知目标线程停止,标记目标线程的中断状态为 true

    目标线程通过 isInterrupted() 不时地检测线程的中断状态,根据情况决定是否停止线程

    如果线程使用了阻塞方法例如 sleep(),那么需要捕获中断异常并处理中断通知,捕获了中断异常会重置中断标记位

    如果 run() 方法调用了其他子方法,那么子方法:

    将异常抛出,传递到顶层 run 方法,由 run 方法统一处理

    将异常捕获,同时重新通知当前线程中断

    下面再说说关于中断的几个相关方法和一些会抛出中断异常的方法,使用的时候需要特别注意。

    3.2 线程中断的相关方法

    • interrupt() 实例方法,通知目标线程中断。

    • static interrupted() 静态方法,获取当前线程是否处于中断状态,会重置中断状态,即如果中断状态为 true,那么调用后中断状态为 false。方法内部通过 Thread.currentThread() 获取执行线程实例。

    • isInterrupted() 实例方法,获取线程的中断状态,不会清除中断状态。

    3.3 阻塞并能响应中断的方法

    • Object.wait()

    • Thread.sleep()

    • Thread.join()

    • BlockingQueue.take() / put()

    • Lock.lockInterruptibly()

    • CountDownLatch.await()

    • CyclicBarrier.await()

    • Exchanger.exchange()

    4 线程的生命周期

    线程的生命周期状态由六部分组成:

    Java 멀티스레딩 지식 포인트에 대한 간략한 요약

    可以用一张图总结线程的生命周期,以及各个过程之间是如何转换的:

    Java 멀티스레딩 지식 포인트에 대한 간략한 요약

    5 Thread 和 Object 中的线程方法

    现在,我们已经知道了线程的创建、启动、停止以及线程的生命周期了,那么,再来看看线程相关的方法有哪些。

    首先,看看 Thread 中的一些方法:

    Java 멀티스레딩 지식 포인트에 대한 간략한 요약

    再看看 Object 中的相关方法:

    Java 멀티스레딩 지식 포인트에 대한 간략한 요약

    运行以下代码,查看 wait() 和 sleep() 是否会释放同步锁

    /**
    * 证明 sleep 不会释放锁,wait 会释放锁
    */
    public class SleepAndWait {
        private static Object lock = new Object();
        public static void main(String[] args) {
            Thread t1 = new Thread(()->{
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "获得同步锁,调用 wait() 方法");
                    try {
                        lock.wait(2000);
                        System.out.println(Thread.currentThread().getName() + "重新获得同步锁");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            Thread t2 = new Thread(()->{
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "获得同步锁,唤醒另一个线程,调用 sleep()");
                    lock.notify();
                    try {
                        // 如果 sleep() 会释放锁,那么在此期间,上面的线程将会继续运行,即 sleep 不会释放同步锁
                        Thread.sleep(2000);
                        // 如果执行 wait 方法,那么上面的线程将会继续执行,证明 wait 方法会释放锁
                        //lock.wait(2000);
                        System.out.println(Thread.currentThread().getName() + "sleep 结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t1.start();
            t2.start();
        }
    }
    로그인 후 복사

    上面的代码已经证明了 sleep() 不会释放同步锁,此外,sleep() 也不会释放 Lock 的锁,运行以下代码查看结果:

    /**
     * sleep 不会释放 Lock 锁
     */
    public class SleepDontReleaseLock implements Runnable {
        private static Lock lock = new ReentrantLock();
        @Override
        public void run() {
            // 调用 lock 方法,线程会尝试持有该锁对象,如果已经被其他线程锁住,那么当前线程会进入阻塞状态
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获得 lock 锁");
                // 如果 sleep 会释放 Lock 锁,那么另一个线程会马上打印上面的语句
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "释放 lock 锁");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 当前线程释放锁,让其他线程可以占有锁
                lock.unlock();
            }
        }
        public static void main(String[] args) {
            SleepDontReleaseLock task = new SleepDontReleaseLock();
            new Thread(task).start();
            new Thread(task).start();
        }
    }
    로그인 후 복사

    5.1 wait 和 sleep 的异同

    接下来总结 Object.wait() 和 Thread.sleep() 方法的异同点。

    相同点:

    • 都会使线程进入阻塞状态

    • 都可以响应中断

    不同点:

    • wait() 是 Object 的实例方法,sleep() 是 Thread 的静态方法

    • sleep() 需要指定时间

    • wait() 会释放锁,sleep() 不会释放锁,包括同步锁和 Lock 锁

    • wait() 必须配合 synchronized 使用

    6 线程的相关属性

    现在我们已经对 Java 中的多线程有一定的了解了,我们再看看 Java 中线程 Thread 的一些相关属性,即它的成员变量。

    Java 멀티스레딩 지식 포인트에 대한 간략한 요약

    运行以下代码,了解线程的相关属性

    public class ThreadFields {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(() -> {
                // 自定义线程的 ID 并不是从 2 开始
                System.out.println("线程 " + Thread.currentThread().getName()
                                   + " 的线程 ID " + Thread.currentThread().getId());
                while (true) {
                    // 守护线程一直运行,但是 用户线程即这里的主线程结束后,也会随着虚拟机一起停止
                }
            });
            // 自定义线程名字
            t.setName("自定义线程");
            // 将其设置为守护线程
            t.setDaemon(true);
            // 设置优先级 Thread.MIN_PRIORITY = 1     Thread.MAX_PRIORITY = 10
            t.setPriority(Thread.MIN_PRIORITY);
            t.start();
            // 主线程的 ID 为 1
            System.out.println("线程 " + Thread.currentThread().getName() + " 的线程 ID " + Thread.currentThread().getId());
            Thread.sleep(3000);
        }
    }
    로그인 후 복사

    7 全局异常处理

    在子线程中,如果发生了异常我们能够及时捕获并处理,那么对程序运行并不会有什么恶劣影响。

    但是,如果发生了一些未捕获的异常,在多线程情况下,这些异常打印出来的堆栈信息,很容易淹没在庞大的日志中,我们可能很难察觉到,并且不好排查问题。

    如果对这些异常都做捕获处理,那么就会造成代码的冗余,编写起来也不方便。

    因此,我们可以编写一个全局异常处理器来处理子线程中抛出的异常,统一地处理,解耦代码。

    7.1 源码查看

    在讲解如何处理子线程的异常问题前,我们先看看 JVM 默认情况下,是如何处理未捕获的异常的。

    查看 Thread 的源码:

    public class Thread implements Runnable {
        【1】当发生未捕获的异常时,JVM 会调用该方法,并传递异常信息给异常处理器
            可以在这里打下断点,在线程中抛出异常不捕获,IDEA 会跳转到这里
        // 向处理程序发送未捕获的异常。此方法仅由JVM调用。
    private void dispatchUncaughtException(Throwable e) {
            【2】查看第 9 行代码,可以看到如果没有指定异常处理器,默认是线程组作为异常处理器
            【3】调用这个异常处理器的处理方法,处理异常,查看第 15 行
            getUncaughtExceptionHandler().uncaughtException(this, e);
        }
        
        public UncaughtExceptionHandler getUncaughtExceptionHandler() {
            return uncaughtExceptionHandler != null ?
                uncaughtExceptionHandler : group;
        }
        
        【4】UncaughtExceptionHandler 是 Thread 的内部接口,线程组也是该接口的实现,
            只有一个方法处理异常,接下来查看第 25 行,看看 Group 是如何实现的
        @FunctionalInterface
        public interface UncaughtExceptionHandler {
            void uncaughtException(Thread t, Throwable e);
        }
    }
    public class ThreadGroup implements Thread.UncaughtExceptionHandler {
        【5】默认异常处理器的实现
        public void uncaughtException(Thread t, Throwable e) {
            // 如果有父线程组,交给它处理
            if (parent != null) {
                parent.uncaughtException(t, e);
            } else {
                // 获取默认的异常处理器,如果没有指定,那么为 null
                Thread.UncaughtExceptionHandler ueh =
                    Thread.getDefaultUncaughtExceptionHandler();
                if (ueh != null) {
                    ueh.uncaughtException(t, e);
                } 
                // 没有指定异常处理器,打印堆栈信息
                else if (!(e instanceof ThreadDeath)) {
                    System.err.print("Exception in thread \""
                                     + t.getName() + "\" ");
                    e.printStackTrace(System.err);
                }
            }
        }
    }
    로그인 후 복사

    7.2 自定义全局异常处理器

    通过上面的源码讲解,已经可以知道 JVM 是如何处理未捕获的异常的了,即只打印堆栈信息。那么,要如何自定义异常处理器呢?

    具体方法为:

    实现接口 Thread.UncaughtExceptionHandler 并实现方法 uncaughtException()

    为创建的线程指定异常处理器

    示例代码:

    public class MyExceptionHandler implements Thread.UncaughtExceptionHandler{
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("发生了未捕获的异常,进行日志处理、报警处理、友好提示、数据备份等等......");
            e.printStackTrace();
        }
        public static void main(String[] args) {
            Thread t = new Thread(() -> {
                throw new RuntimeException();
            });
            t.setUncaughtExceptionHandler(new MyExceptionHandler());
            t.start();
        }
    }
    로그인 후 복사

    8 多线程带来的问题

    合理地利用多线程能够带来性能上的提升,但是如果因为一些疏漏,多线程反而会成为程序员的噩梦。

    例如,多线程开发,我们需要考虑线程安全问题、性能问题。

    首先,讲讲线程安全问题。

    什么是线程安全?所谓线程安全,即

    在多线程情况下,如果访问某个对象,不需要额外处理,例如加锁、令线程阻塞、额外的线程调度等,调用这个对象都能获得正确的结果,那么这个对象就是线程安全的

    因此,在编写多线程程序时,就需要考虑某个数据是否是线程安全的,如果这个对象满足:

    • 被多个线程共享

    • 操作具有时序要求,先读后写

    • 这个对象的类有他人编写,并且没有声明是线程安全的

    那么我们就需要考虑使用同步锁、Lock、并发工具类(java.util.concurrent)来保证这个对象是在多线程下是安全的。

    再看看多线程带来的性能问题。

    多个线程的调度需要上下文切换,这需要耗费 CPU 资源。

    所谓上下文,即处理器中寄存器、程序计数器内的信息。

    上下文切换,即 CPU 挂起一个线程,将其上下文保存到内存中,从内存中获取另一个运行线程的上下文,恢复到寄存器中,根据程序计数器中的指令恢复线程运行。

    一个线程被挂起,另一个线程恢复运行,这个时候,被挂起的线程的数据缓存对于运行线程来说是无效的,减缓了线程的运行速度,新的线程需要重新缓存数据提升运行速度。

    通常情况下,密集的 IO 操作、抢锁操作都会带来密集的上下文切换。

    以上,是上下文切换带来的性能问题,Java 的内存模型也会带来性能问题,为了保证数据的可见性,JVM 会强制令数据缓存失效,保证数据是实时最新的,这也牺牲了缓存带来的性能提升。

    9 总结

    这里总结下上面的内容。

    • 创建线程有两种方式,继承 Thread 和实现 Runnable

    • start 方法才能正确创建和启动线程,run 方法只是一个普通方法

    • start 메소드는 반복적으로 호출할 수 없으며, 반복 호출하면 예외가 발생합니다.

    • 스레드를 중지하는 올바른 방법은 Interrupt()를 통해 스레드에 알리는 것입니다.

    스레드는 때때로 인터럽트 상태를 확인합니다 그리고 스레드 중지 여부를 결정하려면 isInterrupt() 메서드를 사용하세요.

    스레드가 차단되면 인터럽트 예외를 포착하고 스레드 중지 여부를 결정합니다.

    스레드가 호출하는 하위 메서드는 예외를 발생시키는 것이 가장 좋습니다. 그리고 run 메소드는 이를 균일하게 캡처하고 처리합니다

    스레드가 호출한 하위 메소드가 예외를 포착하면 다시 알려야 합니다. Thread Interrupt

    • 스레드의 수명 주기는

    NEW

    RUNNABLE

    BLOCKED

    WAITING

    TIMED WAITING

    TERMINATED

    • wait()/not ify()/notifyAll()은 동기화에 협력해야 합니다. 잠금 사용

    • wait()은 잠금을 해제하고 절전 모드로 전환합니다. ()는 동기화 잠금 및 잠금 잠금을 포함하여 잠금을 해제하지 않습니다.

    • 스레드의 일부 속성

    스레드 ID는 수정할 수 없습니다.

    스레드 이름 이름은

    데몬을 사용자 정의할 수 있습니다. 일반적으로 스레드는 데몬 스레드

    우선순위로 지정되지 않습니다. 일반적으로 기본 우선순위가 사용되며 우선순위는 변경되지 않습니다.

    • 포착되지 않은 예외를 처리하도록 전역 예외 처리 프로세서를 사용자 정의할 수 있습니다. 백업 데이터, 로그 처리, 알람 등과 같은 비메인 스레드

    • 멀티 스레드 개발은 스레드 안전 문제와 성능 문제를 가져오며 개발 프로세스에는 특별한 주의가 필요합니다

    추천 연구: " 자바 비디오 튜토리얼"

    위 내용은 Java 멀티스레딩 지식 포인트에 대한 간략한 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    관련 라벨:
    원천:juejin.im
    본 웹사이트의 성명
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
    인기 튜토리얼
    더>
    최신 다운로드
    더>
    웹 효과
    웹사이트 소스 코드
    웹사이트 자료
    프론트엔드 템플릿