【转】编程思想之多线程与多进程(3)——Java中的多线程

《编程思想之多线程与多进程(1)——以操作系统的角度述说线程与进程》一文详细讲述了线程、进程的关系及在操作系统中的表现,这是多线程学习必须了解的基础。本文将接着讲一下Java中多线程程序的开发

单线程

任何程序至少有一个线程,即使你没有主动地创建线程,程序从一开始执行就有一个默认的线程,被称为主线程,只有一个线程的程序称为单线程程序。如下面这一简单的代码,没有显示地创建一个线程,程序从main开始执行,main本身就是一个线程(主线程),单个线程从头执行到尾。

【Demo1】:单线程程序

public static void main(String args[]) {
   System.out.println("输出从1到100的数:");
   for (int i = 0; i < 100; i ++) {
      System.out.println(i + 1);
   }
}

创建线程

单线程程序简单明了,但有时无法满足特定的需求。如一个文字处理的程序,我在打印文章的同时也要能对文字进行编辑,如果是单线程的程序则要等打印机打印完成之后你才能对文字进行编辑,但打印的过程一般比较漫长,这是我们无法容忍的。如果采用多线程,打印的时候可以单独开一个线程去打印,主线程可以继续进行文字编辑。在程序需要同时执行多个任务时,可以采用多线程。

在程序需要同时执行多个任务时,可以采用多线程。Java给多线程编程提供了内置的支持,提供了两种创建线程方法:1.通过实现Runable接口;2.通过继承Thread类。

Thread是JDK实现的对线程支持的类,Thread类本身实现了Runnable接口,所以Runnable是显示创建线程必须实现的接口; Runnable只有一个run方法,所以不管通过哪种方式创建线程,都必须实现run方法。我们可以看一个例子。

【Demo2】:线程的创建和使用

/**
 * Created with IntelliJ IDEA.
 * User: luoweifu
 * Date: 15-5-24
 * Time: 下午9:30
 * To change this template use File | Settings | File Templates.
 */

/**
 * 通过实现Runnable方法
 */
class ThreadA implements Runnable {
   private Thread thread;
   private String threadName;
   public ThreadA(String threadName) {
      thread = new Thread(this, threadName);
      this.threadName = threadName;
   }

   //实现run方法
   public void run() {
      for (int i = 0; i < 100; i ++) {
         System.out.println(threadName + ": " + i);
      }
   }

   public void start() {
      thread.start();
   }
}

/**
 * 继承Thread的方法
 */
class ThreadB extends Thread {
   private String threadName;

   public ThreadB(String threadName) {
      super(threadName);
      this.threadName = threadName;
   }

   //实现run方法
   public void run() {
      for (int i = 0; i < 100; i ++) {
         System.out.println(threadName + ": " + i);
      }
   }
}

public class MultiThread{

   public static void main(String args[]) {
      ThreadA threadA = new ThreadA("ThreadA");
      ThreadB threadB = new ThreadB("ThreadB");
      threadA.start();
      threadB.start();
   }
}

说明:上面的例子中例举了两种实现线程的方式。大部分情况下选择实现Runnable接口的方式会优于继承Thread的方式,因为:
1. 从 Thread 类继承会强加类层次;
2. 有些类不能继承Thread类,如要作为线程运行的类已经是某一个类的子类了,但Java只支持单继承,所以不能再继承Thread类了。


线程同步

线程与线程之间的关系,有几种:

模型一:简单的线程,多个线程同时执行,但各个线程处理的任务毫不相干,没有数据和资源的共享,不会出现争抢资源的情况。这种情况下不管有多少个线程同时执行都是安全的,其执行模型如下:


图 1:处理相互独立的任务

模型二:复杂的线程,多个线程共享相同的数据或资源,就会出现多个线程争抢一个资源的情况。这时就容易造成数据的非预期(错误)处理,是线程不安全的,其模型如下:


图 2:多个线程共享相同的数据或资源

在出现模型二的情况时就要考虑线程的同步,确保线程的安全。Java中对线程同步的支持,最常见的方式是添加synchronized同步锁。

我们通过一个例子来看一下线程同步的应用。

买火车票是大家春节回家最为关注的事情,我们就简单模拟一下火车票的售票系统(为使程序简单,我们就抽出最简单的模型进行模拟):有500张从北京到赣州的火车票,在8个窗口同时出售,保证系统的稳定性和数据的原子性。


图 3:模拟火车票售票系统

【Demo3】:火车票售票系统模拟程序

/**
 * 模拟服务器的类
 */
class Service {
   private String ticketName;    //票名
   private int totalCount;        //总票数
   private int remaining;        //剩余票数

