多线程

Java对多线程的支持

实现多线程程序的两种方式:
    从Thread 类继承;
    实现Runnable接口。

 

创建线程当然是希望线程执行一段代 码,那么这段代码应该写在什么地方线程才会去执行呢?
    1:创建新执行线程有两种方法。一种方法是将类声明为 Thread的子类。 该子类应重写 Thread 类的 run 方法,如果不重写就会报告错误。接下来可以分配并启动该子类的实例。
    2:创建线程的另一种方法是声明实现 Runnable接口的类。该类然后实现 run 方法。 然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。

 

代码
public class MyThread extends Thread {
/**
* 重写Thread的run方法
*/
@Override
public void run() {// 理解为线程的入口函数
while (true) {
System.out.println(
"thread:" + getName());// MyThread所在线程的名字
//yield();// 让自己暂停一下,让别的线程执行。
}
}
}

 

 

public class MyThread implements Runnable {// 可以达到共享成员的效果。

 

 

代码
package com.thread;

public class PrimeThread {

public static void main(String[] args) {
// main方法也是在一个线程当中被执行的。
// 当java虚拟机启动的时候它就有一个线程负责执行main方法。
MyThread mt = new MyThread();
// mt.setPriority(Thread.MAX_PRIORITY);// 设置线程的优先级
// //mt.setDaemon(true);// 必须在线程启动之前才能将mt设置为后台线程,当仅剩余后台线程运行的时候jvm就退出 。也就是说一个线程当中没有非后台进程在运行。
// mt.start();// 启动线程,导致线程开始,调用run方法

// Thread t = new Thread(mt);
// t.setPriority(Thread.MAX_PRIORITY);// 设置线程的优先级
// t.setDaemon(true);// 必须在线程启动之前才能将t设置为后台线程,当仅剩余后台线程运行的时候jvm就退出 。也就是说一个线程当中没有非后台进程在运行。
// t.start();// 启动线程,导致线程开始,调用run方法

// new Thread(mt).start();
// new Thread(mt).start();
// new Thread(mt).start();
// new Thread(mt).start();

mt.getThread().start();

int index = 0;
while(true) {
if(index++ ==100000)
break;
System.out.println(
"main:" + Thread.currentThread().getName());// main方法所在线程的名字
}
}
}

 

使用多线程误区:

 

代码
public class ThreadDemo01 {
public static void main(String[] args) {
MyThread mt
= new MyThread("THREAD A");
MyThread mt2
= new MyThread("THREAD B");

mt.run();
//直接调用run方法,这样并不会达到多线程的效果。
mt2.run();
}
}

 

从运行结果可以看到,它的运行结果是根据调用方法的顺序进行的,并没有达到多线程的效果。

注意:当我们启动线程的时候应该调用start()。该方法会去自动调用run(),来达到多线程的效果。

 

从此处的效果来看确实是并发执行的。哪个线程先强占到了CPU,哪个线程就先执行。
为什么不直接调用run(),而要调用start()呢?
那么我们先打开Thread.class看看Thread的定义。看看start的定义。
public void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。

 

线程的对象能起用多次吗?start()定义中说了,多次启动一个线程是非法的。

 

public class ThreadDemo01 {
public static void main(String[] args) {
MyThread mt
= new MyThread("THREAD A");
MyThread mt2
= new MyThread("THREAD B");

mt.start();
mt.start();
}
}

运行结果
Exception in thread "main" THREAD A运行0java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:
595)
at com.ThreadDemo01.main(ThreadDemo01.java:
9)

THREAD A运行1
THREAD A运行2
THREAD A运行3
THREAD A运行4
THREAD A运行5
THREAD A运行6
THREAD A运行7
THREAD A运行8
THREAD A运行9

 

实现Runnable接口

代码
public class ThreadDemo01 {
public static void main(String[] args) {
MyThread mt
= new MyThread("THREAD A");
MyThread mt2
= new MyThread("THREAD B");

Thread t1
= new Thread(mt); // 实例化Thread类对象
Thread t2 = new Thread(mt2);// 实例化Thread类对象
t1.start(); // 启动多线程
t2.start();// 启动多线程
}
}

class MyThread implements Runnable {
private String name;

public MyThread(String name) {
this.name = name;
}

public void run() {
for(int i = 0; i < 10; i ++) {
System.out.println(name
+ "运行" + i);
}
}
}

