Java并发编程示例(一):线程的创建和执行

开门见山

在IT圈里,每当我们谈论并发时,必定会说起在一台计算机上同时运行的一系列线程。如果这台电脑上有多个处理器或者是一个多核处理器,那么这时是实实在在的“同时运行”;但是,如果计算机只有一个单核处理器,那么这时的“同时运行”只是表象而已。

所有的现代操作系统全部支持任务的并发执行。你可以边听音乐,边上网看新闻,还不耽误首发电子邮件。我们可以说,这种并发是 进程级并发 。在进程内部,我也可以看到有许许多多的并发任务。我们把运行在一个进程里面的并发任务称 线程。

和并发相关的另外一个常见概念是 并行。并发与并行之间,存在着一些不同,也存在着一些联系。一些程序员(Author,窃译为“程序员”)认为,在一个单核处理器上多线程地执行应用程序就是并发,并且你可以观察到程序员的执行;另外,当你的程序以多线程的形式运行在多个处理器或者是多核处理器上时,就是并行。还有一些程序员认为如果应用程序的线程没有按照预先设定好的顺序执行就是并发;为了简化问题解决方案而是用个线程,并且这些线程是按照一定顺序在执行,那么这是并行。

本章将通过十二个示例来演示如何使用Java7的API来执行一些基本的线程操作。你将可以看到,在Java程序中,如何创建、执行线程,如何控制线程的执行,如何将一组线程作为一个单元来操纵等等。

在本节,我们将学习如何在Java程序中创建线程,以及如何运行。在Java程序中,一切皆为 Object ,线程也是如此。创建线程的方式有两种:

1.继承Thread类,并且重写run()方法;
2.创建一个类,实现Runnable接口,然后创建一个Thread类的对象,然后将实现Runnable接口的类的实例作为参数,传递给Thread类的实例。

在本节,我们将使用第二种方式,来创建十个线程,并且运行起来。每个线程计算并打印两个十以内的整数之积。

知其然

根据下面所述的步骤来实现这里例子:

1.创建一个名为Calculator的类,并且实现Runnable接口。代码如下:

代码如下:

