Java中Thread详解(一篇就够了)

Posted 星光Starsray

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中Thread详解(一篇就够了)相关的知识,希望对你有一定的参考价值。

前言

操作系统中,一个进程往往代表着一个应用程序实例,而线程是进程中轻量级的调度单元,也可以看作是轻量级的进程,可以共享进程资源。下面简单介绍在操作系统中线程通用实现方式。接下来内容主要对线程模型进行简单介绍,然后对Java线程实现Thread类进行了解。

线程模型

暂且抛开Java线程,先说明一下在操作系统中,线程通用的几种实现方式。实现线程主要有三种方式。

内核线程模型

使用内核线程实现的方式,通常也被成为1 : 1实现模型。内核线程(Kernel Level Thread,KLT)是直接由操作系统内核来支持的线程,这种线程由内核来控制切换,内核通过调度器(Scheduler)来对线程进行调度,并负责将线程任务映射到各个处理器,在多核操作系统中具有能力并行处理多个任务,这种支持多线程的内核被称为多线程内核(Mutil-Threads Kernel)。
程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口(轻量级进程Light Weight Process)LWP,也就是通常意义所描述的线程,每个轻量级进程都由一个内核支持,因此先支持内核线程,才能有轻量级进程。其中KLT、Schedule、LWP之间关系如下图所示:

 

在这个模型中,每个轻量级进程都是一个调度单元,单个KLT的阻塞不会影响其他单元的调度,当然也有其本身的局限性,由于是基于内核线程进行创建的,所以线程的各种操作,如创建、同步等都需要进行系统调用。应用程序运行在用户空间,内核处于用户态(User Mode),系统调用需要进行用户态和内核态(Kernel Mode)切换,频繁的进行用户态内核态切换,会严重消耗系统性能。此外,每个KLT都需要内核来支持,因此也会消耗内核资源如内核线程空间等,因此这种模型下所能创建的线程数也是很有限的。

用户线程模型

使用用户线程实现的方式被称为1 : N模型,非内核线程都可以被看作是用户线程(User Thread,UT)的一种。这里需要辩证的看待关于用户线程的定义,广义上来从非内核线程的角度看,轻量级进程也属于用户线程的范畴,但是由于其建立在内核基础之上,过于依赖系统调用,又不具备通常意义上用户线程的优点。其实现模型如图所示:

 

狭义上所说的用户线程指完全建立在用户空间的线程,线程的控制无需内核参与,内核也无法感知其实现模式,这种线程也不需要进行用户态和内核态的切换,因此用户线程对资源的使用率较小,支持大规模的线程数量。部分高性能数据库中的线程就是完全由这种类型的用户线程来实现。
用户线程的优势也是其劣势,由于没有内核的支持,所有线程都需要由用户程序处理。操作系统通常只会进行进程级别资源的分配,那么在用户空间,用户线程如何创建、销毁、切换、阻塞以及调度都要由用户来处理,因此用户线程实现的程序也提高了复杂度和故障机率,除非是大规模的线程需要场景下,否则一般不倾向于使用用户线程。
Java中的线程曾经使用用户线程实现,最终由于其复杂度被放弃。近年来,以高并发为特性的语言,如Golang、Erlang中使用用户线程的场景有所提升。

混合模型

基于使用内核线程和用户线程的模型,还有一种就是整合两种模型特性的混合实现模型,也被成为M : N模型。这种模型中即使用了内核线程,也使用了用户线程,通过轻量级进程作为用户线程和内核线程之间的桥梁,既可以使用内核提供的线程进行调度及处理器映射,同时也兼具了大规模用户线程并发。这种模型下用户线程和轻量级进程的数量比不固定,如下图所示:

 

在许多UNIX类操作系统,如Solaris、HP-UX等都提供了这种M : N的线程模型实现。

Java线程实现

实现模型