   public Service(String ticketName, int totalCount) {
      this.ticketName = ticketName;
      this.totalCount = totalCount;
      this.remaining = totalCount;
   }

   public synchronized int saleTicket(int ticketNum) {
      if (remaining > 0) {
         remaining -= ticketNum;
         try {        //暂停0.1秒,模拟真实系统中复杂计算所用的时间
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }

         if (remaining >= 0) {
            return remaining;
         } else {
            remaining += ticketNum;
            return -1;
         }
      }
      return -1;
   }

   public synchronized int getRemaining() {
      return remaining;
   }

   public String getTicketName() {
      return this.ticketName;
   }

}

/**
 * 售票程序
 */
class TicketSaler implements Runnable {
   private String name;
   private Service service;

   public TicketSaler(String windowName, Service service) {
      this.name = windowName;
      this.service = service;
   }

   @Override
   public void run() {
      while (service.getRemaining() > 0) {
         synchronized (this)
         {
            System.out.print(Thread.currentThread().getName() + "出售第" + service.getRemaining() + "张票,");
            int remaining = service.saleTicket(1);
            if (remaining >= 0) {
               System.out.println("出票成功!剩余" + remaining + "张票.");
            } else {
               System.out.println("出票失败!该票已售完。");
            }
         }
      }
   }
}

测试程序:

/**
 * 测试类
 */
public class TicketingSystem {
   public static void main(String args[]) {
      Service service = new Service("北京-->赣州", 500);
      TicketSaler ticketSaler = new TicketSaler("售票程序", service);
      //创建8个线程,以模拟8个窗口
      Thread threads[] = new Thread[8];
      for (int i = 0; i < threads.length; i++) {
         threads[i] = new Thread(ticketSaler, "窗口" + (i + 1));
         System.out.println("窗口" + (i + 1) + "开始出售 " + service.getTicketName() + " 的票...");
         threads[i].start();
      }

   }
}

结果如下:

窗口1开始出售 北京–>赣州 的票…
窗口2开始出售 北京–>赣州 的票…
窗口3开始出售 北京–>赣州 的票…
窗口4开始出售 北京–>赣州 的票…
窗口5开始出售 北京–>赣州 的票…
窗口6开始出售 北京–>赣州 的票…
窗口7开始出售 北京–>赣州 的票…
窗口8开始出售 北京–>赣州 的票…
窗口1出售第500张票,出票成功!剩余499张票.
窗口1出售第499张票,出票成功!剩余498张票.
窗口6出售第498张票,出票成功!剩余497张票.
窗口6出售第497张票,出票成功!剩余496张票.
窗口1出售第496张票,出票成功!剩余495张票.
窗口1出售第495张票,出票成功!剩余494张票.
窗口1出售第494张票,出票成功!剩余493张票.
窗口2出售第493张票,出票成功!剩余492张票.
窗口2出售第492张票,出票成功!剩余491张票.
窗口2出售第491张票,出票成功!剩余490张票.
窗口2出售第490张票,出票成功!剩余489张票.
窗口2出售第489张票,出票成功!剩余488张票.
窗口2出售第488张票,出票成功!剩余487张票.
窗口6出售第487张票,出票成功!剩余486张票.
窗口6出售第486张票,出票成功!剩余485张票.
窗口3出售第485张票,出票成功!剩余484张票.
……

在上面的例子中,涉及到数据的更改的Service类saleTicket方法和TicketSaler类run方法都用了synchronized同步锁进行同步处理,以保证数据的准确性和原子性。

关于synchronized更详细的用法请参见:《Java中Synchronized的用法


线程控制

在多线程程序中,除了最重要的线程同步外,还有其它的线程控制,如线程的中断、合并、优先级等。

线程等待(wait、notify、notifyAll)

Wait:使当前的线程处于等待状态;
Notify:唤醒其中一个等待线程;
notifyAll:唤醒所有等待线程。

详细用法参见:《Java多线程中wait, notify and notifyAll的使用


线程中断(interrupt)

在Java提供的线程支持类Thread中,有三个用于线程中断的方法:
public void interrupt(); 中断线程。
public static boolean interrupted(); 是一个静态方法,用于测试当前线程是否已经中断,并将线程的中断状态
清除。所以如果线程已经中断,调用两次interrupted,第二次时会返回false,因为第一次返回true后会清除中断状态。
public boolean isInterrupted(); 测试线程是否已经中断。

【Demo4】:线程中断的应用

/**
 * 打印线程
 */
