您的位置 首页 java

Java并发-线程基础

更多互联网新鲜资讯、工作奇淫技巧 关注原创【飞鱼在浪屿】 (日更新)

Java并发-线程基础

JavaThread对象允许我们在单独的线程中运行代码。当应用程序启动时,JVM将创建名为的初始线程main。main方法在主线程上运行。在应用程序内部,我们可以创建新线程以与主线程并行执行其他任务。

Java使用本机操作系统线程。因此,一个操作系统线程映射了一个Java线程。


创建线程

Thread类的构造函数带有一个Runnable对象。可运行接口具有一个抽象run方法,该方法称为Thread#start()方法。它可以由lambda,匿名类或实现Runnable方法的类实例化。

通常,使用lambda更容易且更紧凑:

 Thread thread = new Thread(() -> {
    // content of run command
});
thread.start();  

只要线程的运行挂钩方法没有返回,线程就一直存在。调度程序可以挂起并运行线程多次。为了使线程永远执行,它需要一个无限循环来防止其返回。

Join方法允许一个线程等待另一个线程的完成。这是屏障同步的一种简单形式。

Java并发-线程基础


Java线程类型:用户和守护程序线程

JVM启动时,它包含一个名为主线程的用户线程。用户线程和守护程序线程之间的主要区别是它们退出时会发生什么。

  • 即使主线程退出,用户线程也会继续其生命周期。
  • 但是,当所有用户线程退出时,所有守护进程线程都会终止。
  • 当所有用户线程退出时,JVM本身退出。

线程类包含布尔daemon字段,用于指定线程是否为守护程序。可以在创建时通过构造函数或setter方法进行设置。

 Thread thread = new Thread(getRunnable());
thread.setDaemon(true);
thread.start();  

默认情况下,守护程序字段为false,因此我们生成的大多数线程都是用户线程。isDaemon如果未指定,则线程会复制父线程的状态。Java使用守护线程在一些场景,如ForkJoinPool和Timer。为了说明,看以下示例:

 public class Main {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
//        runDeamonThread();
        runUserThread();
        System.out.println(getCurrentThreadName() + " exits");
    }

    private static void runDeamonThread() throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newWorkStealingPool(10);
        executorService.execute(getRunnable());
    }

    private static void runUserThread() {
        Thread thread = new Thread(getRunnable());
        thread.start();
    }

    private static Runnable getRunnable() {
        return () -> {
            for (int i = 0; i <= 200; i++) {
                System.out.print(".");
                Thread.yield();
            }
            System.out.println(getCurrentThreadName() + " exits. isDeamon: " + isDaemon());
        };
    }

    private static boolean isDaemon() {
        return Thread.currentThread().isDaemon();
    }

    private static String getCurrentThreadName() {
        return Thread.currentThread().getName();
    }
}  
  • 当调用runUserThread方法时,它显示以下示例输出:
 ................................................
main exits
........................................................................................
Thread-0 exits. isDeamon: false  
  • 第二种情况是调用runDeamonThread,以ForkJoinPoolDaemon Threads为例。可以简单地使用setDaemon(true)方法。输出:
 main exits  

因此,当main方法退出时,所有用户线程都将终止,JVM退出并杀死所有守护程序线程,因此我们甚至没有机会看到守护程序线程的输出。


停止线程

与创建相比,停止线程是一件非常困难的事情。一旦线程开始运行,它就会与调用者分离开来,并且它具有自己的生命周期。它可以完成任务并退出,或者如果它长时间运行,则可以永远工作。Java没有为我们提供自愿停止线程的方法(不建议使用)。

  1. 简单的方法可能是使用停止标志:
 volatile boolean isStopped = false;

public void test() {
    new Thread(() -> {
        while (!isStopped) {
            System.out.print(".");
        }
        System.out.println("Child Exits");
    }).start();

    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    isStopped = true;
    System.out.println("Main exits");
}  

该标志用volatile为了使两个线程都可以看到其最新值。但是,如果线程被阻塞sleep,wait,join或阻塞I / O操作,那么将失败。

2.停止的另一种方法是使用线的interrupt()方法。

为了使中断机制正常工作,被中断的线程必须支持其自己的中断机制。可以检查以下2种情况:

  • 非阻塞和长时间运行的任务

在这种情况下,调用该thread.interrupt()方法将设置该线程的中断标志,但是如果任务本身不检查中断标志的状态,则不会有任何影响。例如:

 public void test() throws InterruptedException {
    Thread thread = new Thread(() -> {
        System.out.println("Child Starts");
        while (true) {
            System.out.print(".");
        }
    });

    thread.start();
    thread.interrupt();
    
    thread.join();
    System.out.println("Main exits");
}  

为了使线程捕获中断,它应该迭代检查中断标志的状态,以便它可以了解是否有任何待处理的中断请求并相应地处理该请求。

因此,可以在while循环中检查该标志,如果为true,则可以返回或中断该循环。在lambda表达式中,不可能引发异常,但是在适当的地方也可以引发异常InterruptedException。

 public void test() throws InterruptedException {
    Thread thread = new Thread(() -> {
        System.out.println("Child Starts");
        while (true) {
            if (Thread.interrupted()) {
                break;
            }
            System.out.print(".");
        }
        System.out.println("Child exits");
    });

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

    thread.join();
    System.out.println("Main exits");
}  