Java虚拟机规范中并没有定义关于线程如何实现,因此不同虚拟机厂商实现方式也没有统一标准。在JDK1.2前的线程,早期的Classic虚拟机中,基于一种被称为绿色线程的用户线程来实现。但在JDK1.3之后,商用Java虚拟机普遍开始使用内核线程模型,即1 : 1模型来作为Java线程的实现。
在HotSpot虚拟机中,每个Java线程都是直接映射到操作系统的原生线程来实现,虚拟机本身并不会干预线程的创建、调度,但是可以设置线程的优先级给予操作系统建议。关于线程的冻结或者唤醒、线程由那颗CPU核心执行以及可使用的CPU执行时间片都由操作系统来完成。
当然也有特殊场景,用于JavaME的CLDC HotSpot Implementation同时支持1 : N以及特殊的混合模型。Solaris平台的HotSpot,由于操作系统的特性,因此同时支持1 : 1和N : M的两种线程模型,通过虚拟机参数设置。
总体来说,Java线程模型的实现通常依赖于所运行的操作系统内核提供的支持,在不同平台并不能达成一致,因此Java虚拟机规范也未明确的做出定义。另外,至于使用何种线程模型,其所影响的只有线程规模(线程数量)和操作成本。

调度策略

线程无论基于何种模型创建,都有其调度策略,线程的调度指的是操作系统为线程分配使用权的过程。通常调度方式包含两种,分别是协同式(Cooperative Threads Scheduling)和抢占式(Preemptive Threads Scheduling):

  1. 协同式调度

使用协同式调度方式的线程调度由其本身来控制,线程在自身工作执行完成后,主动通知系统切换到另一个线程执行,这种方式实现简单,便于控制。但是过于依赖线程本身来控制调度,如果某个线程执行任务的程序存在问题就会导致一直阻塞。

     2. 抢占式调度

使用抢占式调度方式的多线程系统,线程的调度由系统分配执行时间,线程的切换由系统决定。这种调度方式下,线程的执行时间可控,不会因单个线程问题导致应用程序堵塞。Java中所使用的线程调度策略就是抢占式,虽然整个调度基于系统来确定,但是可以通过设置优先级的方式给予操作系统一定的建议,总共包含1~10优先级,优先级越高,线程越容易被选择执行。

Thread类

Java语言是支持多线程的,一个正在运行的Java程序可以称之为一个进程(process),在每个进程里面包含多个线程,线程是进程中单一的顺序控制流,CPU在执行计算机指令的时候都是按顺序执行,但是由于其执行速度很快,可以把时间分成很细小的时间片,交替执行,线程和进程的区别在于:

  • 创建进程的开销大于创建线程的开销,进程之间的通信比线程间要难
  • 线程不能独立存在,依托于进程而存在,线程也可以看作轻量级的进程
  • 多进程的稳定性高于多线程,一个进程的运行不会影响其他进程,但线程崩溃往往会引起程序的崩溃

Thread类位于java.lang包,JDK1.0引入。在HotSpot虚拟机中,线程使用的是基于操作系统的1 : 1的内核实现模型来创建线程,线程的创建、调度、执行、销毁等由内核进行控制,调度过程通过抢占式策略进行调度。

生命周期

Java语言定了线程生命周期的6种状态,在任意时刻线程只能处于一种状态,Java中提供了特定的API,在某些场景下可以实现线程间状态的转换。这6种状态使用枚举类java.lang.Thread.State来表示,每种状态如下所示:

  • NEW(新建状态):新创建后尚未启动的线程处于这种状态
  • RUNNABLE(运行状态):Java虚拟机中处于该状态的线程,对应操作系统状态中的Running和Ready,表示该线程正在执行或者等待操作系统分配执行时间
  • BLOCKED(阻塞状态):当多个线程进行资源争抢,等待获取一个排他锁的线程会处于该状态,当线程获取到锁之后状态会变为RUNNABLE状态,其他等待锁的线程继续处于BLOCKED状态,一般被synchronized关键字修饰或者ReentrantLock包裹的代码快会触发不可到到达线程处于该状态。
  • WAITING(无限期等待状态):处于该状态的线程不会被分配执行时间,需要等待被其他线程显式唤醒,调用Thread中以下方法会触发线程处于该状态:
    • Object.wait() with no timeout
    • Thread.join() with no timeout
    • LockSupport.park()
  • TIMED_WAITING(限期等待状态):计时等待状态的线程,不会被操作系统分配执行时间,也不需要被其他线程显式唤醒,在计时结束后由操作系统自动唤醒,Thread中的以下方法会触发线程进入此状态:
    • Thread.sleep()
    • Object.wait() with timeout
    • Thread.join() with timeout
    • LockSupport.parkNanos()
    • LockSupport.parkUntil()
  • TERMINATED(终止状态):线程任务执行结束时处于这种状态

