实现多线程的两种方法:继承Thread类或实现Runnable接口
Java中实现多线程有两种方法:继承Thread类和实现Runnable接口,在程序开发中只要是多线程,我们一般都是实现Runnable接口,原因归结为一点:实现接口比继承类要好。
多线程的第一种实现方式:继承Thread类
步骤如下
- 创建一个继承Thread的类(假定为A),并重写Thread的run方法
- 构造一个A类对象,假定为aa
- 调用aa的start方法。(start方法是从Thread继承过来的)
具体例子如下
package org.wrh.concurrent;
/*
* 火车站多站点卖票程序
* */
class MyThread extends Thread{
private int ticketNum = 10;
public void run(){
for (int i=0;i<10;i++)
{
if(ticketNum > 0){
System.out.println(Thread.currentThread().getName()+" 正在卖第 " + ticketNum+"张票");
ticketNum--;
}
}
}
}
public class ThreadDemo01{
public static void main(String[] args){
/*
* 共三个线程对象,分别为各个线程制定了线程的名字
* */
Thread t1= new MyThread();
t1.setName("线程A");
t1.start();
Thread t2= new MyThread();
t2.setName("线程B");
t2.start();
Thread t3= new MyThread();
t3.setName("线程C");
t3.start();
}
}
程序运行的结果如下:
线程A 正在卖第 10张票
线程B 正在卖第 10张票
线程A 正在卖第 9张票
线程B 正在卖第 9张票
线程A 正在卖第 8张票
线程B 正在卖第 8张票
线程C 正在卖第 10张票
线程C 正在卖第 9张票
线程C 正在卖第 8张票
线程C 正在卖第 7张票
线程C 正在卖第 6张票
线程C 正在卖第 5张票
线程C 正在卖第 4张票
线程C 正在卖第 3张票
线程C 正在卖第 2张票
线程C 正在卖第 1张票
线程A 正在卖第 7张票
线程A 正在卖第 6张票
线程A 正在卖第 5张票
线程A 正在卖第 4张票
线程A 正在卖第 3张票
线程A 正在卖第 2张票
线程A 正在卖第 1张票
线程B 正在卖第 7张票
线程B 正在卖第 6张票
线程B 正在卖第 5张票
线程B 正在卖第 4张票
线程B 正在卖第 3张票
线程B 正在卖第 2张票
线程B 正在卖第 1张票
从结果中可以看出,A、B、C三个线程每个线程相互独立的卖了10张票,但实际的火车站售票中,是需要多个线程去共同去卖N张票。
注意:
- Thread中start方法的功能是创建一个新的线程,并自动调用该线程的run方法,直接调用run方法是不会创建一个新的线程的。
- 执行一个线程实际上就是执行该线程的run方法里面的代码。
- 执行完aa.start()方法后并不表示aa所对应的线程就一定会得到执行,只是表示此线程具有了被CPU立即执行的资格。但由于想抢占CPU执行的线程很多,CPU并不一定会立即执行aa所对应的线程。线程的执行由操作系统控制:优先级、时间长短、最长等待时间等。
- 一个Thread对象不能调用两次start(),否则会抛出异常。
多线程的第二种实现方式:实现Runnable接口
步骤如下:
- 定义一个实现了Runnable接口的类,假定为A
- 创建A类的对象aa
- 利用aa构造一个Thread对象tt,
Thread tt=new Thread(aa)
- 调用tt的start方法:`tt.start()
package org.wrh.concurrent;
class MyThread_1 implements Runnable{
private int ticketNum = 20;
public void run(){
for (int i=0;i<10;i++)
{
if(ticketNum > 0){
System.out.println(Thread.currentThread().getName()+" 正在卖第 " + ticketNum+"张票");
ticketNum--;
}
}
}
}
public class ThreadDemo02{
public static void main(String[] args){
MyThread_1 my = new MyThread_1();
new Thread(my).start();
new Thread(my).start();
new Thread(my).start();
}
}
程序运行结果如下:
Thread-1 正在卖第 20张票
Thread-2 正在卖第 20张票
Thread-0 正在卖第 20张票
Thread-2 正在卖第 18张票
Thread-1 正在卖第 19张票
Thread-2 正在卖第 16张票
Thread-0 正在卖第 17张票
Thread-2 正在卖第 14张票
Thread-1 正在卖第 15张票
Thread-2 正在卖第 12张票
Thread-0 正在卖第 13张票
Thread-2 正在卖第 10张票
Thread-1 正在卖第 11张票
Thread-2 正在卖第 8张票
Thread-0 正在卖第 9张票
Thread-2 正在卖第 6张票
Thread-1 正在卖第 7张票
Thread-2 正在卖第 4张票
Thread-0 正在卖第 5张票
Thread-2 正在卖第 2张票
Thread-1 正在卖第 3张票
Thread-0 正在卖第 1张票
从上面结果可以看出:
- 第一点:这三个线程并不是相互独立的卖20张票,而是一起卖20张票。
- 第二点:这三个线程卖票的过程中有冲突,例如,都卖第20张票,
产生第二点的原因为:
一个线程在判断ticketNum为20>0后,还没有来得及减1,另一个线程此时也判断ticketNum为20>0,还没有来得及减1,那么接下来第三个线程开始执行发现ticketNum还是20>0,于是就这样三个线程把第20张票卖了3次。这就需要加入同步操作(即互斥锁),确保同一时刻只有一个线程在执行每次for循环中的操作。而在第一种方法中,并不需要加入同步操作,因为每个线程执行自己Thread对象中的代码,不存在多个线程共同执行同一个方法的情况。
加同步操作后的代码如下:
package org.wrh.concurrent;
class MyThread_1 implements Runnable{
private int ticketNum = 20;
public synchronized void run(){
for (int i=0;i<10;i++)
{
if(ticketNum > 0){
System.out.println(Thread.currentThread().getName()+" 正在卖第 " + ticketNum+"张票");
ticketNum--;
}
}
}
}
public class ThreadDemo02{
public static void main(String[] args){
MyThread_1 my = new MyThread_1();
new Thread(my).start();
new Thread(my).start();
new Thread(my).start();
}
}
上面的代码只是在原有的代码的基础上在run方法前加上了synchronized
关键字来限定即可完成了锁的功能。
程序的结果如下:
Thread-0 正在卖第 20张票
Thread-0 正在卖第 19张票
Thread-0 正在卖第 18张票
Thread-0 正在卖第 17张票
Thread-0 正在卖第 16张票
Thread-0 正在卖第 15张票
Thread-0 正在卖第 14张票
Thread-0 正在卖第 13张票
Thread-0 正在卖第 12张票
Thread-0 正在卖第 11张票
Thread-2 正在卖第 10张票
Thread-2 正在卖第 9张票
Thread-2 正在卖第 8张票
Thread-2 正在卖第 7张票
Thread-2 正在卖第 6张票
Thread-2 正在卖第 5张票
Thread-2 正在卖第 4张票
Thread-2 正在卖第 3张票
Thread-2 正在卖第 2张票
Thread-2 正在卖第 1张票
总共买了20张票,且每张票只卖了一次,这就是实现了同步锁的功能
最后对线程控制的基本方法进行一个介绍
(1)isAlive():判断线程是否还“活”着,即线程是否还未终止
(2)getPriority():获得线程的优先级数值,0表示线程优先级最高。
(3)setPriority():设置线程的优先级
(4)join():用线程对象调用,如果在一个线程A中调用另一个线程B的join方法,线程A将会等待线程B执行完毕后再执行。
(5)yield():让出CPU,当前线程进入就绪队列等待调度。直接用Thread类调用,yield让出CPU执行权给同等级的线程,如果没有相同级别的线程在等待CPU的执行权,则该线程继续执行