请注意,该Thread.interrupted()方法返回标志的值,如果为true,则将其清除。因此,如果想将线程的状态保持为堆栈高层的中断,则可以使用Thread.currentThread().interrupt();

  • 阻止任务

如果一个线程调用频繁的封锁方式,例如wait,join,sleep,blocking I/O。这些方法在内部检查中断状态,如果他们被中断,如果让他们自动抛出异常InterruptedException。应该在适当的上下文中捕获并处理此异常。以下示例使用中断来中断阻塞sleep操作中的循环:

 public void test() throws InterruptedException {
    Thread thread = new Thread(() -> {
        System.out.println("Child Starts");
        try {
            while (true) {
                Thread.sleep(10000);
            }
        } catch (InterruptedException e) {
            System.out.println("Thread interrupted: " + e.getMessage());
        }
        System.out.println("Child Exits");
    });

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

    thread.join();
    System.out.println("Main exits");
}  

有一些处理Java的模式InterruptedException:

  • 一种方法是将异常传播给调用方,因此上层将负责。
  • 在重新抛出之前,可以进行任务特定的清理。
  • 如果不可能重新抛出,可以再次将中断状态设置为true,Thread.currentThread().interrupt()以保留更上层要检查它的证据。

因此,可以断定,如果要执行可取消的任务,则需要定期检查中断状态,并以线程退出的方式处理中断。


线程组

为了简化线程管理,可以使用java.lang.ThreadGroup对相关线程进行分组的对象来组织多个线程。每个线程组都需要有一个父组。在层次结构中,有一个Main组,它是程序中创建的其他组或线程的父级。可以通过使用父组和/或名称调用的构造函数来创建ThreadGroup。要将线程添加到组中,我需要在线程的构造函数中指定该组。

 public void test() {
    ThreadGroup tg1 = new ThreadGroup("Thread-group-1");
    ThreadGroup tg2 = new ThreadGroup(tg1, "Thread-group-2");

    Thread thread1 = new Thread(tg1,"thread-1");
    Thread thread2 = new Thread(tg2,"thread-2");
    Thread thread3 = new Thread(tg2,"thread-3");

    thread1.start();
    thread2.start();
    thread3.start();
    
    Thread[] threads = new Thread[tg2.activeCount()];
    tg2.enumerate(threads);

    Arrays.asList(threads).forEach(t -> System.out.println(t.getName()));
    tg1.list();
}  

可以通过调用enumerate方法来遍历线程,该方法用组的线程引用,填充tg2数组。

可以通过使用线程组来实现线程池:

 public class ThreadPool {
    // Create a thread group field
    private final ThreadGroup group = new ThreadGroup("ThreadPoolGroup");
    // Create a LinkedList field containing Runnable
    private final List<Runnable> tasks = new LinkedList<>();

    public ThreadPool(int poolSize) {
        // create several Worker threads in the thread group
        for (int i = 0; i < poolSize; i++) {
            var worker = new Worker(group, "worker-" + i);
            worker.start();
        }
    }

    private Runnable take() throws InterruptedException {
        synchronized (tasks) {
            // if the LinkedList is empty, we wait
            while (tasks.isEmpty()) tasks.wait();
            // remove the first job from the LinkedList and return it
            return tasks.remove(0);
        }
    }

    public void submit(Runnable job) {
        // Add the job to the LinkedList and notifyAll
        synchronized (tasks) {
            tasks.add(job);
            tasks.notifyAll();
        }
    }

    public int getRunQueueLength() {
        // return the length of the LinkedList
        // remember to also synchronize!
        synchronized (tasks) {
            return tasks.size();
        }
    }

    public void shutdown() {
        // this should stop all threads in the group
        group.interrupt();
    }

    private class Worker extends Thread {
        public Worker(ThreadGroup group, String name) {
            super(group, name);
        }

        public void run() {
            // we run in an infinite loop:
            while(true) {
                // remove the next job from the linked list using take()
                // we then call the run() method on the job
                try {
                    take().run();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
        }
    }
}  

线程局部变量

JavaThreadLocal类可用于创建其值只能由同一线程访问的变量。因此,即使两个线程正在执行相同的代码,并且该代码具有对相同ThreadLocal变量的引用,两个线程也无法看到彼此的ThreadLocal变量。

 public class Main {

    public static class ThreadLocalStorage {

        private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

        public static void setName(String name) {
            threadLocal.set(name);
        }

        public static String getName() {
            return threadLocal.get();
        }
    }

    public static void main(String[] args) {

        ThreadLocalStorage.setName("Main thread");

        Runnable runnable = () -> {
            ThreadLocalStorage.setName(getCurrentThreadName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread: [" + getCurrentThreadName() + "] " +
                "- value: [" + ThreadLocalStorage.getName() + "]");
        };

        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);

        thread1.start();
        thread2.start();

        System.out.println("Main exits");
    }

    private static String getCurrentThreadName() {
        return Thread.currentThread().getName();
    }
}  

如果运行代码,我们可以看到每个线程都有其自己的ThreadLocal对象副本。

 Main exits
Thread: [Thread-0] - ThreadLocal value: [Thread-0]
Thread: [Thread-1] - ThreadLocal value: [Thread-1]  

而不是每个线程在a内都有其自己的值ThreadLocal,而是InheritableThreadLocal授予对线程以及该线程创建的所有子线程的值的访问权限。

文章来源:智云一二三科技

文章标题:Java并发-线程基础

文章地址:https://www.zhihuclub.com/200356.shtml

关于作者: 智云科技

热门文章

网站地图