class Printer implements Runnable {
   public void run() {
      while (!Thread.currentThread().isInterrupted()) {     //如果当前线程未被中断,则执行打印工作
         System.out.println(Thread.currentThread().getName() + "打印中… …");
      }
      if (Thread.currentThread().isInterrupted()) {
         System.out.println("interrupted:" +  Thread.interrupted());       //返回当前线程的状态,并清除状态
         System.out.println("isInterrupted:" +  Thread.currentThread().isInterrupted());
      }
   }
}

调用代码:

Printer printer = new Printer();
Thread printerThread = new Thread(printer, "打印线程");
printerThread.start();
try {
   Thread.sleep(100);
} catch (InterruptedException e) {
   e.printStackTrace();
}
System.out.println("有紧急任务出现,需中断打印线程.");
System.out.println("中断前的状态:" + printerThread.isInterrupted());
printerThread.interrupt();       // 中断打印线程
System.out.println("中断前的状态:" + printerThread.isInterrupted());

结果:

打印线程打印中… …
… …
打印线程打印中… …
有紧急任务出现,需中断打印线程.
打印线程打印中… …
中断前的状态:false
打印线程打印中… …
中断前的状态:true
interrupted:true
isInterrupted:false

线程合并(join)

所谓合并,就是等待其它线程执行完,再执行当前线程,执行起来的效果就好像把其它线程合并到当前线程执行一样。其执行关系如下:


图 4:线程合并的过程

public final void join()
等待该线程终止

public final void join(long millis);
等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去。

public final void join(long millis, int nanos)
等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒

这个常见的一个应用就是安装程序,很多大的软件都会包含多个插件,如果选择完整安装,则要等所有的插件都安装完成才能结束,且插件与插件之间还可能会有依赖关系。

【Demo5】:线程合并

/**
 * 插件1
 */
class Plugin1 implements Runnable {

   @Override
   public void run() {
      System.out.println("插件1开始安装.");
      System.out.println("安装中...");
      try {
         Thread.sleep(1000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      System.out.println("插件1完成安装.");
   }
}

/**
 * 插件2
 */
class Plugin2 implements Runnable {

   @Override
   public void run() {
      System.out.println("插件2开始安装.");
      System.out.println("安装中...");
      try {
         Thread.sleep(2000);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      System.out.println("插件2完成安装.");
   }
}

合并线程的调用:

System.out.println("主线程开启...");
Thread thread1 = new Thread(new Plugin1());
Thread thread2 = new Thread(new Plugin2());
try {
   thread1.start();   //开始插件1的安装
   thread1.join();       //等插件1的安装线程结束
   thread2.start();   //再开始插件2的安装
   thread2.join();       //等插件2的安装线程结束,才能回到主线程
} catch (InterruptedException e) {
   e.printStackTrace();
}
System.out.println("主线程结束,程序安装完成!");

结果如下:

主线程开启…
插件1开始安装.
安装中…
插件1完成安装.
插件2开始安装.
安装中…
插件2完成安装.
主线程结束,程序安装完成!

优先级(Priority)

线程优先级是指获得CPU资源的优先程序。优先级高的容易获得CPU资源,优先级底的较难获得CPU资源,表现出来的情况就是优先级越高执行的时间越多。

Java中通过getPriority和setPriority方法获取和设置线程的优先级。Thread类提供了三个表示优先级的常量:MIN_PRIORITY优先级最低,为1;NORM_PRIORITY是正常的优先级;为5,MAX_PRIORITY优先级最高,为10。我们创建线程对象后,如果不显示的设置优先级的话,默认为5。

【Demo】:线程优先级

/**
 * 优先级
 */
class PriorityThread implements Runnable{
   @Override
   public void run() {
      for (int i = 0; i < 1000; i ++) {
         System.out.println(Thread.currentThread().getName() + ": " + i);
      }
   }
}

调用代码:

//创建三个线程
Thread thread1 = new Thread(new PriorityThread(), "Thread1");
Thread thread2 = new Thread(new PriorityThread(), "Thread2");
Thread thread3 = new Thread(new PriorityThread(), "Thread3");
//设置优先级
thread1.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(8);
//开始执行线程
thread3.start();
thread2.start();
thread1.start();

从结果中我们可以看到线程thread1明显比线程thread3执行的快。



如果您有什么疑惑和想法,请在评论处给予反馈,您的反馈就是最好的测评师!由于本人技术和能力有限,如果本博文有错误或不足之处,敬请谅解并给出您宝贵的建议!




原文:http://blog.csdn.net/luoweifu/article/details/46673975
作者:luoweifu

原文地址:https://www.cnblogs.com/shujuxiong/p/9246476.html

时间: 2024-11-11 13:54:55

【转】编程思想之多线程与多进程(3)——Java中的多线程的相关文章

编程思想之多线程与多进程(2)——Java中的多线程

原文:http://blog.csdn.net/luoweifu/article/details/46673975 作者:luoweifu 转载请标名出处 <编程思想之多线程与多进程(1)--以操作系统的角度述说线程与进程>一文详细讲述了线程.进程的关系及在操作系统中的表现,这是多线程学习必须了解的基础.本文将接着讲一下Java中多线程程序的开发 单线程 任何程序至少有一个线程,即使你没有主动地创建线程,程序从一开始执行就有一个默认的线程,被称为主线程,只有一个线程的程序称为单线程程序.如下面

编程思想之多线程与多进程(4)——C++中的多线程

<编程思想之多线程与多进程(1)--以操作系统的角度述说线程与进程>一文详细讲述了线程.进程的关系及在操作系统中的表现,<编程思想之多线程与多进程(2)--线程优先级与线程安全>一文讲了线程安全(各种同步锁)和优先级,这是多线程学习必须了解的基础.本文将接着讲一下C++中多线程程序的开发.这里主要讲Windows平台线程的用法,创建线程要调用windows API的CreateThread方法. 创建线程 在Windows平台,Windows API提供了对多线程的支持.前面进程和

Java中的 多线程编程

Java 中的多线程编程 一.多线程的优缺点 多线程的优点: 1)资源利用率更好2)程序设计在某些情况下更简单3)程序响应更快 多线程的代价: 1)设计更复杂虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂.在多线程访问共享数据的时候,这部分代码需要特别的注意.线程之间的交互往往非常复杂.不正确的线程同步产生的错误非常难以被发现,并且重现以修复. 2)上下文切换的开销当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另

