Java之十三 Java多线程(中)

简介: Java之十三 Java多线程(中)

选择合适方法


到这里,你一定会奇怪为什么Java有两种创建子线程的方法,哪一种更好呢。所有的问题都归于一点。Thread类定义了多种方法可以被派生类重载。对于所有的方法,惟一的必须被重载的是run()方法。这当然是实现Runnable接口所需的同样的方法。很多Java程序员认为类仅在它们被加强或修改时应该被扩展。因此,如果你不重载Thread的其他方法时,最好只实现Runnable  接口。这当然由你决定。然而,在本章的其他部分,我们应用实现runnable接口的类来创建线程。

创建多线程


到目前为止,我们仅用到两个线程:主线程和一个子线程。然而,你的程序可以创建所需的更多线程。例如,下面的程序创建了三个子线程:


// Create multiple threads.
class NewThread implementsRunnable {
  String name; // name of thread
  Thread t;
  NewThread(String threadname) {
    name = threadname;
    t = new Thread(this, name);
    System.out.println("New thread: "+ t);
    t.start(); // Start the thread
  }


// This is the entry point for thread.
  public void run() {
   try {
      for(int i = 5; i > 0; i--) {
        System.out.println(name + ":" + i);
        Thread.sleep(1000);
      }
    } catch (InterruptedException e) {
      System.out.println(name +"Interrupted");
    }
    System.out.println(name + "exiting.");
  }
}



class MultiThreadDemo {
  public static void main(String args[]) {
    newNewThread("One"); // start threads
    new NewThread("Two");
    new NewThread("Three");


try {
      // wait for other threads to end
      Thread.sleep(10000);
    } catch (InterruptedException e) {
      System.out.println("Main thread Interrupted");
    }
    System.out.println("Main threadexiting.");
  }
}

程序输出如下所示:


New thread: Thread[One,5,main]


New thread: Thread[Two,5,main]


New thread:Thread[Three,5,main]


One: 5


Two: 5


Three: 5


One: 4


Two: 4


Three: 4


One: 3


Three: 3


Two: 3


One: 2


Three: 2


Two: 2


One: 1


Three: 1


Two: 1


One exiting.


Two exiting.


Three exiting.


Main thread exiting.


如你所见,一旦启动,所有三个子线程共享CPU。注意main()中对sleep(10000)的调用。这使主线程沉睡十秒确保它最后结束。


使用isAlive()和join()




如前所述,通常你希望主线程最后结束。在前面的例子中,这点是通过在main()中调用sleep()来实现的,经过足够长时间的延迟以确保所有子线程都先于主线程结束。然而,这不是一个令人满意的解决方法,它也带来一个大问题:一个线程如何知道另一线程已经结束?幸运的是,Thread类提供了回答此问题的方法。


有两种方法可以判定一个线程是否结束。第一,可以在线程中调用isAlive()。这种方法由Thread定义,它的通常形式如下:


final boolean isAlive( )


如果所调用线程仍在运行,isAlive()方法返回true,如果不是则返回false。 但isAlive()很少用到,等待线程结束的更常用的方法是调用join(),描述如下:


final void join( ) throwsInterruptedException


该方法等待所调用线程结束。该名字来自于要求线程等待直到指定线程参与的概念。


join()的附加形式允许给等待指定线程结束定义一个最大时间。下面是前面例子的改进版本。运用join()以确保主线程最后结束。同样,它也演示了isAlive()方法。


// Using join() to wait forthreads to finish.
class NewThread implementsRunnable {
  String name; // name of thread
  Thread t;
  NewThread(String threadname) {
    name = threadname;
    t = new Thread(this, name);
    System.out.println("New thread: "+ t);
    t.start(); // Start the thread
  }



 

// This is the entry point for thread.
  public void run() {
    try {
      for(int i = 5; i > 0; i--) {
        System.out.println(name + ":" + i);
        Thread.sleep(1000);
      }
    } catch (InterruptedException e) {
      System.out.println(name + "interrupted.");
    }
    System.out.println(name + "exiting.");
  }
}


