《Java并发编程之美》- 线程终止的方法

多线程并发编程之美

等待线程执行终止的 join 方法

执行的代码

public class MyThreadThree {
    public static void main(String[] args) throws InterruptedException {
        Thread thread_a=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("child thread_a start run");
​
        });
        Thread thread_b=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("child thread_b start run");
        });
​
        thread_a.start();
        thread_b.start();
​
        System.out.println("wait all child thread run over");
​
        thread_a.join();
        thread_b.join();
​
        System.out.println("all child thread run over");
    }
}

最终的执行结果:

wait all child thread run over all child thread run over thread_a start run thread_b start run

Process finished with exit code 0

主线程 main 在调用了子线程 thread_a 与 thread_b 的 join 方法之后,当前的主线程 main 处于阻塞的状态,直到线程 thread_a 与 thread_b 执行完毕后,才会继续执行当前的主线程 main 。

如果我注释掉线程 a 与线程 b 的两个方法,那么执行的结果是什么样的呢?没错,all child thread run over 这句主线程打印的话会在线程 a 与线程 b 输出之前执行(线程 a 与线程 b处于休眠状态)

public class MyThreadThree {
    public static void main(String[] args) throws InterruptedException {
        Thread thread_a=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread_a start run");
​
        });
        Thread thread_b=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread_b start run");
        });
​
        thread_a.start();
        thread_b.start();
​
        System.out.println("wait all child thread run over");
​
//        thread_a.join();
//        thread_b.join();
​
        System.out.println("all child thread run over");
    }
}

wait all child thread run over all child thread run over thread_a start run thread_b start run

Process finished with exit code 0

那么 join 方法的作用其实就变得很明显了,就是为了阻塞当前的线程,直到子线程的任务执行完毕。

我们可以将上面的第一部分的代码做一个小小的改动

public class MyThreadThree {
    public static void main(String[] args) throws InterruptedException {
        Thread thread_a=new Thread(()->{
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread_a start run");
​
        });
        Thread thread_b=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread_b start run");
        });
​
        thread_a.start();
        thread_b.start();
​
        System.out.println("wait all child thread run over");
​
        thread_a.join();
        thread_b.join();
​
        System.out.println("all child thread run over");
    }
}

这样保证了线程打印的执行输出顺序为 main -> thread_a -> thread_b -> main

wait all child thread run over thread_b start run thread_a start run all child thread run over

Process finished with exit code 0

既然 join 可以阻塞当前的线程直到,调用 join 方法的线程执行完毕并返回后,才继续执行当前的线程,那么我们可以通过 join 来控制,线程 a 与线程 b 的执行是顺序。

我们对以上的代码做一稍微的修改即可。

public class MyThreadThree {
    public static void main(String[] args) throws InterruptedException {
        Thread thread_a=new Thread(()->{
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread_a start run");
​
        });
        Thread thread_b=new Thread(()->{
            try {
                //阻塞当前的线程 让其线程 a 先执行完毕
                thread_a.join();
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread_b start run");
        });
​
        thread_a.start();
        thread_b.start();
​
        System.out.println("wait all child thread run over");
​
        thread_a.join();
        thread_b.join();
​
        System.out.println("all child thread run over");
    }
}

执行的结果为:

wait all child thread run over thread_a start run thread_b start run all child thread run over

Process finished with exit code 0

可以看到线程 b 在执行到 thread_a.join() 的时候,线程 b 处于停滞阻塞的状态,等待线程 a 执行完毕,后线程的 b 才继续执行。

以上的两段程序执行所占用的时间也是不同的,第一段程序执行所占用的最大时间大致为 2 s,第二段程序执行完毕所占用的最大时间大致为 2 + 1 = 3 s。

【思考】

那么在多线程中提供这样的 join 阻塞当前调用线程的方法作用是什么呢?

在 《Java并发编程之美》-1.4小节[等待线程执行终止的 join 方法]中是这样解释的:

image-20221228101655039

好赖,就这样咯.

让线程睡眠的 sleep() 方法

执行下面的代码会发生什么?

