Java多线程程序设计详细解析


  一、 了解多线程

多线程是这样一种机制,它同意在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间 彼此独立 。

线程又称为轻量级 历程,它和 历程一样 占有独立的执行操纵,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属 历程中的其它线程共享一个存储空间,这使得线程间的通讯远较 历程 容易 。

多个线程的执行是并发的,也便是在逻辑上“同时”,而 无论是不是是物理上的“同时” 。假如系统惟独一个CPU,那么真正的“同时”是不可能的,然而由于CPU的速度十分快,消费者觉得不到其中的区别, 因此我们也不用关怀它, 惟独求 设计各个线程是同时执行即可 。

多线程和传统的单线程在程序设计上最大的区别在于,由于各个线程的操纵流彼此独立,使得各个线程中间的代码是乱序执行的,由此带来的线程调度,同步等问题,将在以后探讨 。

  二、在Java中实现多线程

我们 不妨 设计,为了 缔造一个新的线程,我们需求做些什么?很显然,我们必须指明这个线程所要执行的代码,而这便是在Java中实现多线程我们所需求做的 所有!

真是神秘!Java是如何做到这丝毫的?通过类!作为一个 彻底面向对象的语言,Java提供了类java.lang.Thread来容易多线程编程,这个类提供了大量的 步骤来容易我们操纵自己的各个线程,我们以后的 探讨都将环绕这个类进行 。

那么如何提供应 Java 我们要线程执行的代码呢?让我们来看一看 Thread 类 。Thread 类最主要的 步骤是run(),它为Thread类的 步骤start()所调用,提供我们的线程所要执行的代码 。为了指定我们自己的代码, 惟独求 遮蔽它!

步骤一:继承 Thread 类, 遮蔽 步骤 run(),我们在 缔造的 Thread 类的子类中重写 run() ,加入线程所要执行的代码即可 。下面是一个例子:

public class MyThread extends Thread

{

int count= 1, number;

public MyThread(int num)

{

number = num;

System.out.println

(" 缔造线程 " + number);

}

public void run() {

while(true) {

System.out.println

("线程 " + number + ":计数 " + count);

if(++count== 6) return;

}

}

public static void main(String args[])

{

for(int i = 0;

i 〈 5; i++) new MyThread(i+1).start();

}

}

这种 步骤 容易明了, 相符大家的习惯,然而,它也有一个很大的缺陷,那便是假如我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则 无奈再继承 Thread 类,这时假如我们又不想 构建一个新的类,应该怎么办呢?

我们 不妨来探究一种新的 步骤:我们不 缔造Thread类的子类,而是直接 使用它,那么我们不得不将我们的 步骤作为参数传递给 Thread 类的实例,有点 类似回调函数 。然而 Java 没有指针,我们不得不传递一个包括这个 步骤的类的实例 。

那么如何 制约这个类必须包括这一 步骤呢?固然是 使用接口!( 固然 形象类也可满足,然而需求继承,而我们之所以要采纳这种新 步骤,不便是为了幸免继承带来的 制约吗?)

Java 提供了接口 java.lang.Runnable 来 支撑这种 步骤 。

步骤二:实现 Runnable 接口

Runnable接口惟独一个 步骤run(),我们申明自己的类实现Runnable接口并提供这一 步骤,将我们的线程代码写入其中,就 实现了这一 部分的 使命 。然而Runnable接口并没有任何对线程的 支撑,我们还必须 缔造Thread类的实例,这丝毫通过Thread类的 构造函数public Thread(Runnable target);来实现 。下面是一个例子:

public class MyThread implements Runnable

{

int count= 1, number;

public MyThread(int num)

{

number = num;

System.out.println(" 缔造线程 " + number);

}

public void run()

{

while(true)

{

System.out.println

("线程 " + number + ":计数 " + count);

if(++count== 6) return;

}

}

public static void main(String args[])

{

for(int i = 0; i 〈 5;

i++) new Thread(new MyThread(i+1)).start();

}

}

