转载

Java并发编程那些事儿(一) ——任务与线程

花括号MC(huakuohao-mc):关注JAVA基础编程及大数据,注重经验分享及个人成长。

这是 Java并发编程系列 的第一篇文章,因为并发编程涉及的内容太多,很难用一篇或者两篇文章就说清楚,所以会分成多篇进行讲述。

针对并发编程,我大概列了一份简要的提纲,后面的文章基本会按照这个顺序来写。具体能不能全部写完,我也不太清楚;主要是因为自己的能力水平有限,加之写技术文章确实很占用休息时间。

简要提纲

Java并发编程那些事儿(一) ——任务与线程

正文开始

提到并发编程,很多人会想到多线程;希望让多个线程共同完成一项任务,以提高生产效率。

所以要聊并发编程之前,就要先说一下什么是线程,要说什么是线程就需要说一下什么是进程。

进程:在现代操作系统中,每一个独立运行的程序都是一个进程,比如运行中的word,微信等等都是一个独立进程。

线程:在现代操作系统中,线程也叫轻量级进程,每个进程里面可以包含多个线程。CPU资源可以在多个线程之间不断切换,仿佛所有线程在并行执行。每个线程都有自己的计数器,堆栈,和局部变量等属性。这些线程也能够访问共享的内存变量。这将成为日后阻碍我们写出健壮且安全的并发程序的最大障碍。

如何理解线程和进程

上面介绍的进程和线程,对于某些人来说可能比较抽象,为了方便大家更好的理解线程和进程关系,我们可以做这样一个比喻。一个进程相当于一条运行中的汽车生产线,主要任务是生产汽车;生产线上有很多工人,每个工人负责不同的工作,有的给汽车拧螺丝,有的给车刷漆。每个工人就是一个线程。汽车就是共享变量。

任务和线程的关系

Java 里面,任务和线程是两个完全独立的概念,所谓的线程就是你通过 Thread thread = new Thread() 方式创建的一个线程,如果你只是这样创建一个线程,那么什么意义都没有。因为这个线程什么都不会做,这条语句的意义,仅仅是创建了一个线程。你可以理解成,只创建了生产线上的一个工人,但是没给工人安排任务。

我们创建线程的目的显然是为了更高效的完成一些任务。所以我们需要把任务和线程进行关联。 Java 里面的任务是通过 Runnable 或者 Callable 接口来表达的。线程是驱动任务的媒介,必须将线程和任务进行绑定才有意义,他们才能各自发挥价值,真正的做到一加一大于二的效果。如果你不理解我说的这段话,没关系,下面会通过一些代码示例帮助大家理解线程和任务之间的关系。

如何创建线程

创建线程的方式很多人都会说有两种,一种通过继承 Thread 类来实现,另外一种是实现 Runnable 接口。从某种角度来看,这么说是没有错,但我更愿意说只有一种方式。因为 Thread 类也实现了 Runnable 接口。所以归根到底还都是通过实现 Runnable 接口来描述任务,然后将任务和线程进行绑定。

什么都不做的线程
创建一个线程并启动其实很简单,只需要两行代码。但是下面的线程什么都做不了。因为我们没有让他跟任何任务关联。

1//创建线程
2Thread thread = new Thread();
3//启动线程
4thread.start();

会说 hello world 的多线程
上面新建的线程,什么工作都做不了,显然不是我们想要的,下面我们再来创建一个线程,这个线程会打印出线程名字,并且说一句hello world。

1Thread thread = new Thread(){
2    @Override
3    public void run(){
4        System.out.println(Thread.currentThread() + "hello world!");
5    }
6};
7thread.start();

上面的代码, new 了一 Thread 对象,并且重写了 run 方法,将要执行的业务逻辑写到了 run 方法里面。这样就建立了线程和任务的关系。

通过继承实现自己的Thread

我们也可以通过继承的方式自定义一个 Thread ,如下:

 1public class HelloThread extends Thread {
 2    private String name;
 3    public HelloThread(String name){
 4        this.name = name;
 5    }
 6    @Override
 7    public void run(){
 8        System.out.println( Thread.currentThread() + " hello " + name);
 9    }
10}

通过HelloThread()创建线程,并启动。