  • sleep() 会让当前的线程进入睡眠的状态,该状态下的线程不参与 CPU 时间片的调度,但该线程拥有监视器资源,锁资源还是持有不让出的,当到达了指定的唤醒时间后,线程重新进入就绪状态,然后继续参与 CPU 时间片的调度.
public class ThreadFour {
​
    private static final Lock lock = new ReentrantLock();
​
    public static void main(String[] args) {
​
        Thread thread_a= new Thread(()->{
            lock.lock();
            try {
                System.out.println("thread_a is sleep");
                Thread.sleep(1000);
                System.out.println("thread_a is awaked");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
​
        });
        Thread thread_b=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.lock();
            try {
                System.out.println("thread_b is sleep");
                Thread.sleep(1000);
                System.out.println("thread_b is awaked");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        });
​
        thread_a.start();
        thread_b.start();
    }
}

thread_a is sleep thread_a is awaked thread_b is sleep thread_b is awaked

Process finished with exit code 0

如果在当前的线程处于睡眠的状态时,其他线程调用了当前线程的 interrupt() 方法,则会导致当前的线程抛出 InterruptedException 的中断异常.

CPU 让出执行的权的 yield 方法

public class YieldTest implements Runnable {
​
    YieldTest() {
        Thread thread = new Thread(this);
        thread.start();
    }
​
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            if (i % 5 == 0) {
                System.out.println(Thread.currentThread() + "yield CPU ... ");
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(Thread.currentThread() + "over");
    }
}
​
class test {
    public static void main(String[] args) {
        new YieldTest();
        new YieldTest();
        new YieldTest();
    }
}
public class YieldTest implements Runnable {
​
    YieldTest() {
        Thread thread = new Thread(this);
        thread.start();
    }
​
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            if (i % 5 == 0) {
                System.out.println(Thread.currentThread() + "yield CPU ... ");
                 Thread.yield();
            }
        }
        System.out.println(Thread.currentThread() + "over");
    }
}
​
class test {
    public static void main(String[] args) {
        new YieldTest();
        new YieldTest();
        new YieldTest();
    }
}

对比这两段代码可以发现什么?

第一段的代码在调用了 sleep(1) 方法后进入阻塞的状态,并且等待阻塞时间过期然后唤醒。(这种的情况出现的次数最多)

Thread[Thread-2,5,main]yield CPU … Thread[Thread-2,5,main]over Thread[Thread-0,5,main]yield CPU … Thread[Thread-1,5,main]yield CPU … Thread[Thread-0,5,main]over Thread[Thread-1,5,main]over

Process finished with exit code 0

第二段代码在调用 yield() 方法使当前线程让出 CPU 的执行时间片,线程调度器会从线程的队列中优先选出一个优先级别最高的线程进行执行,当前这个期间也有可能调用到刚刚让出 CPU 执行的时间片的线程。

所以第二段的代码片段在执行之后的结果是这样的:

*Thread[Thread-2,5,main]yield CPU … **Thread[Thread-1,5,main]yield CPU … **Thread[Thread-0,5,main]yield CPU … *Thread[Thread-1,5,main]over Thread[Thread-0,5,main]over Thread[Thread-2,5,main]over

Process finished with exit code 0

  • sleep()方法暂停当前线程后,会给其他线程执行机会,不区分其他线程的优先级;但yield()方法只会给优先级相同,或优先级更高的线程执行机会。(优先级区分)
  • sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程被yield()方法暂停之后,立即再次获得处理器资源被执行。(运行状态区分)
  • sleep()方法声明抛出了InterruptedException异常,所以调用sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;而yield()方法则没有声明抛出任何异常。(抛出异常区分)

线程的中断

public class MyThreadFired {
    public static void main(String[] args) throws InterruptedException {
        Thread thread_a = new Thread(() -> {
            //获取当前的线程的中断标志并且重置
            while (!Thread.currentThread().interrupted()){
​
            }
            //获取当前线程的中断标志
            System.out.println("thread isInterrupted: " + Thread.currentThread().isInterrupted());
        });
        thread_a.start();
        //设置线程的中断标志
        thread_a.interrupt();
        Thread.sleep(1000);
        thread_a.join();
        System.out.println("main thread is over");
    }
}

