Java进阶05 多线程

链接地址:http://www.cnblogs.com/vamei/archive/2013/04/15/3000898.html

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢!

多线程

多线程(multiple thread)是计算机实现多任务并行处理的一种方式。

在单线程情况下,计算机中存在一个控制权,并按照顺序依次执行指令。单线程好像是一个只有一个队长指挥的小队,整个小队同一个时间只能执行一个任务。

单线程

在多线程情境下,计算机中有多个控制权。多个控制权可以同时进行,每个控制权依次执行一系列的指令。多线程好像是一个小队中的成员同时执行不同的任务。

可参考Linux多线程与同步,并对比Python多线程与同步

多线程

传统意义上,多线程是由操作系统提供的功能。对于单核的CPU,硬件中只存在一个线程。在操作系统的控制下,CPU会在不同的任务间(线程间)切换,从而造成多任务齐头并进的效果。这是单CPU分时复用机制下的多线程。现在,随着新的硬件技术的发展,硬件本身开始提供多线程支持,比如多核和超线程技术。然而,硬件的多线程还是要接受操作系统的统一管理。在操作系统之上的多线程程序依然通用。

多个线程可以并存于同一个进程空间。在JVM的一个进程空间中,一个栈(stack)代表了方法调用的次序。对于多线程来说,进程空间中需要有多个栈,以记录不同线程的调用次序。多个栈互不影响,但所有的线程将共享堆(heap)中的对象。

创建线程

Java中“一切皆对象”,线程也被封装成一个对象。我们可以通过继承Thread类来创建线程。线程类中的的run()方法包含了该线程应该执行的指令。我们在衍生类中覆盖该方法,以便向线程说明要做的任务:

public class Test
{
    public static void main(String[] args)
    {
        NewThread thread1 = new NewThread();
        NewThread thread2 = new NewThread();
        thread1.start(); // start thread1
        thread2.start(); // start thread2
    }
}

/**
 * create new thread by inheriting Thread
 */
class NewThread extends Thread {

    private static int threadID = 0; // shared by all

    /**
     * constructor
     */
    public NewThread() {
        super("ID:" + (++threadID));
    }

    /**
     * convert object to string
     */
    public String toString() {
        return super.getName();
    }

    /**
     * what does the thread do?
     */
    public void run() {
        System.out.println(this);
    }
}

(++是Java中的累加运算符,即让变量加1。这里++出现在threadID之前,说明先将threadID加1,再对周边的表达式求值

toString是Object根类的方法,我们通过覆盖该方法,来将对象转换成字符串。当我们打印该对象时,Java将自动调用该方法。)

可以看到,Thread基类的构建方法(super())可以接收一个字符串作为参数。该字符串是该线程的名字,并使用getName()返回。

定义类之后,我们在main()方法中创建线程对象。每个线程对象为一个线程。创建线程对象后,线程还没有开始执行。

我们调用线程对象的start()方法来启动线程。start()方法可以在构造方法中调用。这样,我们一旦使用new创建线程对象,就立即执行。

Thread类还提供了下面常用方法:

join(Thread tr)   等待线程tr完成

setDaemon()       设置当前线程为后台daemon (进程结束不受daemon线程的影响)

Thread类官方文档: http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html

Runnable

实现多线程的另一个方式是实施Runnable接口,并提供run()方法。实施接口的好处是容易实现多重继承(multiple inheritance)。然而,由于内部类语法,继承Thread创建线程可以实现类似的功能。我们在下面给出一个简单的例子,而不深入:

public class Test
{
    public static void main(String[] args)
    {
        Thread thread1 = new Thread(new NewThread(), "first");
        Thread thread2 = new Thread(new NewThread(), "second");
        thread1.start(); // start thread1
        thread2.start(); // start thread2
    }
}

/**
 * create new thread by implementing Runnable
 */
class NewThread implements Runnable {
    /**
     * convert object to string
     */
    public String toString() {
        return Thread.currentThread().getName();
    }

    /**
     * what does the thread do?
     */
    public void run() {
        System.out.println(this);
    }
}

synchronized

多任务编程的难点在于多任务共享资源。对于同一个进程空间中的多个线程来说,它们都共享堆中的对象。某个线程对对象的操作,将影响到其它的线程。

在多线程编程中,要尽力避免竞争条件(racing condition),即运行结果依赖于不同线程执行的先后。线程是并发执行的,无法确定线程的先后,所以我们的程序中不应该出现竞争条件。

然而,当多任务共享资源时,就很容易造成竞争条件。我们需要将共享资源,并造成竞争条件的多个线程线性化执行,即同一时间只允许一个线程执行。