线程的生命周期以及状态转换如下图所示:

 

接下来对Thread对象的声明周期包含的六种状态进行进行简单模拟。

NEW

public class Test
    public static void main(String[] args) throws Exception
        System.out.println("Thread State is:"+new Thread().getState());
    

输出结果

Thread State is:NEW

RUNNABLE

public class Test implements Runnable
    @Override
    public void run() 
        System.out.println("Thread State is:"+Thread.currentThread().getState());
    
    public static void main(String[] args) throws Exception
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
    

输出结果

Thread State is:RUNNABLE

BLOCKED

  • 创建线程T1,T2调用start()方法,T1,T2状态均为RUNNABLE
  • 若T1获得锁,T1状态为RUNNABLE,T2状态变为BLOCKED
  • 等待T1执行完释放锁,T2获得锁,T2状态RUNNABLE
class BlockThread extends Thread 
    private String name;    //当前线程名称
    private Object lock;    //锁
    public BlockThread(Object lock,String name)
        this.lock = lock;
        this.name = name;
    
    @Override
    public void run() 
        System.out.println("Thread "+name+" State is "+Thread.currentThread().getState());
        synchronized (lock) 
            System.out.println("Thread "+name+" hold the lock");
            try 
                System.out.println("Thread "+name+" State is "+Thread.currentThread().getState());
                Thread.sleep(1000 * 10);    //抢到锁的线程执行逻辑,这里用睡眠模拟
             catch (InterruptedException e) 
                e.printStackTrace();
            
            System.out.println("Thread " + name + " release the lock");
        
    

public class Test
    public static void main(String[] args) throws Exception
        Object lock = new Object();//锁
        BlockThread t1 = new BlockThread(lock,"T1");
        BlockThread t2 = new BlockThread(lock,"T2");
        t1.start(); //线程 T1开始运行
        t2.start(); //线程 T2开始运行
        Thread.sleep(100);  //阻塞主线程,等待T1,T2抢锁
        System.out.println("Thread T1 State is " + t1.getState());  //获取T1线程状态
        System.out.println("Thread T2 State is " + t2.getState());  //获取T2线程状态
    

输出结果

Thread T1 State is RUNNABLE
Thread T1 hold the lock
Thread T1 State is RUNNABLE
Thread T2 State is RUNNABLE
Thread T1 State is TIMED_WAITING
Thread T2 State is BLOCKED
Thread T1 release the lock
Thread T2 hold the lock
Thread T2 State is RUNNABLE
Thread T2 release the lock

WAITING

class WaitingThread extends Thread 
    private Object lock;
    public WaitingThread(String name, Object lock) 
        super(name);
        this.lock = lock;
    
    
    @Override
    public void run() 
        System.out.println("Thread " + Thread.currentThread().getName()+" try to wait");
        synchronized (lock) 
            try 
                lock.wait();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

public class Test
    public static void main(String[] args) throws Exception 
        Object lock = new Object();
        WaitingThread t = new WaitingThread("T", lock);
        t.start();
        Thread.sleep(1000);
        System.out.println("Thread T State is " + t.getState());
        System.out.println("Thread "+Thread.currentThread().getName()+" State is " + Thread.currentThread().getState());
    

输出结果

Thread T try to wait
Thread T State is WAITING
Thread main State is RUNNABLE

TIMED_WAITING

线程调用sleep()方法,进入TIMED_WAITING状态

class WaitingThread extends Thread 
    private Object lock;
    public WaitingThread(String name, Object lock) 
        super(name);
        this.lock = lock;
    
    @Override
    public void run() 
        synchronized (lock) 
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

public class Test
    public static void main(String[] args) throws Exception 
        Object lock = new Object();
        WaitingThread t1 = new WaitingThread("T1", lock);
        WaitingThread t2 = new WaitingThread("T2", lock);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("Thread T1 State is " + t1.getState());
        System.out.println("Thread T2 State is " + t2.getState());
    

输出结果

Thread T1 State is TERMINATED
Thread T2 State is TIMED_WAITING

TERMINATED

public class Test implements Runnable
    @Override
    public void run() 

    
    public static void main(String[] args) throws Exception
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
        System.out.println("Thread State is "+thread.getState());
    