Java中的多线程技术全面详解

本文主要从整体上介绍Java中的多线程技术,对于一些重要的基础概念会进行相对详细的介绍,若有叙述不清晰或是不正确的地方,希望大家指出,谢谢大家:) 为什么使用多线程 并发与并行 我们知道,在单核机器上,"多进程"并不是真正的多个进程在同时执行,而是通过CPU时间分片,操作系统快速在进程间切换而模拟出来的多进程.我们通常把这种情况成为并发,也就是多个进程的运行行为是"一并发生"的,但不是同时执行的,因为CPU核数的限制(PC和通用寄存器只有一套,严格来说在同一时刻只能

java 中的多线程

java中实现多线程的方式有两种: 1.实现继承Thread 类的类(重写run方法) 2.实现Runnable 接口(重写run方法) 上述两种方式的关系; 看过jdk你会发现 Thread类是实现了 runnable的接口了的   可见,实现Runnable接口相对于继承Thread类来说,有如下显著的优势: (1). 适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码.数据有效分离,较好地体现了面向对象的设计思想. (2). 可以避免由于Java的单继承特性带

Java中的多线程你只要看这一篇就够了

Java中的多线程你只要看这一篇就够了 引 如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其实只有一半对,因为反应"多角色"的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的"生产者,消费者模型". 很多人都对其中的一些概念不够明确,如同步.并发等等,让我

为什么Java中实现多线程的方式有两种?

在面试的过程中,我们经常问被面试者,为什么Java中实现多线程的方式有两种(一种是直接继承Thread类,一种是实现Runnable接口)?可惜的是,很多面试者都答不出来,甚至从来没有想为什么.,那么真正的原因是什么呢?我们可以用反证法推理一下: 假设Java只提供Thread供大家继承从而实现多线程,考虑下面的一个需求,如果有一个已经继承了某个父类的类,但是这个类又想实现多线程,怎么办?很显然,如果只提供一个可以继承的类,肯定解决不了这个问题.那么,如何解决,毫无疑问,就只能使用接口了.

Java中使用多线程、curl及代理IP模拟post提交和get访问

Java中使用多线程.curl及代理IP模拟post提交和get访问 菜鸟,多线程好玩就写着玩,大神可以路过指教,小弟在这受教,谢谢! [java] view plaincopyprint? /** * @组件名:javaDemo * @包名:javaDemo * @文件名:Jenny.java * @创建时间: 2014年8月1日 下午5:53:48 * @版权信息:Copyright ? 2014 eelly Co.Ltd,小姨子版权所有. */ package javaDemo; impo

Java中的多线程=你只要看这一篇就够了

如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的“生产者,消费者模型”. 很多人都对其中的一些概念不够明确,如同步.并发等等,让我们先建立一个数据字典,以免产生误会. 多线程:指的是这个程序(一个进程)运