(可更多参考Linux多线程与同步)

下面是一个售票程序。3个售票亭(Booth)共同售卖100张票(Reservoir)。每个售票亭要先判断是否有余票,然后再卖出一张票。如果只剩下一张票,在一个售票亭的判断和售出两个动作之间,另一个售票亭卖出该票,那么第一个售票亭(由于已经执行过判断)依然会齿形卖出,造成票的超卖。为了解决该问题,判断和售出两个动作之间不能有“空隙”。也就是说,在一个线程完成了这两个动作之后,才能有另一个线程执行。

在Java中,我们将共享的资源置于一个对象中,比如下面r(Reservoir)对象。它包含了总共的票数;将可能造成竞争条件的,针对共享资源的操作,放在synchronized(同步)方法中,比如下面的sellTicket()。synchronized是方法的修饰符。在Java中,同一对象的synchronized方法只能同时被一个线程调用。其他线程必须等待该线程调用结束,(余下的线程之一)才能运行。这样,我们就排除了竞争条件的可能。

在main()方法中,我们将共享的资源(r对象)传递给多个线程:

public class Test
{
    public static void main(String[] args)
    {
        Reservoir r = new Reservoir(100);
        Booth b1 = new Booth(r);
        Booth b2 = new Booth(r);
        Booth b3 = new Booth(r);
    }
}

/** * contain shared resource */
class Reservoir {
    private int total;

    public Reservoir(int t)
    {
        this.total = t;
    }

    /**
     * Thread safe method
     * serialized access to Booth.total
     */
    public synchronized boolean sellTicket()
    {
        if(this.total > 0) {
            this.total = this.total - 1;
            return true; // successfully sell one
        }
        else {
            return false; // no more tickets
        }
    }
}

/**
 * create new thread by inheriting Thread
 */
class Booth extends Thread {
    private static int threadID = 0; // owned by Class object

    private Reservoir release;      // sell this reservoir
    private int count = 0;          // owned by this thread object
    /**
     * constructor
     */
    public Booth(Reservoir r) {
        super("ID:" + (++threadID));
        this.release = r;          // all threads share the same reservoir
        this.start();
    }

    /**
     * convert object to string
     */
    public String toString() {
        return super.getName();
    }