输出结果

Thread State is TERMINATED

构造方法

Java中的线程相关操作由Thread类实现,Thread类位于java.lang包,于JDK 1.0版本引入。Thread类封装了多线程操作的上层API,可执行单元代码逻辑通过实现Runnable接口,重写run方法完成,Thread类本身也实现了该接口,接口定义如下所示:

@FunctionalInterface
public interface Runnable 
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();

在JDK1.8 引入函数式编程后,Runnable接口也被@FunctionalInterface注解标记为函数式接口,支持Lambda表达式。在JDK1.8中,Thread类提供了如下构造方法:

  • Thread() :创建一个默认设置的线程对象实例
  • Thread(Runnable target) :创建一个包含可执行对象的线程实例
  • Thread(Runnable target, String name) :创建一个包含可执行对象,指定名称的线程对象
  • Thread(String name):创建一个指定名称的线程对象
  • Thread(ThreadGroup group, Runnable target) :创建一个指定线程组,包含可执行对象的线程对象实例
  • Thread(ThreadGroup group, Runnable target, String name) :创建一个指定线程组,包含可执行对象,指定线程名称的线程对象实例
  • Thread(ThreadGroup group, Runnable target, String name, long stackSize) :创建一个指定线程组、包含可执行对象、指定名称以及堆栈大小的线程对象实例
  • Thread(ThreadGroup group, String name):创建一个指定线程组,线程名称的线程实例

说明:关于线程组(ThreadGroup类),一个线程组代表一组线程。此外,一个线程组还可以包括其他线程组。线程组形成一棵树,其中除了初始线程组之外的每个线程组都有一个父级。允许线程访问有关其自己的线程组的信息,但不能访问有关其线程组的父线程组或任何其他线程组的信息。

优先级

Java中创建的线程,每个线程都有一个优先级,具有较高优先级的线程优先于具有较低优先级的线程执行。当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级。Thread类中定义了以下三个默认优先级:

// 线程所能设置的最小优先级
public final static int MIN_PRIORITY = 1;

// 创建线程的默认优先级
public final static int NORM_PRIORITY = 5;

// 线程所能设置的最大优先级
public final static int MAX_PRIORITY = 10;

创建线程

Java中如何创建一个线程Thread,可以继承Thread类、实现Runnable或者Callable接口进行显式的创建线程。需要注意的是Runnable接口只是提供了自定义任务执行单元的方法逻辑,而真正关于线程的上层API操作实现还是位于Thread类。查看Thread中重写的run方法源码:

@Override
public void run() 
    if (target != null) 
        target.run();
    

虽然Thread类实现了run方法,但是并没有做任何的特殊处理,真正的任务执行单元,会交给构造方法传入的可执行对象完成逻辑处理,这里使用的是模板方法设计模式。

关于Callable接口下面会详细介绍。

继承Thread类

通过继承Thread类,并重写run方法。代码如下:

public class Test extends Thread
    @Override
    public void run() 
        System.out.println("Thread is Created");
    
    public static void main(String[] args) 
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
        System.out.println(Thread.currentThread().getName());
    

在主线程创建新的线程,二者的执行时机由系统调度确定,因此输出结果是随机的,如下所示:

/*新建线程先执行*/
Thread is Created
main
/*主线程先执行*/
main
Thread is Created

实现Runnable接口

通过实现Runnable接口并且重写run方法来创建一个线程。代码如下

public class Test implements Runnable
    @Override
    public void run() 
        System.out.println("Thread is Created");
    
    public static void main(String[] args) 
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
        System.out.println(Thread.currentThread().getName());
    

输出结果

/*新建线程先执行*/
Thread is Created
main
/*主线程先执行*/
main
Thread is Created

实现Callable接口

严格来讲,参考Oracle官方文档,创建一个Thread只有这两种方式,无论实现Runable接口,还是继承Thread类,都存在一些缺陷,我们无法获得线程的执行结果,无法处理执行过程的异常,这里提供另外一种创建线程的方式。
Callable是JDK 1.5新增的接口,位于java.util.concurrent 包下,Callable接口里面定义了call方法,call方法是run方法的增强版,可以通过实现Callable接口时传入泛型来指定call方法的返回值,并且可以声明抛出异常。

