首页 > 编程语言 > 详细

Java 线程基础

时间:2021-05-21 12:18:21      阅读:11      评论:0      收藏:0      [点我收藏+]

多用线程池少用 Thread 这个类,下面是各个版本的线程池:

  • < Java 5:Thread、Runnable
  • Java 5:Executor、Future、Callable
  • Java 7:ForkJoin
  • Java 8:CompletionStage、CompletionFuture
  • Java 9:Flow

Java 中线程的生命周期和线程的状态可以理解为:在其生命周期中,线程会经历各种状态。

类似于下面这张图是最常见的:

技术分享图片

使用 new Thread() 创建出来的线程就是 NEW 状态,这个时候线程并没有启动;当调用 start() 方法时,线程状态就会变成 RUNNABLE 状态。但是要注意,RUNNABLE 状态又分为两种,一种是准备运行(I/O 阻塞),另一种是运行。

当 t1 获得锁,t2 会等待 t1 释放锁,这个时候 t2 线程的状态就是 BLOCKED 阻塞状态。

当调用了线程的 Object#wait()Thread#join()LockSupport#park() 方法时,就会变成 WAITTING 状态;如果调用的是带有超时参数的就会变成 TIMED_WAITTING 状态。

线程执行结束就是 TERMINATED 状态。

在 Java 中线程一共就分为 6 种状态并且可以通过 state() 方法查看线程状态。

创建线程

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Thread run");
    }
});