起初由于MyThread继承自Thread,那么MyThread就是Thread的子类,拥有Thread的方法,所以可以调用start(),那么现在MyThread实现了Runnable接口,而Runnable接口只有一个方法,那就是run()。没有start(),那么怎么样才能让实现Runnable接口的类实现并发运行的效果呢?看一下Thread类的构造方法:Thread(Runnable target),它可以传递一个Runnable接口的实现类,那么我们就可以使用以上的构造方法启动多线程。

Thread与Runnable的关系:

public interface Runnable
public class Thread extends Object implements Runnable

  可以发现Thread也是Runnable的实现类。

Thread与Runnable的区别:

如果一个类实现了Runnable,那么它能达到数据共享的效果。如果一个类自Thread,那么它不能达到数据共享的效果。


使用多线程模拟火车站售票系统。

SellThread.java

代码
// 因为线程要访问同一个变量,所以让这个类实现Runnable接口。可以共享数据
public class SellThread implements Runnable {

int tickets = 100;

@Override
public void run() {

while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName()
+ " sell tickets:" + tickets);
tickets
--;
}
}
}

}

TicketSystem.java

售票站
public class TicketSystem {
public static void main(String[] args) {
SellThread st
= new SellThread();
// 不能构造四个SellThread对象,然后依次传递给四个线程,因为这样每一
// 个SellThread对象中都有100张票。它们就是各自卖各自的了。
// 构造四个售票站(线程)同时去买100张票。
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
new Thread(st).start();
}
}
运行结果
Thread-0 sell tickets:100
Thread
-1 sell tickets:100
Thread
-3 sell tickets:99
Thread
-0 sell tickets:99
Thread
-2 sell tickets:99
Thread
-2 sell tickets:95
Thread
-2 sell tickets:94
Thread
-2 sell tickets:93
Thread
-2 sell tickets:92
... ... 中间省略了一部分
Thread
-1 sell tickets:7
Thread
-3 sell tickets:2
Thread
-2 sell tickets:3
Thread
-0 sell tickets:4
Thread
-1 sell tickets:1

这个程序除了卖重复的票以外还有一个潜在的问题:当还剩余最后一张票的时候,也就是tickets=1的时候,第一个线程进入到if语句当中,这个时候它的时间片到期了,第二个线程开始运行。因为tickets仍然为1,所以第二个线程进入到if 语句当中,然而它的时间片也到期了,第三个线程开始运行。因为tickets仍然为1,所以第三个线程进入到if 语句当中,然而它的时间片也到期了,然后就是第四个线程开始运行,它也进入到if语句当中,然后它的时间片到期。第一个线程开始运行,打印卖了1票,然后tickets--变为0;然后第二个线程开始运行,打印卖了0票,然后tickets--变为-1;然后第三个线程开始运行,打印卖了-1票,然后tickets--变为-2;然后第四个线程开始运行,打印卖了-2票,然后tickets--变为-3。结果当中就会出现1,0,-1,-2的结果。这样的结果当然是不正确的。当然在上面没有出现这样的问题,但是根据分析是一定会出现问题的,只是没有遇到而已。

这个问题的发生主要是因为多个线程同时访问了tickets这个变量,而在对这个变量的操作过程当中,是通过几个步骤完成的,而在这些步骤当中,可能发生时间片的轮换,从而导致这个结果,那么我们也来通过一种机制来演示一下这个错误的结果。可以使用Thread类的sleep(long millis),让当前线程睡眠一会儿。

修改SellThread.java类如下:

代码
// 因为线程要访问同一个变量,所以让这个类实现Runnable接口。
public class SellThread implements Runnable {
int tickets = 100;
@Override
public void run() {
while (true) {
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(
10);
}
catch (Exception e) {
e.printStackTrace();
} System.out.println(Thread.currentThread().getName()
+ " sell tickets:" + tickets);
tickets
--;
}
}
}
}
}

这样就不会出现问题了,那么我们就可以将那个Thread.sleep(10);删除了。干净一点。这样卖相同的票(数据相同)的问题也解决了。所以在编写多线程程序的时候就一定要注意这个问题。当多个线程同时操作同一个成员的时候就一定要注意同步的问题。

 

 

 

 

 

 

 

 

posted @ 2010-12-22 15:47  meng72ndsc  阅读(244)  评论(0编辑  收藏  举报