@FunctionalInterface
public interface Callable<V> 
    V call() throws Exception;

Thread中无论哪一种构造方法都没有Callable类型的target,只能传入Runnable类型的target,那如何把Thread和Callable联系起来,这里就要引入Future接口和FutureTask实现类。

Future接口定义如下方法

/**尝试取消执行此任务。*/
boolean cancel(boolean mayInterruptIfRunning) 
/**等待计算完成,然后检索其结果。*/  
V get() 
/**如果需要等待最多在给定的时间计算完成,然后检索其结果(如果可用)。*/  
V get(long timeout, TimeUnit unit) 
/**如果此任务在正常完成之前被取消,则返回 true 。*/  
boolean isCancelled() 
/**返回 true如果任务已完成。*/  
boolean isDone()

查看源码,RunnableFuture作为一个过渡同时继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

public interface RunnableFuture<V> extends Runnable, Future<V> 
    void run();

查看FutureTask的类图关系

FutureTask类提供了两个构造方法

  • FutureTask(Callable callable):创建一个包含Callable可执行对象的FutureTask实例。
  • FutureTask(Runnable runnable, V result):创建一个包含Runnable可执行对象及执行结果的FutureTask实例,参数由RunnableAdapter进行适配包装。

再回到最初的问题如何将实现了Callable接口的线程类作为Thread实例的target,这里经过了以下过程

  • 创建线程类实现Callable接口,重写call方法
  • 创建FutureTask实例,将实现Callable接口的线程类实例化对象作为FutureTask的target
  • 创建Thread类,将FutureTask实例化对象作为Thread的target

Future接口和FutureTask类在中间做了一层包装,代码展示如下

public class Test implements Callable<String> 
    @Override
    public String call() throws Exception 
        System.out.println("Thread is Created");
        return "OK";
    
    public static void main(String[] args) throws Exception 
        Test test = new Test();
        FutureTask futureTask = new FutureTask(test);
        Thread thread = new Thread(futureTask);
        thread.start();
        String str = (String) futureTask.get(5,TimeUnit.SECONDS);
        System.out.println(str);
        System.out.println(Thread.currentThread().getName());
    

输出结果

Thread is Created
OK
main

native方法

Java中线程采用内核线程模型来实现用户程序中的线程,因此一些常用方法依托于虚拟机原生实现,下面介绍这些native方法的基本使用。

yield

yield方法是一个native方法,由C++底层进行关于操作系统层面的逻辑处理。yield的字面意思是退让。调用该方法会向调度程序提示当前线程愿意放弃其当前对处理器的使用,调度程序可以随意忽略此提示。
yield是一种启发式尝试,使用它可以改善线程之间的相对进展,否则会过度使用 CPU。在使用yield方法时通常有下面两种使用场景:

  • yield的使用应与详细的分析和基准测试相结合,以确保实际上具有预期的效果,但很少使用这种方法。对于调试或测试目的可能很有用,它可能有助于重现由于竞争条件导致的错误
  • 在设计并发控制结构(例如 java.util.concurrent.locks 包中的结构)时,它也可能很有用

如下代码所示

public class TestYield 
    public static void main(String[] args) 
        MyThread thread1 = new MyThread("thread-1");
        MyThread thread2 = new MyThread("thread-2");
        thread1.start();
        thread2.start();
    

    private static class MyThread extends Thread 
        public MyThread(String name) 
            super(name);
        
        @Override
        public void run() 
            for (int i = 1; i <= 5; i++) 
                if (i % 2 == 0) 
                    Thread.yield();
                    System.out.println(getName() + ":" + i);
                
            
        
    

join

join方法让一个线程加入到另一个线程之前执行,在此线程执行期间,其他线程进入阻塞状态,当然也可以指定join入参(指定执行等待的超时时间),最多等待几毫秒让该线程终止,超时0意味着永远等待。
此实现使用以this.isAlive为条件的this.wait调用循环,当线程终止时,将调用this.notifyAll方法。建议应用程序不要在Thread实例上使用wait、notify或notifyAll。如果任何线程中断了当前线程,会抛出InterruptedException异常时清除当前线程的中断状态。