执行结果:

thread isInterrupted: false main thread is over

Process finished with exit code 0

线程死锁

资源占用导致线程死锁

public class MyThreadLock {

    private static Object obj1=new Object();
    private static Object obj2=new Object();

    public static void main(String[] args) {
      Thread thread_a=  new Thread(()->{
            synchronized (obj1){
                System.out.println(Thread.currentThread() + "get obj1 Lock");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2){
                    System.out.println(Thread.currentThread() + "get obj2 Lock");
                }
            }
        });
      Thread thread_b=new Thread(()->{
          synchronized (obj2){
              System.out.println(Thread.currentThread()+"get obj2 Lock");
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              synchronized (obj1){
                  System.out.println(Thread.currentThread() + "get obj1 Lock");
              }
          }
      });

      thread_a.start();
      thread_b.start();
    }
}

Thread[Thread-0,5,main]get obj1 Lock Thread[Thread-1,5,main]get obj2 Lock

ThreadLocal 实现原理

先看两组代码

public class MyThreadLocalTest {

    static void print(String str) {
        //获取本地变量中的值
        System.out.println(str+":"+threadLocal.get());
        //清除本地变量中的值
//        threadLocal.remove();
    }

    //创建本地变量
    static ThreadLocal<String> threadLocal=new ThreadLocal<>();
    public static void main(String[] args) {
        Thread threadOne=new Thread(()->{
            //线程 One 获取本地线程变量中的值
            System.out.println("threadOne threadLocal vault "+threadLocal.get());
            //设置本地线程变量中的值
            threadLocal.set("卡卡罗特来咯!!!");
            threadLocal.set("卡卡罗特来咯!!!与他的儿子悟饭");
            //调用打印函数
            print("threadOne thread ");
            //再次打印本地线程变量中的值
            System.out.println("threadOne two threadLocal vault "+threadLocal.get());
        });

        Thread threadTwo=new Thread(()->{
            //获取本地线程变量中的值
            System.out.println("threadTwo threadLocal vault "+threadLocal.get());
            //设置本地线程变量值
            threadLocal.set("骄傲的贝吉塔!!!");
            print("threadTwo thread");

            System.out.println("threadTwo two threadLocal vault "+threadLocal.get());

        });


        threadOne.start();
        threadTwo.start();

    }
}

threadTwo threadLocal vault null threadOne threadLocal vault null *threadTwo thread:骄傲的贝吉塔!!! **threadTwo two threadLocal vault 骄傲的贝吉塔!!! *threadOne thread :卡卡罗特来咯!!!与他的儿子悟饭 threadOne two threadLocal vault 卡卡罗特来咯!!!与他的儿子悟饭

Process finished with exit code 0

public class MyThreadLocalTest {

    static void print(String str) {
        //获取本地变量中的值
        System.out.println(str+":"+threadLocal.get());
        //清除本地变量中的值
        threadLocal.remove();
    }

    //创建本地变量
    static ThreadLocal<String> threadLocal=new ThreadLocal<>();
    public static void main(String[] args) {
        
        Thread threadOne=new Thread(()->{
            //线程 One 获取本地线程变量中的值
            System.out.println("threadOne threadLocal vault "+threadLocal.get());
            //设置本地线程变量中的值
            threadLocal.set("卡卡罗特来咯!!!");
            threadLocal.set("卡卡罗特来咯!!!与他的儿子悟饭");
            //调用打印函数
            print("threadOne thread ");
            //再次打印本地线程变量中的值
            System.out.println("threadOne two threadLocal vault "+threadLocal.get());
        });

        Thread threadTwo=new Thread(()->{
            //获取本地线程变量中的值
            System.out.println("threadTwo threadLocal vault "+threadLocal.get());
            //设置本地线程变量值
            threadLocal.set("骄傲的贝吉塔!!!");
            print("threadTwo thread");

            System.out.println("threadTwo two threadLocal vault "+threadLocal.get());

        });

        threadOne.start();
        threadTwo.start();

    }
}

