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();
        }
    }
}

时间: 2014-12-03

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解

Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解 在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch,CyclicBarrier和Semaphore,今天我们就来学习一下这三个辅助类的用法. 以下是本文目录大纲: 一.CountDownLatch用法 二.CyclicBarrier用法 三.Semaphore用法 若有不正之处请多多谅解,并欢迎批评指正. 一.CountDownLatch

Java并发编程(CyclicBarrier)实例详解

Java并发编程(CyclicBarrier)实例详解 前言: 使用JAVA编写并发程序的时候,我们需要仔细去思考一下并发流程的控制,如何让各个线程之间协作完成某项工作.有时候,我们启动N个线程去做一件事情,只有当这N个线程都达到某一个临界点的时候,我们才能继续下面的工作,就是说如果这N个线程中的某一个线程先到达预先定义好的临界点,它必须等待其他N-1线程也到达这个临界点,接下来的工作才能继续,只要这N个线程中有1个线程没有到达所谓的临界点,其他线程就算抢先到达了临界点,也只能等待,只有所有这N

Java并发编程之闭锁与栅栏的实现

一.前言 闭锁与栅栏是在多线程编程中的概念,因为在多线程中,我们不能控制线程的执行状态,所以给线程加锁,让其按照我们的想法有秩序的执行. 闭锁 CountDownLatch,实例化时需要传入一个int类型的数字(count),意为等待count个线程完成之后才能执行下一步动作. 如今天要做的事情是吃晚饭,再去散步.假设11个人相约晚饭后一起去散步,我们得等11个人全都吃完晚饭了才能出发去散步.简而言之就是做了才到达某一种状态. 栅栏 CyclicBarrier,实例化时需要传入一个int类型的数

Java并发编程Callable与Future的应用实例代码

本文主要探究的是java并发编程callable与future的使用,分享了相关实例代码,具体介绍如下. 我们都知道实现多线程有2种方式,一种是继承Thread,一种是实现Runnable,但这2种方式都有一个缺陷,在任务完成后无法获取返回结果.要想获得返回结果,就得使用Callable,Callable任务可以有返回值,但是没法直接从Callable任务里获取返回值:想要获取Callabel任务的返回值,需要用到Future.所以Callable任务和Future模式,通常结合起来使用. 试想

java并发编程专题(九)----(JUC)浅析CyclicBarrier

上一篇我们介绍了CountDownlatch,我们知道CountDownlatch是"在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待",即CountDownLatch的作用是允许1或N个线程等待其他线程完成执行,而我们今天要介绍的CyclicBarrier则是允许N个线程相互等待. 1.CyclicBarrier简介 CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)

java并发编程专题(八)----(JUC)实例讲解CountDownLatch

CountDownLatch 是一个非常实用的多线程控制工具类." Count Down " 在英文中意为倒计数, Latch 为门问的意思.如果翻译成为倒计数门阀, 我想大家都会觉得不知所云吧! 因此,这里简单地称之为倒计数器.在这里, 门问的含义是:把门锁起来,不让里面的线程跑出来.因此,这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束, 再开始执行. CountDown Latch 的构造函数接收一个整数作为参数,即当前这个计数器的计数个数. public Co

java并发编程实例分析

java并发编程是java程序设计语言的一块重点,在大部分的业务场景中都需要并发编程. 比如:并发的去处理http请求,这样就可以使得一台机器同时处理多个请求,大大提高业务的响应效率,从而使用用户体验更加流畅. java如何并发编程,要注意以下几个方面: 1.java语言中的多线程操作:创建和启动线程的几种方式. 2.共享变量的同步问题,要保证线程安全,辨别哪些变量是线程安全的.那些变量是线程不安全的,对于不安全的变量我们要想办法让其同步,一般也就是加锁. 3.线程锁:包括方法锁和synchro

Java 并发编程:volatile的使用及其原理解析

Java并发编程系列[未完]: •Java 并发编程:核心理论 •Java并发编程:Synchronized及其实现原理 •Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) •Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) •Java 并发编程:volatile的使用及其原理 一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchroniz

深入分析java并发编程中volatile的实现原理

引言 在多线程并发编程中synchronized和Volatile都扮演着重要的角色,Volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的"可见性".可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值.它在某些情况下比synchronized的开销更小,本文将深入分析在硬件层面上Inter处理器是如何实现Volatile的,通过深入分析能帮助我们正确的使用Volatile变量. 术语定义 术语 英文单词 描述 共享变量 在多个线