public class TestJoin 
    public static void main(String[] args) throws InterruptedException 
        MyThread thread1 = new MyThread("thread-1");
        MyThread thread2 = new MyThread("thread-2");

        thread1.start();
        thread1.join();
        thread2.start();
    
    private static class MyThread extends Thread 
        public MyThread(String name) 
            super(name);
        
        @Override
        public void run() 
            for (int i = 0; i < 20; i++) 
                System.out.println(getName());
            
        
    

sleep

当调用线程的sleep方法,使当前执行的线程休眠(暂时停止执行)指定的毫秒数,取决于系统计时器和调度程序的精度和准确性。如果任何线程中断了当前线程,会抛出InterruptedException异常时清除当前线程的中断状态。

interrupt

使用interrupt方法会中断这个线程,除非当前线程正在中断自己,否则会调用该线程的checkAccess方法,这可能会导致抛出SecurityException。主要有以下几种场景:

  • 如果一个线程被Object类的wait、或者Thread的join、sleep方法调用处于阻塞状态时,那么它的中断状态会被清除并且会收到一个InterruptedException。
  • 如果该线程在InterruptibleChannel上的IO操作中被阻塞,则通道将关闭,线程的中断状态将被设置,线程将抛出 java.nio.channels.ClosedByInterruptException。
  • 如果该线程在java.nio.channels.Selector中被阻塞,则该线程的中断状态将被设置,并且它将立即从选择操作返回,可能带有非零值,就像调用了选择器的唤醒方法一样。如果前面的条件都不成立,则将设置该线程的中断状态。
public class TestInterrupt 
    public static void main(String[] args) 

        Thread thread = new Thread(() -> 
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        , "thread");

        thread.start();
        thread.interrupt();
    

输出结果

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.starsray.test.api.TestInterrupt.lambda$main$0(TestInterrupt.java:8)
	at java.lang.Thread.run(Thread.java:748)

其他

这里就不一一演示,列举在Thread中提供的其他方法。

  • activeCount():返回当前线程的thread group及其子组中活动线程数的估计
  • checkAccess():确定当前正在运行的线程是否有权限修改此线程
  • clone():将CloneNotSupportedException作为线程抛出无法有意义地克隆
  • currentThread():返回对当前正在执行的线程对象的引用
  • dumpStack():将当前线程的堆栈跟踪打印到标准错误流
  • enumerate(Thread[] tarray):将当前线程的线程组及其子组中的每个活动线程复制到指定的数组中
  • getAllStackTraces():返回所有活动线程的堆栈跟踪图
  • getContextClassLoader():返回此Thread的上下文ClassLoader
  • getDefaultUncaughtExceptionHandler():返回线程由于未捕获异常突然终止而调用的默认方法
  • getId():返回此线程的标识符
  • getName():返回此线程的名称
  • getPriority():返回此线程的优先级
  • getStackTrace():返回表示此线程的堆栈转储的堆栈跟踪元素数组。
  • getState():返回此线程的状态
  • getThreadGroup():返回此线程所属的线程组
  • getUncaughtExceptionHandler():返回由于未捕获的异常,此线程突然终止时调用的处理程序
  • holdsLock(Object obj):返回 true当且仅当当前线程在指定的对象上保持监视器锁
  • interrupt():中断这个线程
  • interrupted():返回当前线程是否中断
  • isAlive():返回这个线程是否活着
  • isDaemon():返回这个线程是否是守护线程
  • isInterrupted(boolean ClearInterrupted):测试某个线程是否已被中断
  • setContextClassLoader(ClassLoader cl):设置此线程的上下文ClassLoader。
  • setDaemon(boolean on):将此线程标记为 daemon线程或用户线程。
  • ...

守护线程