严格地说, 缔造Thread子类的实例也是可行的,然而必须 留神的是,该子类必须没有 遮蔽 Thread 类的 run 步骤,不然该线程执行的将是子类的 run 步骤,而不是我们用以实现Runnable 接口的类的 run 步骤,对此大家 不妨试验一下 。

使用 Runnable 接口来实现多线程使得我们 可以在一个类中 包容全部的代码,有利于封装,它的缺陷在于,我们不得不 使用一套代码,若想 缔造多个线程并使各个线程执行不同的代码,则仍必须额外 缔造类,假如这样的话,在大多数状况下 兴许还不如直接用多个类分别继承 Thread 来得紧凑 。

综上所述,两种 步骤各有千秋,大家 可以灵便 使用 。

下面让我们一同来探究一下多线程 使用中的一些问题 。

  三、线程的四种状态

1. 新状态:线程已被 缔造但尚未执行(start() 尚未被调用) 。

2. 可执行状态:线程 可以执行, 固然不 定然正在执行 。CPU 工夫随时可能被 调配给该线程,从而使得它执行 。

3. 死亡状态: 畸形状况下 run() 返回使得线程死亡 。调用 stop()或 destroy() 亦有同样 动机,然而不被推举,前者会产生 异样,后者是强制终止,不会 开释锁 。

4. 堵塞状态:线程不会被 调配 CPU 工夫, 无奈执行 。

  四、线程的优先级

线程的优先级代表该线程的主要程度,当有多个线程同时处于可执行状态并期待 获得 CPU 工夫时,线程调度系统依据各个线程的优先级来决定给谁 调配 CPU 工夫,优先级高的线程有更大的机会 获得 CPU 工夫,优先级低的线程也不是没有机会,只不过机会要小一些罢了 。

你 可以调用 Thread 类的 步骤 getPriority() 和 setPriority()来存取线程的优先级,线程的优先级界于1(MIN_PRIORITY)和10(MAX_PRIORITY)中间,缺省是5(NORM_PRIORITY) 。

  五、线程的同步

由于同一 历程的多个线程共享同一片存储空间,在带来容易的同时,也带来了 拜访 摩擦这个严峻的问题 。Java语言提供了专门机制以解决这种 摩擦,有效幸免了同一个数据对象被多个线程同时 拜访 。

由于我们 可以通过 private 要害字来 保障数据对象不得不被 步骤 拜访,所以我们 惟独针对 步骤提出一套机制,这套机制便是 synchronized 要害字,它包括两种用法:synchronized 步骤和 synchronized 块 。

1. synchronized 步骤:通过在 步骤申明中加入 synchronized 要害字来申明 synchronized 步骤 。如:

public synchronized void accessVal(int newVal);

synchronized 步骤操纵对类成员变量的 拜访:每个类实例对应一把锁,每个 synchronized 步骤都必须 获得调用该 步骤的类实例的锁方能执行,不然所属线程堵塞, 步骤一旦执行,就独占该锁,直到从该 步骤返回时才将锁 开释, 尔后被堵塞的线程方能 获得该锁,再一次进入可执行状态 。

这种机制确保了同一时刻关于每一个类实例,其全部申明为 synchronized 的成员函数中至多惟独一个处于可执行状态(由于至多惟独一个 可以 获得该类实例对应的锁),从而有效幸免了类成员变量的 拜访 摩擦( 惟独全部可能 拜访类成员变量的 步骤均被申明为 synchronized) 。

在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数申明为 synchronized ,以操纵其对类的静态成员变量的 拜访 。

synchronized 步骤的缺陷:若将一个大的 步骤申明为synchronized 将会大大影响效率,典型地,若将线程类的 步骤 run() 申明为 synchronized ,由于在线程的整个生命期内它向来在运行, 因此将招致它对本类任何 synchronized 步骤的调用都永远不会 顺利 。固然我们 可以通过将 拜访类成员变量的代码放到专门的 步骤中,将其申明为 synchronized ,并在主 步骤中调用来解决这一问题,然而 Java 为我们提供了更好的解决 步骤,那便是 synchronized 块 。

2. synchronized 块:通过 synchronized 要害字来申明synchronized 块 。语法如下:

synchronized(syncObject)

{

//同意 拜访操纵的代码

}