输出结果:

threadTwo threadLocal vault null *threadTwo thread:骄傲的贝吉塔!!! *threadTwo two threadLocal vault null threadOne threadLocal vault null threadOne thread :卡卡罗特来咯!!!与他的儿子悟饭 threadOne two threadLocal vault null

Process finished with exit code 0

代码分析:

两组代码都是调用了本地线程变量 threadLocal 并设置了不同的值。其区别在于第一段代码在调用 print 方法时执行了threadLocal.remove() 第二组代码中未执行,所以在第二组代码中,对于每一个线程而言,最后的第二次输出的本地线程变量中都是有其固定的值存在的。

问题:

  1. 设计 ThreadLocal 本地线程变量的目的与什么?
  2. 两个线程调用的是同一个 ThreadLocal 对象的引用都执行的 set 方法,其对于没一个线程而言是怎么保证其内部在调用 get 方法获取本地变量的时候不会发生混乱的呢?

首先第一个问题很好解决:

先了解一点,多线程环境下在操作同一个数据的时候最容易出现的问题是什么,对,就是在同一时间段内多个线程并发执行最终导致唯一的数据对象产生混乱,为了解决这一个问题,在多线程环境下引入了线程锁机制,保证一个数据对象在同一时间点只有一个线程去操作,保证数据的唯一性。但这样同样的也有一个问题的存在,锁机制简单的理解就是将之前的程序的执行由并行切换成了串行,这样如果并发量如果较高的情况下就会导致,程序的执行效率大幅度的降低。

其实对于这一问题 JDK 内部就提供一种解决方案,就是 ThreadLocal 线程本地变量。

通过字面意思就可以很好的理解,线程本地变量,就是对于每一个线程都有一个属于自己的存储在内存中的本地变量,也就是每一个线程内部的该变量都是独立的。

这里举个例子:

public class MyThreadLocalTestTwo {

    //构造一个公有变量

    private static String str = "";

    public static void main(String[] args) throws InterruptedException {
        //创建线程 A
        Thread thread_A = new Thread(() -> {
            if (str.equals("")) {
                //线程 A 对变量 str 进行修改
                str = "卡卡罗特";
                System.out.println(Thread.currentThread() + "===>" + str);
            }
        }, "thread_A");

        //创建线程 B
        Thread thread_B = new Thread(() -> {
            if (str.equals("")) {
                //线程 B 对变量 str 进行修改
                str = "特兰克斯";
                System.out.println(Thread.currentThread() + "===>" + str);
            }
        }, "thread_B");

        thread_A.start();
        thread_B.start();

    }
}

执行以上代码输出的结果是什么样呢?可能会出现以下结果:

Thread[thread_A,5,main]===>特兰克斯 Thread[thread_B,5,main]===>特兰克斯

Process finished with exit code 0

Thread[thread_B,5,main]===>特兰克斯

Process finished with exit code 0

Thread[thread_A,5,main]===>卡卡罗特

Process finished with exit code 0

这个与我们期望的理想输出的值存在差异,我们理想期望输出的为两个线程同时执行

线程 A 输出 卡卡罗特 ,线程 B 输出 特兰克斯

出现以上的结果就是应该共享变量在线程的并发情况下出现的混乱,我们可以使用 ThreadLocal 线程局部变量来处理

public class MyThreadLocalTestTwo {

    //构造一个公有变量

    private static String str = "";

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

    public static void main(String[] args) throws InterruptedException {
        //创建线程 A
        Thread thread_A = new Thread(() -> {
            threadLocal.set(str);
            if (threadLocal.get().equals("")) {
                //线程 A 对变量 str 进行修改
                threadLocal.set("卡卡罗特");
                System.out.println(Thread.currentThread() + "===>" + threadLocal.get());
            }
        }, "thread_A");

        //创建线程 B
        Thread thread_B = new Thread(() -> {
            threadLocal.set(str);
            if (threadLocal.get().equals("")) {
                //线程 A 对变量 str 进行修改
                threadLocal.set("特兰克斯");
                System.out.println(Thread.currentThread() + "===>" + threadLocal.get());
            }
        }, "thread_B");

        thread_A.start();
        thread_B.start();

    }
}