1Thread thread = new HelloThread("Frank");
2thread.start();

通过Runnable接口定义任务

上面示例都是通过重写 Thread 类的 run 方法,来完成业务逻辑的描述。我们也可以通过实现 Runnable 接口的方式,来实现业务逻辑的描述。

1public class Task implements Runnable {
2  @Override
3  public void run(){
4        System.out.println(Thread.currentThread().getName()+"hello world");
5  }
6}

Runnable Thread 进行关联,让 Thread 驱动 Runnable .

1//创建线程,并绑定相应的业务逻辑
2Thread thread = new Thread(new Task());
3//启动线程,让线程驱动任务
4thread.start();

使用Executors将线程和任务进行绑定

上面讲解的样例代码,在实际生产上很少用到,生产用的最多的是 JDK1.5 以后开始提供的 Executor 并发框架。这里先放一段代码示例,先简单感受一下,后面还会有专门的文章进行介绍。

1//创建一个容量为2的线程池
2ExecutorService exec = Executors.newFixedThreadPool(2);
3//向线程池提交5个任务
4for (int i = 0; i < 5; i++){
5    exec.execute(new Task());
6}
7exec.shutdown();

与线程相关的一些方法

yield()

yield这个单词在英语里面有放弃,停止争论的意思,所以当我们执行

Thread.yield(); 方法的时候,表示当前线程愿意尝试放弃CPU资源,但是否放弃不确定。这个方法在日常工作中很少用到,主要用于debug,或者测试。

sleep()

sleep这个单词大家应该很熟悉,睡眠的意思。所以当我们执行

Thread.sleep(1000); 表示当前线程休眠1秒钟,然后继续执行。

join()

join这个单词有连接,加入的意思。所以当调用

join() 方法的时候,表示当前线程会等待调用 join() 这个方法的线程执行完成之后在继续执行。这么说会比较抽象,下面我写个简单的例子,让各位感受一下。

定义一个任务,该任务接收一个外部传入的线程,并调用传入线程的 join() 方法

 1public class JoinTask implements Runnable{
 2    private Thread thread;
 3    public JoinTask(Thread thread){
 4        this.thread = thread;
 5    }
 6    @Override
 7    public void run() {
 8        try {
 9            thread.join();
10            } catch (InterruptedException e) {
11                e.printStackTrace();
12            }
13            System.out.println(Thread.currentThread() + "currentTime " + System.currentTimeMillis());
14    }
15}

在main方法里面启动两个线程

 1Thread thread1 = new Thread(){
 2    @Override
 3    public void run(){
 4        System.out.println( currentThread() + "currentTime " + System.currentTimeMillis());
 5        try {
 6            Thread.sleep(1000);
 7        } catch (InterruptedException e) {
 8            e.printStackTrace();
 9        }
10    }
11};
12
13Thread thread2 = new Thread(new JoinTask(thread1));
14
15//启动两个线程
16thread1.start();
17thread2.start();

这段代码的输出结果为

1Thread[Thread-0,5,main]currentTime 1588752684516
2Thread[Thread-1,5,main]currentTime 1588752685516

从结果可以看出线程2等待线程1执行结束后,才继续执行。大家可以把 join() 方法注释掉之后在运行一下,感受一下 join() 的作用。

这个方法理论上可以实现简单的任务间协作(实际生产很少这样用),比如洗完盘子才能进行烘干。烘干线程要等待洗盘子线程结束才能开始工作。

结束

好了,这篇文章就写到这里了。本篇的核心要点就是理解线程和任务的关系,知道如何将线程和任务建立联系。至于文章最后提到的几个方法,大家知道一下就OK,真实生产上如果真的要用的话,一定要特别清楚你想要做什么。下一篇按照计划会写“锁”相关的内容。

·END·

花括号MC

Java·大数据·个人成长

Java并发编程那些事儿(一) ——任务与线程

微信号:huakuohao-mc

点一下你会更好看耶

Java并发编程那些事儿(一) ——任务与线程

原文  http://mp.weixin.qq.com/s?__biz=MzUzMzE4MDY0Nw==&mid=2247483913&idx=1&sn=e6a0b88866865d6f94c1d3a0601b0d5b
正文到此结束
Loading...