Java中线程除了用户工作线程外还有一种特殊的线程被称为守护线程。通过setDaemon(true)方法将此线程标记为守护线程或用户线程。当虚拟机中唯一运行的线程都是守护线程时,Java虚拟机退出。该方法必须在线程启动前调用。
守护线程最主要的作用就是服务于虚拟机中的非守护线程,比如虚拟机中的垃圾回收线程就是典型的守护线程。需要注意的是如果是在守护线程中产生的线程,那么这个线程依然是守护线程。如代码所示:

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class TestDaemon 
    private static volatile boolean restFlag = false;
    private static final int threshold = 5;

    public static void main(String[] args) throws InterruptedException, IOException 
        List<Integer> list = new ArrayList<>();
        // 工作线程
        Thread worker = new Thread(() -> 
            while (!restFlag) 
                try 
                    Thread.sleep(1000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                if (!restFlag) 
                    list.add(1);
                    System.out.println("worker is working");
                
            
            System.out.println("worker rest");
        , "work-thread");

        // 守护线程
        Thread employers = new Thread(() -> 
            while (!restFlag) 
                if (list.size() >= threshold) 
                    restFlag = true;
                    System.out.println("employers exit");
                
            
        , "employers-thread");

        employers.setDaemon(true);
        worker.start();
        employers.start();
    

总结

Java中线程使用的是与操作系统1 : 1的内核线程模型,因此线程的创建、运行等依赖于操作系统的调度。线程生命周期中包含六种状态,线程调用不同的方法可以在RUNNING、WATING、TIMED_WAITING、BLOCK之间相互转换。
这里总结一下三种创建线程的方法特点

  • 继承Thread类实现多线程:
    • 实现起来简单,而且要获取当前线程,无需调用Thread.currentThread()方法,直接使用this即可获取当前线程
    • 线程类已经继承Thread类了,就不能再继承其他类
    • 多个线程不能共享同一份资源
  • 通过实现Runnable接口或者Callable接口实现多线程:
    • 线程类只是实现了接口,还可以继承其他类
    • 多个线程可以使用同一个target对象,适合多个线程处理同一份资源的情况
    • 通过这种方式实现多线程,相较于第一类方式,编程较复杂
    • 要访问当前线程,必须调用Thread.currentThread()方法

一般推荐使用以实现接口的方式创建线程类。

图文详解CDC技术,看这一篇就够了!

点击上方“朱小厮的博客”,选择“设为星标”

后台回复"书",获取

后台回复“k8s”,可领取k8s资料

这篇文章是对变更数据捕获 (CDC) 实践的介绍,而不是对特定工具的深入探讨。

假设我们正在构建一个简单的 Web 应用程序。在大多数情况下,此类项目从最小的数据架构开始。例如,像 MySQL 或 PostgreSQL 这样的关系数据库足以处理和存储许多用户可以使用的数据。他们输入查询,更新它们,关闭它们,更正它们,通常会执行许多操作。它可以是 CRM、ERP、自动银行系统、计费系统,甚至是 POS 终端,应有尽有。

但是,存储在数据库中的信息可能会引起许多第三方系统的兴趣,通常是分析系统。企业需要了解存储在该系统中的应用程序或其他实体的状态——账户、存款、制造、人力资源等。数据几乎在每项业务运营中都发挥着重要作用。因此,企业会定期生成报告,这些报告反映了企业感兴趣的所有主要指标,并且是做出进一步管理决策所必需的。

报告和分析计算通常非常消耗资源。查询可能需要数小时才能完成,这通常会严重影响从中检索数据的系统的性能。另一个缺点是发送所有这些数据会给网络带来很大压力。最后,基于该数据的业务决策由于查询频率而延迟。所以,如果你每晚更新数据,那意味着你要到第二天才能知道昨天发生了什么。

如果系统有一个明确的负载减少时间段(例如在夜间),并且这段时间足以卸载所有必要的数据而不影响系统的主要活动——那么使用对 RDBMS 的直接查询可能是一个可以接受的选项。但是,如果没有减少负载的时期,或者分配的负载窗口不足以完全卸载整个更改的数据怎么办?

在这里 CDC 流程来救援。顾名思义,变更数据捕获只会捕获数据中的变化,这是复制数据的 ETL 模式之一。它是一种确定我们感兴趣的数据的机制,即跟踪源数据库中的更改并将其应用于目标数据库或数据仓库。并且在目标数据库或数据仓库中,我们可以进行所有类型的分析、报告生成等,甚至不会影响源数据库的性能。

因此,用户可以在不降低性能的情况下使用原始系统,并且管理层可以随时获取他们做出管理决策所需的报告。

CDC

因此,CDC 的本质——通过捕获数据操作语言 (DML) 更改(插入/更新/删除)的事实和更改的数据本身来为用户表提供历史更改信息。CDC 以可以在上游数据系统中复制的形式提取它们。用行话来说,这样的数据也被称为“delta”。

您可以将 CDC 视为一种机制,它不断监控原始数据系统的更改、提取它们并将它们分发到上游系统。Change Data Capture 通过近乎实时地实现数据的增量加载,排除了批量数据加载的过程。

那么,使用CDC如何解决我们提到的问题呢?

好吧,您没有定期运行非常大的请求,因为您的负载率实际上是峰值负载行为不会高的,因此,您必须执行网络以确保及时发送您想要的所有数据,而不是发送所有这些数据并最终因为数据是连续发布的,而且是为了小规模的数据,您可以通过多个数据泄露更多的网络正常运作,并且显着地展示您的工作成果,让您有不同的业务范围。将数据发送到您的数据仓库更新,因此仓库中的数据是最新的,这是为了提供实时信息,以便根据数据业务决策。

变更数据操作数据中心是您最好的数据存储方式。

提取增量

在分析也是仓库、CRM、MDM hub、灾难时间、提取事务的“delta”是它创建系统的架构任务,当有一段时间的并行操作时,迁移从一个系统的数据项目中的人常有任务交给另一个。

在早期,增量知道我们现在的整个问题列表更新了。将成为一个潜在的可能的结果,并且您可能会遗弃一些数据。

为了确保任何遗失这些数据,但工程师还尝试了对行进行控制,并得出了大致相同的结果——它可以工作,但它非常资源。不到。

所有的问题都随着简单的出现而得到解决。

是数据库中的一种特殊过程。示例过程的特殊性类型,每次数据库中的事件发生后,在示例中的 SQL 示例中的简单示例被执行。

所以,我们需要一个简单的表来跟踪所有的更改,以便为每个创建对象创建一个表,该表将用于更改。

但也有一个类——尽管现在表中的数据有任何变化,简单简单。但一些CDC产品仍然基于简单。

CDC的现代方法

我们可以回想一下,数据库实际上是事务性的,并且具有类似的数据库日志(也称为数据库事务日志或事务日志)之类的功能。几乎所有管理系统都有事务日志文件,记录每个事务在我们需要做访问事务日志的所有数据库并选择我们想要跟踪的更改。

因此,在 CDC 中,更改是使用从事务中读取更改的,并且会在相应的更改表中设置管理员的过程中通过管理员的。

日志的更改部分是我们正确设置的这些事务并跟踪实际过去的更改。将它们应用到目标。

现代读取事务处理在独立服务器的内存中进行系统处理,突出需要远程更改这些更改此类型的通知显示,系统的架构不,并提供了令人印象深刻的源代码,能够跟踪数据发生的变化变化的预测目标。

生产就绪的CDC系统

要制造一个之前需要生产的CDC系统,除了提取之外,我们在一个系统考虑问题:

  1. 必须按照发生的变化,否则系统可能会出现的不同状态;

  2. 在传递方面,传递保证是的,因此,CDC 必须至少传递一次及时通知消息,如果后续系统交付了一个变更事件,则可能导致整个系统的状态;

  3. 最后,简单的消息转换,因为必须支持不同系统的数据格式可能。

系统的所有消息都报道了——所有消息源对更改源/订阅的消息进行更改,源源不断地将系统监听到系统目标对象,然后在更改消息这些对象时,都可以在更改消息的范围内。时使用它们。

该解决方案提供了许多好处,扩展性。订阅方法允许主要消息来源/可以向目标系统发送更多的更新,并且可以相应地扩展此用户的数量,以便在需要时使用地处理数据。

第二个想要的好处是两个系统现在连接了。如果源系统更改其数据库或将特定数据集移动到其他位置,则目标不需要使用像解拉系统那样进行更改。只要源系统继续以相同的格式将消息就不会继续接收到系统更新消息,而显示源已更改任何内容。

来源:https://luminousmen.com/post/change-data-capture

想知道更多?描下面的二维码关注我

后台回复"技术",加入技术群

后台回复“k8s”,可领取k8s资料

以上是关于Java中Thread详解(一篇就够了)的主要内容,如果未能解决你的问题,请参考以下文章

四万字长文总结多线程,一篇就够了!

四万字长文总结多线程,一篇就够了!

四万字长文总结多线程,一篇就够了!

Java中的多线程你只要看这一篇就够了

java多线程面试题这一篇就够了

Java中的多线程你只要看这一篇就够了