public class Calculator implements Runnable {

2.声明一个私有的整形属性,名称为number,实现该类的构造函数来初始化刚刚声明的属性。代码如下:

代码如下:

private int number;

public Calculator(int number) {
    this.number = number;
}

3.实现run()方法,该方法是我们创建的线程执行时运行的程序(instruction),故而该方法用于计算乘法表。具体代码如下:

代码如下:

@Override
public void run() {
    for (int i = 0; i < 10; i++) {
        System.out.printf("%s: %d * %d = %d\n",
                Thread.currentThread().getName(),
                number, i, i * number);
    }
}

4.现在,是时候实现示例应用的主类(main class)了。创建名为Main的类,在该类中添加main方法。代码如下:

代码如下:

public class Main {
    public static void main(String[] args) {

5.在main()方法内部,创建一个遍历十次的for循环,在循环体内,创建一个Calculator类的对象calculator,创建一个Thread类的对象thread,将calculator作为构造函数的参数,传递给thread的初始化语句。最后,调用thread对象的start()方法。代码如下:

代码如下:

for (int i = 0; i < 10; i++) {
    Calculator calculator = new Calculator(i);
    Thread thread = new Thread(calculator);
    thread.start();
}

6.运行这个程序,看不同线程是如何并发执行的。

知其所以然

下面是运行程序时,控制台打印出来的的一段输出,我们可以看到我们创建的所有线程都在并发执行。

代码如下:

Thread-3: 3 * 5 = 15
Thread-0: 0 * 2 = 0
Thread-3: 3 * 6 = 18
Thread-1: 1 * 6 = 6
Thread-1: 1 * 7 = 7
Thread-3: 3 * 7 = 21
Thread-3: 3 * 8 = 24
Thread-0: 0 * 3 = 0
Thread-0: 0 * 4 = 0
Thread-3: 3 * 9 = 27
Thread-1: 1 * 8 = 8

所有的Java程序最少执行一个线程。当我们运行Java程序时,Java虚拟机(以后称为JVM)会运行一个线程,调用含有main()方法的程序。

当调用Thread对象的start()方法时,就会创建另外一个线程。调用多少次start()方法,就会创建多少个线程。

当所有线程执行完成后,Java程序会随之终止。(非特殊情况下,是所有非后台(non-daemon)线程执行完成)当启动线程(例如执行main()方法的线程)终止后,其余线程会继续执行直到完成计算任务。当其中一个线程调用System.exit(),请求JVM中止程序时,所有线程中止其执行。

调用Thread对象的run()方法时,不会创建线程;同样,调用实现Runnable接口的类run()方法时,也不会创建线程。只有调用Thread对象的start()方法时,才会创建线程。

永无止境

正如本节开头所说,还有另外一种创建线程的方法:继承Thread类,重写run()方法,这样,就可以创建一个Thread子类的对象,然后调用该对象的start()方法来创建线程。

代码如下:

因为准备面试,找来一堆Java多线程方面的资料,其中包括这本《Java 7 Concurrency Cookbook》,讲解的非常浅显易懂,非常适合对多线程了解不多,又想认真学习一下的朋友。找了找,没找到中文版,干脆自己动手丰衣足食。所以,计划出一个非官方翻译版,书名暂时定为 《Java7并发示例集》。

拿来主义

本文是从 《Java 7 Concurrency Cookbook》 (D瓜哥窃译为 《Java7并发示例集》 )翻译而来,仅作为学习资料使用。没有授权,不得用于任何商业行为。

小有所成

原书没有完整代码,不利于查看。所以,D瓜哥加了一个小节,专门展示本节所示的完整版代码。

Calculator类的完整代码

代码如下:

package com.diguage.books.concurrencycookbook.chapter1.recipe1;

/**
 * Date: 2013-09-13
 * Time: 21:42
 */
public class Calculator implements Runnable {
    private int number;

public Calculator(int number) {
        this.number = number;
    }

@Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.printf("%s: %d * %d = %d\n",
                    Thread.currentThread().getName(),
                    number, i, i * number);
        }
    }
}

Main类的完整代码

代码如下:

package com.diguage.books.concurrencycookbook.chapter1.recipe1;

/**
 * Date: 2013-09-13
 * Time: 19:46
 */
public class Main {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Calculator calculator = new Calculator(i);
            Thread thread = new Thread(calculator);
            thread.start();
        }
    }
}

(0)

