多线程
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);删除了。干净一点。这样卖相同的票(数据相同)的问题也解决了。所以在编写多线程程序的时候就一定要注意这个问题。当多个线程同时操作同一个成员的时候就一定要注意同步的问题。