上面是 Java 创建一个线程的例子,下面代码展示了这个线程具体是怎么创建的,Thread 构造方法最终都会调用 init 方法。

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    /* 每一个线程必须有一个名字,默认的线程名称是:Thread- 加上线程ID。 
       线程ID 是存放在 threadInitNumber 变量中,每次创建 Thread 实例时,都会加1。*/
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    
    this.name = name;

    /* 假设在线程 A 中,调用了 new Thread 创建了线程 B。
       这里获取线程 A 是为了,将线程 A 的一些配置传递给线程 B。*/
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    
    /* 每一个线程必须有一个线程组,如果没有指定则会先获取安全管理器中的线程组,否则再回去父级(线程A)线程组。 */
    if (g == null) {

        if (security != null) {
            g = security.getThreadGroup();
        }

        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    /* 判断当前线程(线程A) 有没有操作线程组的权限,如果没有就会抛出 SecurityException。
       因为 Thread#start() 方法会操作线程组。*/
    g.checkAccess();
    
    if (security != null) {
        /* 主要用来判断子类有没有重写 getContextClassLoader 和 setContextClassLoader 方法,如果重写了则返回 true。*/
        if (isCCLOverridden(getClass())) {
            /* 既然重写了那么就判断一下有没有重写权限(enableContextClassLoaderOverride),如果没有则抛出 SecurityException。*/
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }
    
    /* 将线程组中的 未启动线程 变量加 1。*/
    g.addUnstarted();
    
    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    
    /* 如果重写了就调用子类的 getContextClassLoader() 方法,否则就直接获取父类的 ClassLoader 就可以了。*/
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    
    /* 我也不清楚为什么要获取这个。*/
    this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
    
    /* 我也不清楚为什么要获取这个。*/
    this.target = target;
    
    /* 设置线程优先级。*/
    setPriority(priority);
    
    /* 继承父级的 ThreadLocal.ThreadLocalMap。
       当然自己的线程数据可以放到 ThreadLocal.ThreadLocalMap threadLocals 中。*/
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    
    this.stackSize = stackSize;

    tid = nextThreadID();
}

下面是简化的实例化流程:

  1. 设置线程名
  2. 如果没有指定线程组,则会先获取安全管理器中的线程组,否则再回去父级线程组
  3. ThreadGroup#nUnstartedThreads 变量加 1
  4. 设置当前线程实例的变量 daemon、priority 和 contextClassLoader 的值为父级的值。
  5. 关联 Runnable 实例
  6. 调用 setPriority0 本地方法设置优先级
  7. 判断需不需要传递(默认需要传递) ThreadLocal
  8. 设置线程栈大小
  9. 最后设置线程ID

终止一个线程

Thread#stop() 方法已经不推荐使用了,但可以使用 Thread#interrupt() 方法停止一个线程。

但是当调用目标线程的 interrupt() 方法后只会将目标线程打上中断标记,而目标线程并不会立即终止。也就是说,只是一个中断标记,JVM 不会强制要求终止线程。下面就是一个不会立即终止的例子:

public class ThreadWithoutIntterupt {
    public static void main(String[] args) {
        Thread t = new Thread(new PrintEvenNumbers());
        t.start();

        // 调用目标线程 t 的 interrupt() 方法。
        t.interrupt();
    }
}

class PrintEvenNumbers implements Runnable {
    @Override
    public void run() {
        System.out.println("Printing even numbers till 20 :");
        for (int i = 2; i <= 20; i = i + 2) {
            System.out.println(i);
        }
    }
}

上面这个例子中,目标线程并没有立即终止,而是正常停止(打印完了所有偶数)。也就是说,终止目标线程的执行,必须要目标线程自己终止,这样才能保证一致性。目标线程可以使用 Thread#interrupted() 方法来判断是否要终止自身。将 PrintEvenNumbers 类改成如下:

class PrintEvenNumbers implements Runnable {
    @Override
    public void run() {
        System.out.println("Printing even numbers till 20 :");
        for (int i = 2; i <= 20; i = i + 2) {
            System.out.println(i);
            if (Thread.interrupted()) {
                System.out.println("保证数据一致性,并终止线程。");
                return;
            }
        }
    }
}

值得注意的是:当调用 Thread.interrupted() 方法时,会清除中断标记(也就是再次设置为 false),再次调用时该方法会返回 false。但是调用 t1.isInterrupted() 方法则不会清除中断标记。

还有一点就是无论是否先调用了目标线程的 interrupt() 方法,Object#wait(...) Thread#join(...) Thread#sleep(...) 都会抛出 InterruptedException 并且会清空 interrupt 状态。如果是 I/O 操作的话则会抛出 ClosedByInterruptException 异常。

class PrintEvenNumbers implements Runnable {
    @Override
    public void run() {
        System.out.println("Printing even numbers till 20 :");
        try {
            Thread.sleep(10_000);
        } catch (InterruptedException e) {
            throw new InternalError("Thraed is interrupted");
        }
    }
}

值得注意的是:如果 interrupt() 方法在 start() 方法之前调用则无效。

join

在当前线程中调用目标线程的 join(...) 方法时,当前线程进入等待状态,直到目标的线程终止、 join 超时或调用了目标线程的 interrupt 方法,当前线程才会继续执行。

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t2 = new SampleThread(1);
        t2.start();
        System.out.println("Invoking join");
        t2.join();
        System.out.println("Returned from join");
        System.out.println(t2.isAlive());
    }
}

class SampleThread extends Thread {
    public int processingCount = 0;

    SampleThread(int processingCount) {
        this.processingCount = processingCount;
        System.out.println("Thread Created");;
    }

    @Override
    public void run() {
        System.out.println("Thread " + this.getName() + " started");
        while (processingCount > 0) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Thread " + this.getName() + " interrupted");
            }
            processingCount--;
        }
        System.out.println("Thread " + this.getName() + " exiting");;
    }
}

下面是一个带有超时时间的 join 代码示例:

Thread t2 = new SampleThread(10);
t2.start();
System.out.println("Invoking join");
t2.join(1000);
System.out.println("Returned from join");
System.out.println(t2.isAlive());

值得注意的是:join(...) 方法满足 Happens-before 关系。

wait 和 notify

当我们调用 wait() 方法时,会使当前线程进行等待,直到其他某个线程在同一对象上调用 notify()、notifyAll() 或 interrupt() 方法为止。

值得注意的是:notify() 和 wait() 方法只能从同步方法或同步代码块中被调用。另外,wait 方法也可以设置超时时间,当设置超时时间为 wait(0) 时与调用 wait() 的作用是相同的。

Java 线程基础

原文:https://www.cnblogs.com/daihao-g/p/14792858.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!