相关推荐

  • Java并发编程示例(九):本地线程变量的使用

    共享数据是并发程序最关键的特性之一.对于无论是继承Thread类的对象,还是实现Runnable接口的对象,这都是一个非常周重要的方面. 如果创建了一个实现Runnable接口的类的对象,并使用该对象启动了一系列的线程,则所有这些线程共享相同的属性.换句话说,如果一个线程修改了一个属性,则其余所有线程都会受此改变的影响. 有时,我们更希望能在线程内单独使用,而不和其他使用同一对象启动的线程共享.Java并发接口提供了一种很清晰的机制来满足此需求,该机制称为本地线程变量.该机制的性能也非常可观.

  • Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁

    在Java5.0之前,只有synchronized(内置锁)和volatile. Java5.0后引入了显示锁ReentrantLock. ReentrantLock概况 ReentrantLock是可重入的锁,它不同于内置锁, 它在每次使用都需要显示的加锁和解锁, 而且提供了更高级的特性:公平锁, 定时锁, 有条件锁, 可轮询锁, 可中断锁. 可以有效避免死锁的活跃性问题.ReentrantLock实现了 Lock接口: 复制代码 代码如下: public interface Lock {  

  • Java并发编程示例(六):等待线程执行终止

    在某些场景下,我们必须等待线程执行完成才能进行下一步工作.例如,某些程序在开始执行之前,需要先初始化一些资源.这时,我们可以启动一个线程专门来做初始化任务,等到线程任务完成后,再去执行其他部分. 为此,Thread类为我们提供了join()方法.当我们使用线程对象调用此方法时,正在掉调用的线程对象将被推迟到被调用对象执行完成后再开始执行. 在本节,示例程序演示等待初始化方法完成后,再去执行其他任务. 知其然 按照下面所示步骤,完成示例程序. 1.创建一个名为DataSourcesLoader的类

  • Java并发编程之显式锁机制详解

    我们之前介绍过synchronized关键字实现程序的原子性操作,它的内部也是一种加锁和解锁机制,是一种声明式的编程方式,我们只需要对方法或者代码块进行声明,Java内部帮我们在调用方法之前和结束时加锁和解锁.而我们本篇将要介绍的显式锁是一种手动式的实现方式,程序员控制锁的具体实现,虽然现在越来越趋向于使用synchronized直接实现原子操作,但是了解了Lock接口的具体实现机制将有助于我们对synchronized的使用.本文主要涉及以下一些内容: 接口Lock的基本组成成员 可重入锁Re

  • Java并发编程示例(二):获取和设置线程信息

    Thread类包含几个属性,这些属性所表示的信息能帮助我们识别线程.观察其状态.控制其优先级等.这些线程包括如下几种: ID: 该属性表示每个线程的唯一标识: Name: 该属性存储每个线程的名称: Priority: 该属性存储每个Thread对象的优先级.线程优先级分1到10十个级别,1表示最低优先级,10表示最高优先级.并不推荐修改线程的优先级,但是如果确实有这方面的需求,也可以尝试一下. Status: 该属性存储线程的状态.线程共有六种不同的状态:新建(new).运行(runnable

  • Java并发编程示例(七):守护线程的创建和运行

    Java有一种特殊线程,守护线程,这种线程优先级特别低,只有在同一程序中的其他线程不执行时才会执行. 由于守护线程拥有这些特性,所以,一般用为为程序中的普通线程(也称为用户线程)提供服务.它们一般会有一个无限循环,或用于等待请求服务,或用于执行任务等.它们不可以做任何重要的工作,因为我们不确定他们什么时才能分配到CPU运行时间,而且当没有其他线程执行时,它们就会自动终止.这类线程的一个典型应用就是Java的垃圾回收. 在本节示例中,我们将创建两个线程,一个是普通线程,向队列中写入事件:另外一个是

  • Java并发编程示例(十):线程组

    对线程分组是Java并发API提供的一个有趣功能.我们可以将一组线程看成一个独立单元,并且可以随意操纵线程组中的线程对象.比如,可以控制一组线程来运行同样的任务,无需关心有多少线程还在运行,还可以使用一次中断调用中断所有线程的执行. Java提供了ThreadGroup类来控制一个线程组.一个线程组可以通过线程对象来创建,也可以由其他线程组来创建,生成一个树形结构的线程. 根据<Effective Java>的说明,不再建议使用ThreadGroup.建议使用Executor. --D瓜哥特此

  • 深入探究Java多线程并发编程的要点

    关键字synchronized synchronized关键可以修饰函数.函数内语句.无论它加上方法还是对象上,它取得的锁都是对象,而不是把一段代码或是函数当作锁. 1,当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一段时间只能有一个线程得到执行,而另一个线程只有等当前线程执行完以后才能执行这块代码. 2,当一个线程访问object中的一个synchronized(this)同步代码块时,其它线程仍可以访问这个object中是其它非synchr

  • Java并发编程示例(五):线程休眠与恢复

    有时,我们需要在指定的时间点中断正在执行的线程.比如,每分钟检查一次传感器状态的线程,其余时间,线程不需要做任何事情.在此期间,线程不需要使用计算机的任何资源.过了这段时间之后,并且当Java虚拟机调度了该线程,则该线程继续执行.为此,你可以使用Thread类的sleeep()方法.该方法以休眠的方式来推迟线程的执行,而且整数类型的参数则指明休眠的毫秒数.当调用sleep()方法,休眠时间结束后,Java虚拟机分配给线程CPU运行时间,线程就会继续执行. 另一种是用sleep()方法的方式是通过

  • Java并发编程之栅栏(CyclicBarrier)实例介绍

    栅栏类似闭锁,但是它们是有区别的. 1.闭锁用来等待事件,而栅栏用于等待其他线程.什么意思呢?就是说闭锁用来等待的事件就是countDown事件,只有该countDown事件执行后所有之前在等待的线程才有可能继续执行;而栅栏没有类似countDown事件控制线程的执行,只有线程的await方法能控制等待的线程执行. 2.CyclicBarrier强调的是n个线程,大家相互等待,只要有一个没完成,所有人都得等着. 场景分析:10个人去春游,规定达到一个地点后才能继续前行.代码如下 复制代码 代码如

随机推荐