Thread[thread_B,5,main]===>特兰克斯 Thread[thread_A,5,main]===>卡卡罗特

Process finished with exit code 0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java并发核心知识体系是指在Java编程语言中,用于处理多线程编程的一系列核心知识和技术。Xmind是一种思维导图工具,可以帮助我们清晰地组织和呈现这些知识体系。 Java并发核心知识体系包括以下内容: 1.线程基础知识:了解线程概念、线程创建和启动、线程状态转换等基本概念和操作。 2.线程安全:学习如何确保多个线程访问共享资源时的线程安全性,如使用锁、同步关键字、volatile关键字等。 3.锁和同步:深入研究各种锁的实现原理,比如synchronized关键字、ReentrantLock、ReadWriteLock等,并学习如何正确使用它们。 4.并发集合:了解Java中提供的线程安全的集合类,如ConcurrentHashMap、CopyOnWriteArrayList等。 5.线程通信:学习线程之间的协作和通信,包括使用wait()、notify()、notifyAll()等方法实现等待、通知机制。 6.线程池:学习如何使用线程池来管理和调度线程,提高线程的执行效率和资源利用率。 7.并发工具类:研究一些常用的并发工具类,如Semaphore、CountDownLatch、CyclicBarrier等。 8.原子操作:了解Java提供的原子操作类,如AtomicInteger、AtomicLong等,可以保证某些操作的原子性。 9.并发模型:掌握几种常用的线程并发模型,如生产者消费者模型、读写者模型等。 Xmind可以帮助我们将以上知识整理成一张思维导图,以便更好地理解和记忆。我们可以用中心主题为“Java并发核心知识体系”,然后分支出各个子主题,如“线程基础知识”、“线程安全”、“锁和同步”等,再进一步细分为各个具体的知识点。通过这样清晰的组织结构,我们可以更加系统地学习和理解Java并发编程的核心知识。 ### 回答2: Java并发核心知识体系精讲xmind是一份专门用于讲解Java并发编程的思维导图。它通过图形化的方式系统地呈现了Java并发编程的核心知识,方便学习者理解和记忆。以下是对Java并发核心知识体系精讲xmind的回答: Java并发核心知识体系精讲xmind是一份非常有价值的学习资料,它对Java并发编程的相关知识进行了详细的整理和总结。通过该xmind文件,学习者可以快速了解并发编程的基本概念、原理和常用工具类,深入了解多线程线程安全和锁机制等重要的内容。 该xmind文件首先介绍了并发编程的基本概念,如进程、线程和并发的概念,并讲解了线程的生命周期和线程的创建、启动、暂停、终止等操作。接着,该文件详细讲解了Java提供的并发编程的核心类,包括Thread、Runnable、Callable、Lock、Condition等,以及线程池和计数器等常用的并发工具类。 该xmind文件还深入讨论了Java并发编程中的一些重点内容,比如线程安全、原子性、可见性和有序性等问题。它解释了线程安全的概念,以及Java中如何实现线程安全,如使用同步机制、锁机制和原子类等方式。此外,该文件还介绍了线程间的通信方式,包括共享内存和消息传递。 在最后,该xmind文件还介绍了一些高级的并发编程技术,比如并发集合类、并发控制和并发算法等。它详细讲解了Java中提供的并发集合类,如ConcurrentHashMap和ConcurrentLinkedQueue等,并解释了它们的设计原理和使用方法。此外,该文件还介绍了一些常见的并发控制和并发算法,如信号量和读写锁等。 综上所述,Java并发核心知识体系精讲xmind是一份非常有价值的学习资料,对于掌握Java并发编程知识和提高多线程编程能力非常有帮助。通过系统地学习该xmind文件,可以更好地理解并发编程的原理和应用,提高并发编程的技术水平。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值