一、线程类
Java是通过Java.lang.Thread类来实现多线程的,第个Thread对象描述了一个单独的线程。要产生一个线程,有两种方法:
1、需要从Java.lang.Thread类继承一个新的线程类,重载它的run()方法;
2、通过Runnalbe接口实现一个从非线程类继承来类的多线程,重载Runnalbe接口的run()方法。运行一个新的线程,只需要调用它的start()方法即可。如:
/**=====================================================================
* 文件:ThreadDemo_01.java
* 描述:产生一个新的线程
* ======================================================================
*/
class ThreadDemo extends Thread{
Threads()
{
}
Threads(String szName)
{
super(szName);
}
// 重载run函数
public void run()
{
for (int count = 1,row = 1; row < 20; row++,count++)
{
for (int i = 0; i < count; i++)
{
System.out.print('*');
}
System.out.println();
}
}
}
class ThreadMain{
public static void main(String argv[]){
ThreadDemo th = new ThreadDemo();
// 调用start()方法执行一个新的线程
th.start();
}
}
线程类的一些常用方法:
sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级。
二、等待一个线程的结束
有些时候我们需要等待一个线程终止后再运行我们的另一个线程,这时我们应该怎么办呢?请看下面的例子:
/**=====================================================================
* 文件:ThreadDemo_02.java
* 描述:等待一个线程的结束
* ======================================================================
*/
class ThreadDemo extends Thread{
Threads()
{
}
Threads(String szName)
{
super(szName);
}
// 重载run函数
public void run()
{
for (int count = 1,row = 1; row < 20; row++,count++)
{
for (int i = 0; i < count; i++)
{
System.out.print('*');
}
System.out.println();
}
}
}
class ThreadMain{
public static void main(String argv[]){
//产生两个同样的线程
ThreadDemo th1 = new ThreadDemo();
ThreadDemo th2 = new ThreadDemo();
// 我们的目的是先运行第一个线程,再运行第二个线程
th1.start();
th2.start();
}
}
这里我们的目标是要先运行第一个线程,等第一个线程终止后再运行第二个线程,而实际运行的结果是如何的呢?实际上我们运行的结果并不是两个我们想要的直角三角形,而是一些乱七八糟的*号行,有的长,有的短。为什么会这样呢?因为线程并没有按照我们的调用顺序来执行,而是产生了线程赛跑现象。实际上Java并不能按我们的调用顺序来执行线程,这也说明了线程是并行执行的单独代码。如果要想得到我们预期的结果,这里我们就需要判断第一个线程是否已经终止,如果已经终止,再来调用第二个线程。代码如下:
/**=====================================================================
* 文件:ThreadDemo_03.java
* 描述:等待一个线程的结束的两种方法
* ======================================================================
*/
class ThreadDemo extends Thread{
Threads()
{
}
Threads(String szName)
{
super(szName);
}
// 重载run函数
public void run()
{
for (int count = 1,row = 1; row < 20; row++,count++)
{
for (int i = 0; i < count; i++)
{
System.out.print('*');
}
System.out.println();
}
}
}
class ThreadMain{
public static void main(String argv[]){
ThreadMain test = new ThreadMain();
test.Method1();
// test.Method2();
}
// 第一种方法:不断查询第一个线程是否已经终止,如果没有,则让主线程睡眠一直到它终止为止
// 即:while/isAlive/sleep
public void Method1(){
ThreadDemo th1 = new ThreadDemo();
ThreadDemo th2 = new ThreadDemo();
// 执行第一个线程
th1.start();
// 不断查询第一个线程的状态
while(th1.isAlive()){
try{
Thread.sleep(100);
}catch(InterruptedException e){
}
}
//第一个线程终止,运行第二个线程
th2.start();
}
// 第二种方法:join()
public void Method2(){
ThreadDemo th1 = new ThreadDemo();
ThreadDemo th2 = new ThreadDemo();
// 执行第一个线程
th1.start();
try{
th1.join();
}catch(InterruptedException e){
}
// 执行第二个线程
th2.start();
}
三、线程的同步问题
有些时候,我们需要很多个线程共享一段代码,比如一个私有成员或一个类中的静态成员,但是由于线程赛跑的问题,所以我们得到的常常不是正确的输出结果,而相反常常是张冠李戴,与我们预期的结果大不一样。看下面的例子:
/**=============================================================================
* 文件:ThreadDemo_04.java
* 描述:多线程不同步的原因
* =============================================================================
*/
// 共享一个静态数据对象
class ShareData{
public static String szData = "";
}
class ThreadDemo extends Thread{
private ShareData oShare;
ThreadDemo(){
}
ThreadDemo(String szName,ShareData oShare){
super(szName);
this.oShare = oShare;
}
public void run(){
// 为了更清楚地看到不正确的结果,这里放一个大的循环
for (int i = 0; i < 50; i++){
if (this.getName().equals("Thread1")){
oShare.szData = "这是第 1 个线程";
// 为了演示产生的问题,这里设置一次睡眠
try{
Thread.sleep((int)Math.random() * 100);
catch(InterruptedException e){
}
// 输出结果
System.out.println(this.getName() + ":" + oShare.szData);
}else if (this.getName().equals("Thread2")){
oShare.szData = "这是第 1 个线程";
// 为了演示产生的问题,这里设置一次睡眠
try{
Thread.sleep((int)Math.random() * 100);
catch(InterruptedException e){
}
// 输出结果
System.out.println(this.getName() + ":" + oShare.szData);
}
}
}
class ThreadMain{
public static void main(String argv[]){
ShareData oShare = new ShareData();
ThreadDemo th1 = new ThreadDemo("Thread1",oShare);
ThreadDemo th2 = new ThreadDemo("Thread2",oShare);
th1.start();
th2.start();
}
}
由于线程的赛跑问题,所以输出的结果往往是Thread1对应“这是第 2 个线程”,这样与我们要输出的结果是不同的。为了解决这种问题(错误),Java为我们提供了“锁”的机制来实现线程的同步。锁的机制要求每个线程在进入共享代码之前都要取得锁,否则不能进入,而退出共享代码之前则释放该锁,这样就防止了几个或多个线程竞争共享代码的情况,从而解决了线程的不同步的问题。可以这样说,在运行共享代码时则是最多只有一个线程进入,也就是和我们说的垄断。锁机制的实现方法,则是在共享代码之前加入synchronized段,把共享代码包含在synchronized段中。上述问题的解决方法为:
/**=============================================================================
* 文件:ThreadDemo_05.java
* 描述:多线程不同步的解决方法--锁
* =============================================================================
*/
// 共享一个静态数据对象
class ShareData{
public static String szData = "";
}
class ThreadDemo extends Thread{
private ShareData oShare;
ThreadDemo(){
}
ThreadDemo(String szName,ShareData oShare){
super(szName);
this.oShare = oShare;
}
public void run(){
// 为了更清楚地看到不正确的结果,这里放一个大的循环
for (int i = 0; i < 50; i++){
if (this.getName().equals("Thread1")){
// 锁定oShare共享对象
synchronized (oShare){
oShare.szData = "这是第 1 个线程";
// 为了演示产生的问题,这里设置一次睡眠
try{
Thread.sleep((int)Math.random() * 100);
catch(InterruptedException e){
}
// 输出结果
System.out.println(this.getName() + ":" + oShare.szData);
}
}else if (this.getName().equals("Thread2")){
// 锁定共享对象
synchronized (oShare){
oShare.szData = "这是第 1 个线程";
// 为了演示产生的问题,这里设置一次睡眠
try{
Thread.sleep((int)Math.random() * 100);
catch(InterruptedException e){
}
// 输出结果
System.out.println(this.getName() + ":" + oShare.szData);
}
}
}
}
class ThreadMain{
public static void main(String argv[]){
ShareData oShare = new ShareData();
ThreadDemo th1 = new ThreadDemo("Thread1",oShare);
ThreadDemo th2 = new ThreadDemo("Thread2",oShare);
th1.start();
th2.start();
}
}
由于过多的synchronized段将会影响程序的运行效率,因此引入了同步方法,同步方法的实现则是将共享代码单独写在一个方法里,在方法前加上synchronized关键字即可。
在线程同步时的两个需要注意的问题:
1、无同步问题:即由于两个或多个线程在进入共享代码前,得到了不同的锁而都进入共享代码而造成。
2、死锁问题:即由于两个或多个线程都无法得到相应的锁而造成的两个线程都等待的现象。这种现象主要是因为相互嵌套的synchronized代码段而造成,因此,在程序中尽可能少用嵌套的synchronized代码段是防止线程死锁的好方法。
在写上面的代码遇到的一个没能解决的问题,在这里拿出来,希望大家讨论是什么原因。
/**=============================================================================
* 文件:ThreadDemo_06.java
* 描述:为什么造成线程的不同步。
* =============================================================================
*/
class ThreadDemo extends Thread{
//共享一个静态数据成员
private static String szShareData = "";
ThreadDemo(){
}
ThreadDemo(String szName){
super(szName);
}
public void run(){
// 为了更清楚地看到不正确的结果,这里放一个大的循环
for (int i = 0; i < 50; i++){
if (this.getName().equals("Thread1")){
synchronized(szShareData){
szShareData = "这是第 1 个线程";
// 为了演示产生的问题,这里设置一次睡眠
try{
Thread.sleep((int)Math.random() * 100);
catch(InterruptedException e){
}
// 输出结果
System.out.println(this.getName() + ":" + szShareData);
}
}else if (this.getName().equals("Thread2")){
synchronized(szShareData){
szShareData = "这是第 1 个线程";
// 为了演示产生的问题,这里设置一次睡眠
try{
Thread.sleep((int)Math.random() * 100);
catch(InterruptedException e){
}
// 输出结果
System.out.println(this.getName() + ":" + szShareData);
}
}
}
}
class ThreadMain{
public static void main(String argv[]){
ThreadDemo th1 = new ThreadDemo("Thread1");
ThreadDemo th2 = new ThreadDemo("Thread2");
th1.start();
th2.start();
}
}
这段代码的共享成员是一个类中的静态成员,按理说,这里进入共享代码时得到的锁应该是同样的锁,而实际上以上程序的输入却是不同步的,为什么呢??
参考:<< 用线程获得强大的性能 >>
四、Java的等待通知机制
在有些时候,我们需要在几个或多个线程中按照一定的秩序来共享一定的资源。例如生产者--消费者的关系,在这一对关系中实际情况总是先有生产者生产了产品后,消费者才有可能消费;又如在父--子关系中,总是先有父亲,然后才能有儿子。然而在没有引入等待通知机制前,我们得到的情况却常常是错误的。这里我引入《用线程获得强大的功能》一文中的生产者--消费者的例子:
/* ==================================================================================
* 文件:ThreadDemo07.java
* 描述:生产者--消费者
* 注:其中的一些注释是我根据自己的理解加注的
* ==================================================================================
*/
// 共享的数据对象
class ShareData{
private char c;
public void setShareChar(char c){
this.c = c;
}
public char getShareChar(){
return this.c;
}
}
// 生产者线程
class Producer extends Thread{
private ShareData s;
Producer(ShareData s){
this.s = s;
}
public void run(){
for (char ch = 'A'; ch <= 'Z'; ch++){
try{
Thread.sleep((int)Math.random() * 4000);
}catch(InterruptedException e){}
// 生产
s.setShareChar(ch);
System.out.println(ch + " producer by producer.");
}
}
}
// 消费者线程
class Consumer extends Thread{
private ShareData s;
Consumer(ShareData s){
this.s = s;
}
public void run(){
char ch;
do{
try{
Thread.sleep((int)Math.random() * 4000);
}catch(InterruptedException e){}
// 消费
ch = s.getShareChar();
System.out.println(ch + " consumer by consumer.");
}while(ch != 'Z');
}
}
class Test{
public static void main(String argv[]){
ShareData s = new ShareData();
new Consumer(s).start();
new Producer(s).start();
}
}
在以上的程序中,模拟了生产者和消费者的关系,生产者在一个循环中不断生产了从A-Z的共享数据,而消费者则不断地消费生产者生产的A-Z的共享数据。我们开始已经说过,在这一对关系中,必须先有生产者生产,才能有消费者消费。但如果运行我们上面这个程序,结果却出现了在生产者没有生产之前,消费都就已经开始消费了或者是生产者生产了却未能被消费者消费这种反常现象。为了解决这一问题,引入了等待通知(wait/notify)机制如下:
1、在生产者没有生产之前,通知消费者等待;在生产者生产之后,马上通知消费者消费。
2、在消费者消费了之后,通知生产者已经消费完,需要生产。
下面修改以上的例子(源自《用线程获得强大的功能》一文):
/* ==================================================================================
* 文件:ThreadDemo08.java
* 描述:生产者--消费者
* 注:其中的一些注释是我根据自己的理解加注的
* ==================================================================================
*/
class ShareData{
private char c;
// 通知变量
private boolean writeable = true;
// -------------------------------------------------------------------------
// 需要注意的是:在调用wait()方法时,需要把它放到一个同步段里,否则将会出现
// "java.lang.IllegalMonitorStateException: current thread not owner"的异常。
// -------------------------------------------------------------------------
public synchronized void setShareChar(char c){
if (!writeable){
try{
// 未消费等待
wait();
}catch(InterruptedException e){}
}
this.c = c;
// 标记已经生产
writeable = false;
// 通知消费者已经生产,可以消费
notify();
}
public synchronized char getShareChar(){
if (writeable){
try{
// 未生产等待
wait();
}catch(InterruptedException e){}
}
// 标记已经消费
writeable = true;
// 通知需要生产
notify();
return this.c;
}
}
// 生产者线程
class Producer extends Thread{
private ShareData s;
Producer(ShareData s){
this.s = s;
}
public void run(){
for (char ch = 'A'; ch <= 'Z'; ch++){
try{
Thread.sleep((int)Math.random() * 400);
}catch(InterruptedException e){}
s.setShareChar(ch);
System.out.println(ch + " producer by producer.");
}
}
}
// 消费者线程
class Consumer extends Thread{
private ShareData s;
Consumer(ShareData s){
this.s = s;
}
public void run(){
char ch;
do{
try{
Thread.sleep((int)Math.random() * 400);
}catch(InterruptedException e){}
ch = s.getShareChar();
System.out.println(ch + " consumer by consumer.**");
}while (ch != 'Z');
}
}
class Test{
public static void main(String argv[]){
ShareData s = new ShareData();
new Consumer(s).start();
new Producer(s).start();
}
}
在以上程序中,设置了一个通知变量,每次在生产者生产和消费者消费之前,都测试通知变量,检查是否可以生产或消费。最开始设置通知变量为true,表示还未生产,在这时候,消费者需要消费,于时修改了通知变量,调用notify()发出通知。这时由于生产者得到通知,生产出第一个产品,修改通知变量,向消费者发出通知。这时如果生产者想要继续生产,但因为检测到通知变量为false,得知消费者还没有生产,所以调用wait()进入等待状态。因此,最后的结果,是生产者每生产一个,就通知消费者消费一个;消费者每消费一个,就通知生产者生产一个,所以不会出现未生产就消费或生产过剩的情况。
五、线程的中断
在很多时候,我们需要在一个线程中调控另一个线程,这时我们就要用到线程的中断。用最简单的话也许可以说它就相当于播放机中的暂停一样,当第一次按下暂停时,播放器停止播放,再一次按下暂停时,继续从刚才暂停的地方开始重新播放。而在Java中,这个暂停按钮就是Interrupt()方法。在第一次调用interrupt()方法时,线程中断;当再一次调用interrupt()方法时,线程继续运行直到终止。这里依然引用《用线程获得强大功能》一文中的程序片断,但为了更方便看到中断的过程,我在原程序的基础上作了些改进,程序如下:
/* ===================================================================================
* 文件:ThreadDemo09.java
* 描述:线程的中断
* ===================================================================================
*/
class ThreadA extends Thread{
private Thread thdOther;
ThreadA(Thread thdOther){
this.thdOther = thdOther;
}
public void run(){
System.out.println(getName() + " 运行...");
int sleepTime = (int)(Math.random() * 10000);
System.out.println(getName() + " 睡眠 " + sleepTime
+ " 毫秒。");
try{
Thread.sleep(sleepTime);
}catch(InterruptedException e){}
System.out.println(getName() + " 觉醒,即将中断线程 B。");
// 中断线程B,线程B暂停运行
thdOther.interrupt();
}
}
class ThreadB extends Thread{
int count = 0;
public void run(){
System.out.println(getName() + " 运行...");
while (!this.isInterrupted()){
System.out.println(getName() + " 运行中 " + count++);
try{
Thread.sleep(10);
}catch(InterruptedException e){
int sleepTime = (int)(Math.random() * 10000);
System.out.println(getName() + " 睡眠" + sleepTime
+ " 毫秒。觉醒后立即运行直到终止。");
try{
Thread.sleep(sleepTime);
}catch(InterruptedException m){}
System.out.println(getName() + " 已经觉醒,运行终止...");
// 重新设置标记,继续运行
this.interrupt();
}
}
System.out.println(getName() + " 终止。");
}
}
class Test{
public static void main(String argv[]){
ThreadB thdb = new ThreadB();
thdb.setName("ThreadB");
ThreadA thda = new ThreadA(thdb);
thda.setName("ThreadA");
thdb.start();
thda.start();
}
}
运行以上程序,你可以清楚地看到中断的过程。首先线程B开始运行,接着运行线程A,在线程A睡眠一段时间觉醒后,调用interrupt()方法中断线程B,此是可能B正在睡眠,觉醒后掏出一个InterruptedException异常,执行其中的语句,为了更清楚地看到线程的中断恢复,我在InterruptedException异常后增加了一次睡眠,当睡眠结束后,线程B调用自身的interrupt()方法恢复中断,这时测试isInterrupt()返回true,线程退出。
更多的线程组及相关的更详细的信息,请参考《用线程获得强大功能》一文及《Thinking in Java》。
相关推荐
Java 语言 编程 - 多 线 程
在Java编程中,多线程是一个核心概念,它允许程序同时执行多个任务,极大地提高了程序的效率和响应性。本篇将深入探讨Java中的多线程机制,包括线程的创建、管理以及如何处理并发环境中的数据安全问题。 首先,我们...
Java 多线程-Socket 编程是指在 Java 语言中使用多线程技术来实现网络编程,特别是使用 Socket 编程来实现客户端和服务器端的通信。在 Java 中,多线程可以使用 Thread 类和 Runnable 接口来实现,而 Socket 编程则...
Java多线程与线程安全编程实践-基于Http协议的断点续传.zip Java多线程与线程安全编程实践-基于Http协议的断点续传.zip Java多线程与线程安全编程实践-基于Http协议的断点续传.zip Java多线程与线程安全编程实践-...
Java多线程编程是Java语言的一大特性,它允许程序同时执行多个任务,提高程序的效率和响应速度。在Java中,可以使用`Thread`类或实现`Runnable`接口来创建线程。在本项目中,多线程被用于实现文件的并发下载,每个...
基于Http协议的断点续传-Java多线程与线程安全实践编程.zip 基于Http协议的断点续传-Java多线程与线程安全实践编程.zip 基于Http协议的断点续传-Java多线程与线程安全实践编程.zip 基于Http协议的断点续传-Java多...
标题中的“大学生计算机网络课程期末作业java聊天系统-多线程cocket”表明这是一个基于Java编程语言实现的网络聊天系统,特别强调了多线程和Socket通信技术的应用。在这个项目中,学生可能需要理解并应用计算机网络...
在Java编程语言中,多线程是程序设计中的一个重要概念,尤其在开发高效能和响应迅速的应用时。这个“JAVA-多线程 所有文件”压缩包很可能包含了一系列关于Java多线程学习的源代码示例和相关文档。下面我们将深入探讨...
综上所述,Java编程语言具有简单、稳健、面向对象、可移植以及多线程等多重特性,使其在计算机软件开发中获得了广泛的应用。随着未来技术的不断发展,Java编程语言在软件开发领域的应用前景将会更加广阔。
多线程是Java编程中不可或缺的一部分,它允许程序同时执行多个独立的任务,从而提高应用程序的效率和响应性。在Java中,多线程是通过实现Runnable接口或继承Thread类来创建的。 1. **线程的基本概念**: - 线程是...
JAVA编程语言在软件开发中具有独特的优势,比如平台独立性、可移植性、多线程以及面向对象的特性,这些特性使得JAVA成为安全软件开发的重要工具。 1. JAVA编程语言的特点 JAVA编程语言自C++语言发展而来,它在继承...
《多线程编程指南》介绍了 SolarisTM 操作系统 (Solaris Operating System, Solaris OS)中 POSIX®线程和 Solaris 线程的多线程...■ C 编程语言-多线程接口由标准 C 库提供。 ■ 并发编程(与顺序编程相对)的原理。
多线程是现代编程语言中的一项重要技术,特别是在Java这样的面向对象语言中,多线程能够极大提高程序的性能和响应速度。线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。 ...
Java线程编程是Java开发中的重要组成部分,尤其在如今并发处理...通过学习和实践这个Java多线程编程教程,你将能够熟练掌握如何在Java环境中创建和管理线程,以及解决并发编程中可能出现的问题,提升你的Java编程能力。
- Java语言内建对多线程的支持,允许程序包含多个同时运行的部分,每个部分称为一个线程,有自己的执行路径。 - 在Java运行时系统中,线程用于实现异步执行,适应单核和多核系统。在多核系统中,线程可以实现真正...
Java语言多线程编程讲义 本文对Java语言的多线程编程进行了详细的讲解,涵盖了Java的线程机制、线程的创建、执行、结束、优先级、状态及状态间的转换、共享访问与线程同步、wait和notify在线程间的交互、Thread类的...
标题中的“聊天室------java编写----多人聊天”表明这是一个基于Java编程语言开发的多用户聊天应用程序。这个项目可能是一个简单的实现,旨在帮助学习者理解如何构建一个基础的网络通信系统,特别是涉及到多人实时...
除了上述领域,Java编程语言还被应用于计算机图像处理技术,如板形识别技术中。研究人员通过增强计算机系统捕捉物体运动图像的效率,利用高分辨率相机来提高图像质量和信息的准确率,进而在医学、工业和通信等领域...
Java语言程序设计线程编程是Java开发中至关重要的一部分,它涉及到多任务并发执行的能力,使得程序能够高效地利用计算资源。以下是对Java线程编程基础知识的详细解释: 1. **线程的概念**: - 线程是程序中执行的...