class DemoJoin {
  public static void main(String args[]) {
    NewThread ob1 = newNewThread("One");
    NewThread ob2 = newNewThread("Two");
    NewThread ob3 = new NewThread("Three");
    System.out.println("Thread One isalive: "
                        + ob1.t.isAlive());
    System.out.println("Thread Two isalive: "
                        + ob2.t.isAlive());
    System.out.println("Thread Three isalive: "
                        + ob3.t.isAlive());
    // wait for threads to finish
   try {
      System.out.println("Waiting forthreads to finish.");
      ob1.t.join();
      ob2.t.join();
      ob3.t.join();
    } catch (InterruptedException e) {
      System.out.println("Main threadInterrupted");
    }



System.out.println("Thread One isalive: "
                        + ob1.t.isAlive());
    System.out.println("Thread Two isalive: "
                        + ob2.t.isAlive());
    System.out.println("Thread Three isalive: "
                        + ob3.t.isAlive());
    System.out.println("Main threadexiting.");
  }
}


程序输出如下所示:


New thread: Thread[One,5,main]


New thread: Thread[Two,5,main]


New thread:Thread[Three,5,main]


Thread One is alive: true


Thread Two is alive: true


Thread Three is alive: true


Waiting for threads to finish.


One: 5


Two: 5


Three: 5


One: 4


Two: 4


Three: 4


One: 3


Two: 3


Three: 3


One: 2


Two: 2


Three: 2


One: 1


Two: 1


Three: 1


Two exiting.


Three exiting.


One exiting.


Thread One is alive: false


Thread Two is alive: false


Thread Three is alive: false


Main thread exiting.


如你所见,调用join()后返回,线程终止执行。

线程优先级



线程优先级被线程调度用来判定何时每个线程允许运行。理论上,优先级高的线程比优先级低的线程获得更多的CPU时间。实际上,线程获得的CPU时间通常由包括优先级在内的多个因素决定(例如,一个实行多任务处理的操作系统如何更有效的利用CPU时间)。


一个优先级高的线程自然比优先级低的线程优先。举例来说,当低优先级线程正在运行,而一个高优先级的线程被恢复(例如从沉睡中或等待I/O中),它将抢占低优先级线程所使用的CPU。


理论上,等优先级线程有同等的权利使用CPU。但你必须小心了。记住,Java是被设计成能在很多环境下工作的。一些环境下实现多任务处理从本质上与其他环境不同。为安全起见,等优先级线程偶尔也受控制。这保证了所有线程在无优先级的操作系统下都有机会运行。实际上,在无优先级的环境下,多数线程仍然有机会运行,因为很多线程不可避免的会遭遇阻塞,例如等待输入输出。遇到这种情形,阻塞的线程挂起,其他线程运行。


但是如果你希望多线程执行的顺利的话,最好不要采用这种方法。同样,有些类型的任务是占CPU的。对于这些支配CPU类型的线程,有时你希望能够支配它们,以便使其他线程可以运行。


设置线程的优先级,用setPriority()方法,该方法也是Tread 的成员。它的通常形式为:


final void setPriority(int level)


这里,level指定了对所调用的线程的新的优先权的设置。Level的值必须在MIN_PRIORITY到MAX_PRIORITY范围内。通常,它们的值分别是1和10。要返回一个线程为默认的优先级,指定NORM_PRIORITY,通常值为5。这些优先级在Thread中都被定义为final型变量。


你可以通过调用Thread的getPriority()方法来获得当前的优先级设置。该方法如下:


final int getPriority( )


当涉及调度时,Java的执行可以有本质上不同的行为。Windows 95/98/NT/2000 的工作或多或少如你所愿。但其他版本可能工作的完全不同。大多数矛盾发生在你使用有优先级行为的线程,而不是协同的腾出CPU时间。最安全的办法是获得可预先性的优先权,Java获得跨平台的线程行为的方法是自动放弃对CPU的控制。


