从修行者角度理解java中的多线程

对于多线程的解释有很多书籍和文章,但是都不能通俗易懂的让多数人快速理解,主要就是举得例子未能完全贴合实际生活,既然Java是面向对象,它的所有设计和思想都可以从现实生活中找到答案,我也是很不喜欢很多书籍一上来就写的跟天书一样的晦涩难懂,尤其官方文档更不用说了,当然是为了严谨,但是我们在学习java理解的时候,必须要做到知其然并知其所以然,而不是记一个标准答案,或者死记硬背,这样是无法达到知命境界的。

一、成为修行者

从下面开始我们进入一个全新的世界来理解java的多线程

首先你只是一个凡人,但是机缘巧合之下,你被java的一个主线程附体,变成了修行者,达到了初始境界,看着这个世界来了一句,hello world。

public class Me {

    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

二、创建多线程

虽然成了修行者还是得吃饭,可是传统的做饭一个人只能一样样的做,可不可以同时完成这些工作呢,比如一边洗菜,一边炒菜,一边淘米,一边煮饭,而这些如何能同时进行呢?

这时候你翻看java卷天书,从而发现了以下一段秘籍,你创建了多线程,来解决了这个问题

/**
 * @author xiaomianyang
 * @description
 * @date 2020-02-06 12:30
 */
public class Me {

    public static void main(String[] args) throws Exception{
        String worker[]={"我是洗菜的","我是炒菜的","我是淘米的","我是煮饭的"};
        for(int i=0;i<worker.length;i++){
            Work work=new Work(worker[i]);
            Thread thread=new Thread(work);
            thread.start();
        }
    }
}

class Work implements Runnable{

    private String out;

    public Work(String out) {
        this.out = out;
    }

    @Override
    public void run() {
        try{
            Thread.sleep(1000);
            System.out.println(this.out);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}

三、线程为什么不按顺序执行

你分身出来四个人都开始帮你干活了,可是发现一个问题,他们没有按照顺序执行自己的工作,无论执行多少次都会发现执行顺序总是在发生变化。

为什么没有按照顺序执行呢,理想中不是谁先start,谁先开始吗?为啥会这样呢?首先从面向对象角度来解释,因为这四个分身都是你分出来的,虽然看上去这四个分身都拥有自己的想法,看上去四个是同时在做事情,实际上他们用的都是你这一个大脑,只是作为修行者,大脑控制频率切换比较快,看上去四个人都是同时在干活,实际上在同一时刻大脑只能控制一个人在工作,属于并发行为,而感官上他们似乎是同时发生的,从计算机角度也是一样的,你的大脑相当于CPU在真正的同一时刻只有一个线程再跑,只是CPU切换比较快,所以感觉好像是并行实则是并发运行,所以他们不能按顺序就是因为CPU在随机切换。

如何解决这个问题呢,既然是你创建的分身你肯定希望它按照自己的执行顺序执行,于是在翻看天书发现找到了join指令,那你就给每个分身说,你干完了他在干活。

join就是阻塞当前线程,直到当前线程执行完后才执行下一个线程。

public class Me {

    public static void main(String[] args) throws Exception{
        String worker[]={"我是洗菜的","我是炒菜的","我是淘米的","我是煮饭的"};
        for(int i=0;i<worker.length;i++){
            Work work=new Work(worker[i]);
            Thread thread=new Thread(work);
            thread.start();
            thread.join();
        }
    }
}

四、可重入锁可以干什么

的确达到想要的效果了,你分身出来了四个人同时在帮你干活,而你什么都不用做,可是你发现炒菜的没有菜却在炒菜,煮米的没有米却在煮米,因为洗菜的还把菜没有洗好,而淘米的也没有弄好米,这就导致炒菜的和煮米的在干无意义的工作,而且还浪费水浪费火,这不就是浪费资源吗?那么问题来了,怎么让洗完菜通知炒菜的,洗完米的通知煮饭的呢?这就涉及到了线程之间的通信,让线程互相交流而合理完成工作,我们可以利用可重入锁实现这个功能

要利用可重入锁,我们创建四个线程,采用java8的新语法创建,这里我们就让这四个线程协调通信完成工作,那为啥不用join,因为join是阻塞式的,事实上,洗菜和淘米是可以同时进行的,但是用了join,就得等前面的一个个执行完,那么利用可重入锁就可以很好的解决这个问题了

对于锁的释放要写在finally里面,防止死锁

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author xiaomianyang
 * @description
 * @date 2020-02-06 12:30
 */
public class Me {

    private static Lock lock = new ReentrantLock();
    private static Lock lock2 = new ReentrantLock();
    private static Condition condition1 = lock.newCondition();
    private static Condition condition2 = lock2.newCondition();

    private static Boolean one=false;
    private static Boolean three=false;

    public static void main(String[] args){
        new Thread(() -> {
            //加锁,执行完成后发送条件信号
            lock.lock();
            System.out.println("我是洗菜的");
            one=true;
            condition1.signal();
            lock.unlock();
        }).start();

        new Thread(() -> {
            try {
                lock.lock();
                //如果一号没有执行完成就继续等待 否则 执行任务并释放锁
                if (!one) {
                    condition1.await();
                }
                System.out.println("我是炒菜的");
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
            
        }).start();

        new Thread(() -> {
            lock2.lock();
            System.out.println("我是淘米的");
            three=true;
            condition2.signal();
            lock2.unlock();
        }).start();

        new Thread(() -> {
            try {
                lock2.lock();
                if (!three) {
                    condition2.await();
                }
                System.out.println("我是煮饭的");
            }catch(InterruptedException e){
                e.printStackTrace();
            }finally {
                lock2.unlock();
            }
        }).start();
    }
}

查看输出结果,的确按照我们想要的方式执行了

五、线程的停止

比如分身中的其中一个在炒菜,炒了五盘菜的时候,我不想让他炒了,那就得把他停止掉,该如何停止掉呢?

可以使用interrupt对当前线程终止,但是这个方法并不会立即终止该线程,所以我们使用Thread.currentThread().isInterrupted()判断是否有中断信号,以此来停止当前线程,这样程序就会break跳出循环,程序的不执行也就是真正意义上的线程终止

        new Thread(() -> {
            try {
                lock.lock();
                //如果一号没有执行完成就继续等待 否则 执行任务并释放锁
                if (!one) {
                    condition1.await();
                }
                for(int i=0;i<50;i++) {
                    if(Thread.currentThread().isInterrupted()){
                        break;
                    }
                    System.out.println("我是炒菜的"+i);
                    Thread.sleep(1000);
                    if(i==5){
                        Thread.currentThread().interrupt();
                    }
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }

        }).start();

那么再来一个问题,如果我想让煮米的人发现炒菜的人炒了10盘菜后,告诉炒菜的人停止炒菜该怎么做呢?

发表评论