    /**
     * what does the thread do?
     */
    public void run() {
        while(true) {
            if(this.release.sellTicket()) {
                this.count = this.count + 1;
                System.out.println(this.getName() + ": sell 1");
                try {
                    sleep((int) Math.random()*100);   // random intervals
                }
                catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            else {
                break;
            }
        }
        System.out.println(this.getName() + " I sold:" + count);
    }
}

(Math.random()用于产生随机数)

Java的每个对象都自动包含有一个用于支持同步的计数器,记录synchronized方法的调用次数。线程获得该计数器,计数器加1,并执行synchronized方法。如果方法内部进一步调用了该对象的其他synchronized方法,计数器加1。当synchronized方法调用结束并退出时,计数器减1。其他线程如果也调用了同一对象的synchronized方法,必须等待该计数器变为0,才能锁定该计数器,开始执行。Java中的类同样也是对象(Class类对象)。Class类对象也包含有计数器,用于同步。

关键代码

上面,我们利用synchronized修饰符同步了整个方法。我们可以同步部分代码,而不是整个方法。这样的代码被称为关键代码(critical section)。我们使用下面的语法:

synchronized (syncObj) {

  ...;

}

花括号中包含的是想要同步的代码,syncObj是任意对象。我们将使用syncObj对象中的计数器,来同步花括号中的代码。

欢迎继续阅读“Java快速教程”系列文章

时间: 2024-08-24 05:20:11

Java进阶05 多线程的相关文章

Java进阶之多线程

多线程 多线程(multiple thread)是计算机实现多任务并行处理的一种方式. 在单线程情况下,计算机中存在一个控制权,并按照顺序依次执行指令.单线程好像是一个只有一个队长指挥的小队,整个小队同一个时间只能执行一个任务. 单线程 在多线程情境下,计算机中有多个控制权.多个控制权可以同时进行,每个控制权依次执行一系列的指令.多线程好像是一个小队中的成员同时执行不同的任务. 多线程 传统意义上,多线程是由操作系统提供的功能.对于单核的CPU,硬件中只存在一个线程.在操作系统的控制下,CPU会

[Java复习05] 多线程&并发 知识点补充

0. wait/notify/notifyAll的理解? wait:让持有该对象锁的线程等待: notify: 唤醒任何一个持有该对象锁的线程: notifyAll: 唤醒所有持有该对象锁的线程: 它们 3 个的关系是,调用对象的 wait 方法使线程暂停运行,通过 notify/ notifyAll 方法唤醒调用 wait 暂时的线程. 它们并不是 Thread 类中的方法,而是 Object 类中的,为什么呢? 因为每个对象都有监视锁,线程要操作某个对象当然是要获取某个对象的锁了,而不是线程

Java进阶(三)多线程开发关键技术

原创文章,转载请务必将下面这段话置于文章开头处. 本文转发自Jason's Blog,原文链接 http://www.jasongj.com/java/multi_thread/ sleep和wait到底什么区别 其实这个问题应该这么问--sleep和wait有什么相同点.因为这两个方法除了都能让当前线程暂停执行完,几乎没有其它相同点. wait方法是Object类的方法,这意味着所有的Java类都可以调用该方法.sleep方法是Thread类的静态方法. wait是在当前线程持有wait对象锁

Java进阶书籍推荐

学习Java,书籍是必不可少的学习工具之一,尤其是对于自学者而言.废话不多说,下边就给广大程序猿们推荐一些Java进阶的好书. 第一部分:Java语言篇 1.<Java编程规范> 适合对象:初级.中级 介绍:这本书的作者是被誉为Java之父的James Gosling,入门者推荐阅读,对基础的讲解很不错. 2.<Java编程思想> 适合对象:初级.中级 介绍:豆瓣给出了9.1的评分,全球程序员广泛赞誉.有人说这本书不适合初学者,不过小编认为作者并没有对读者已有的知识经验有过多要求,

java进阶计划

鉴于自己在java 的学习过程中,像是无头苍蝇一样,东扎一把,西戳一下,没有一个明确的方向,也没有一个比较明确的方面,所以有了这个大致的计划. 计划的目标: 1. java本身的目标 对线程(thread),串行化,反射,网络编程,JNI技术,容器(Map,List, Iterator), 类加载器(ClassLoader),输入输出流,垃圾回收机制, 有比较深入的了解,最起码做过项目应用.有过Java项目的性能优化经验,最起码掌握一种性能监视工具的使用,熟悉JVM参数,最起码知道可以在JVM启

Java线程及多线程技术及应用

第6 章 Java线程及多线程技术及应用 6.1线程基本概念 1.进程和线程的基础知识 l 进程:运行中的应用程序称为进程,拥有系统资源(cpu.内存) l 线程:进程中的一段代码,一个进程中可以哦有多段代码.本身不拥有资源(共享所在进程的资源) 在java中,程序入口被自动创建为主线程,在主线程中可以创建多个子线程. 区别: 1.是否占有资源问题 2.创建或撤销一个进程所需要的开销比创建或撤销一个线程所需要的开销大. 3.进程为重量级组件,线程为轻量级组件 l 多进程: 在操作系统中能同时运行

Java进阶之路

Java进阶之路--从初级程序员到架构师,从小工到专家. 怎样学习才能从一名Java初级程序员成长为一名合格的架构师,或者说一名合格的架构师应该有怎样的技术知识体系,这是不仅一个刚刚踏入职场的初级程序员也是工作三五年之后开始迷茫的老程序员经常会问到的问题.希望这篇文章会是你看到过的最全面最权威的回答. 一: 编程基础 不管是C还是C++,不管是Java还是PHP,想成为一名合格的程序员,基本的数据结构和算法基础还是要有的.下面几篇文章从思想到实现,为你梳理出常用的数据结构和经典算法. 1-1 常

Java进阶(三十二) HttpClient使用详解

Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们再讨论),它不仅是客户端发送Http请求变得容易,而且也方便了开发人员测试接口(基于Http协议的),即提高了开发的效率,也方便提高代码的健壮性.因此熟练掌握HttpClient是很重要的必修内容,掌握HttpClient后,相信对于Http协议的了解会更加深入. 一.简介 HttpClient是A

Java进阶(三十四)Integer与int的种种比较你知道多少?

Java进阶(三十四)Integer与int的种种比较你知道多少? 前言 如果面试官问Integer与int的区别:估计大多数人只会说到两点:Ingeter是int的包装类,注意是一个类:int的初值为0,Ingeter的初值为null.但是如果面试官再问一下Integer i = 1;int ii = 1; i==ii为true还是为false?估计就有一部分人答不出来了,如果再问一下其他的,估计更多的人会头脑一片混乱.所以我对它们进行了总结,希望对大家有帮助. 首先看代码: package