下面的例子阐述了两个不同优先级的线程,运行于具有优先权的平台,这与运行于无优先级的平台不同。一个线程通过Thread.NORM_PRIORITY设置了高于普通优先级两级的级数,另一线程设置的优先级则低于普通级两级。两线程被启动并允许运行10秒。每个线程执行一个循环,记录反复的次数。10秒后,主线程终止了两线程。每个线程经过循环的次数被显示。


// Demonstrate thread priorities.
class clicker implements Runnable {
  int click =0;
  Thread t;
  privatevolatile boolean running = true;
  publicclicker(int p) {
    t = newThread(this);
   t.setPriority(p);
  }


public void run() {
    while(running) {
     click++;
    }
  }


public voidstop() {
    running =false;
  }



public voidstart() {
   t.start();
  }
}



class HiLoPri {
  publicstatic void main(String args[]) {
   Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
    clickerhi = new clicker(Thread.NORM_PRIORITY + 2);
    clickerlo = new clicker(Thread.NORM_PRIORITY - 2);
   lo.start();
   hi.start();
    try {      Thread.sleep(10000);
    } catch(InterruptedException e) {
       System.out.println("Main thread interrupted.");
    }
   lo.stop();
   hi.stop();
    // Waitfor child threads to terminate.
    try {
     hi.t.join();
     lo.t.join();
    } catch(InterruptedException e) {
      System.out.println("InterruptedExceptioncaught");
    }
   System.out.println("Low-priority thread: " + lo.click); 
   System.out.println("High-priority thread: " + hi.click);
  }
}

该程序在Windows 98下运行的输出,表明线程确实上下转换,甚至既不屈从于CPU,也不被输入输出阻塞。优先级高的线程获得大约90%的CPU时间。


Low-priority thread: 4408112


High-priority thread: 589626904


当然,该程序的精确的输出结果依赖于你的CPU的速度和运行的其他任务的数量。当同样的程序运行于无优先级的系统,将会有不同的结果。


上述程序还有个值得注意的地方。注意running前的关键字volatile。尽管volatile 在下章会被很仔细的讨论,用在此处以确保running的值在下面的循环中每次都得到验证。


while (running) {
  click++;
}


如果不用volatile,Java可以自由的优化循环:running的值被存在CPU的一个寄存器中,每次重复不一定需要复检。volatile的运用阻止了该优化,告知Java running可以改变,改变方式并不以直接代码形式显示。

线  程  同  步



当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用。达到此目的的过程叫做同步(synchronization)。像你所看到的,Java为此提供了独特的,语言水平上的支持。


同步的关键是管程(也叫信号量semaphore)的概念。管程是一个互斥独占锁定的对象,或称互斥体(mutex)。在给定的时间,仅有一个线程可以获得管程。当一个线程需要锁定,它必须进入管程。所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程。这些其他的线程被称为等待管程。一个拥有管程的线程如果愿意的话可以再次进入相同的管程。


如果你用其他语言例如C或C++时用到过同步,你会知道它用起来有一点诡异。这是因为很多语言它们自己不支持同步。相反,对同步线程,程序必须利用操作系统源语。幸运的是Java通过语言元素实现同步,大多数的与同步相关的复杂性都被消除。

目录
相关文章
|
1月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
152 1
|
1月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
167 1
|
2月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
140 0
|
2月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
226 16
|
3月前
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。
|
3月前
|
数据采集 存储 前端开发
Java爬虫性能优化:多线程抓取JSP动态数据实践
Java爬虫性能优化:多线程抓取JSP动态数据实践
|
4月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
355 83
|
4月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
201 0
|
4月前
|
存储 Java 调度
Java虚拟线程:轻量级并发的革命性突破
Java虚拟线程:轻量级并发的革命性突破
331 83