一种时继承java.lang包下的Thread类,另一种是实现java.lang.Runnable接口。
一、继承Thread类创建多线程
①先看一个单线程程序,单线程程序的线程是进程默认创建,在代码中看不到。
当一个Java程序启动时,就会产生一个进程,该进程会默认创建一个线程,在这个线程上会运行main()方法中的代码
public class Example01 {
public static void main(String[] args) {
Lei p=new Lei(); // 创建Lei实例对象,这不是线程对象,只是一个普通的类的对象
p.run(); // 调用Lei类的run()方法
while (true) { // 该循环是一个死循环,打印输出语句
System.out.println("Main方法在运行");
}
}
}
class Lei {
public void run() {
while (true) { // 该循环是一个死循环,打印输出语句
System.out.println("Lei类的run()方法在运行");
}
}
}
结果:一直输出:“Lei类的run()方法在运行”,“Main方法在运行”不会输出,因为前面语句一直霸占这cpu.
②通过 继承 Thread 类实现多线程
public class Example {
public static void main(String[] args) {
NewThread newThread = new NewThread(); // 创建线程MyThread的线程对象
newThread.start(); // 开启线程
while (true) { // 通过死循环语句打印输出
System.out.println("main()方法在运行");
}
}
}
class NewThread extends Thread {
public void run() {
while (true) { // 通过死循环语句打印输出
System.out.println("MyThread类的run()方法在运行");
}
}
}
结果:两条语句交叉输出
二、实现Runnable接口创建多线程
①因为Java只支持单继承,一个类一旦继承了某一个父类就无法再继承Thread类,若Student类继承了Person类之后就不能再继承Thread类了,所以继承Thread类创建多线程具有一定的局限性
②为了克服这个问题,Thread类提供了另外一个构造方法Thread(Runnable target), Runnable是一个接口,它只有一个run()方法。
当使用这个构造方法时,需要为这个方法传递一个实现了Runnable接口的实例对象,然后创建的线程将调用实现了Runnable接口中的run()方法作为运行代码,而不需要调用Tread类中的run()方法。
如下面代码
public class Example {
public static void main(String[] args) {
MyThread myThread = new MyThread(); // 创建MyThread的实例对象
Thread thread=new Thread(myThread); // 创建线程对象
thread.start(); // 开启线程,执行线程中的run()方法
while (true) {
System.out.println("main()方法在运行");
}
}
}
class MyThread implements Runnable {
public void run() { // 线程的代码段,当调用start()方法时,线程从此处开始执行
while (true) {
System.out.println("MyThread类的run()方法在运行");
}
}
}
三、两种方式的对比
假设有200张飞机票,这时,200张飞机票可以看作共享资源,4个售票窗口需要创建4个线程。通过这个案例比较上述两种方式创建多线程的不同
其中为了更直观的显示窗口的售票情况,通过Tread的currentThread()方法得到当前的线程的实例对象,然后调用getName()可以获取到线程的名称。
1、通过继承Thread类实现多线程
public class Example {
public static void main(String[] args) {
new TicketWindow().start(); // 创建一个线程对象TicketWindow并开启
new TicketWindow().start(); // 创建一个线程对象TicketWindow并开启
new TicketWindow().start(); // 创建一个线程对象TicketWindow并开启
new TicketWindow().start(); // 创建一个线程对象TicketWindow并开启
}
}
class TicketWindow extends Thread {
private int tickets = 100;
public void run() {
while (true) { // 通过死循环语句打印语句
if (tickets > 0) {
Thread th = Thread.currentThread(); // 获取当前线程
String th_name = th.getName(); // 获取当前线程的名字
System.out.println(th_name + " 正在发售第 " + tickets--+" 张票 ");
}
}
}
}
结果:
由结果可以看到票会重复出现,都被打印了四次,出现这种现象的原因时四个线程没有共享100张票,而是各自出售了100张票,在程序中创建了四个TicketWindow对象,就等于创建了四个售票程序,每个程序都有100张票,每个线程都在独立的处理各自的资源。
每个线程我们都可以自定义名字,这里用了默认的名字,主线程默认的名字是main,用户创建的第一个线程默认的名字为Thread-0,第二个是Thread-1.以此类推。
2、实现Runnable接口创建多线程
public class Example {
public static void main(String[] args) {
TicketWindow tw = new TicketWindow(); // 创建TicketWindow实例对象tw
new Thread(tw, "窗口1").start(); // 创建线程对象并命名为窗口1,开启线程
new Thread(tw, "窗口2").start(); // 创建线程对象并命名为窗口2,开启线程
new Thread(tw, "窗口3").start(); // 创建线程对象并命名为窗口3,开启线程
new Thread(tw, "窗口4").start(); // 创建线程对象并命名为窗口4,开启线程
}
}
class TicketWindow implements Runnable {
private int tickets = 100;
public void run() {
while (true) {
if (tickets > 0) {
Thread th = Thread.currentThread(); // 获取当前线程
String th_name = th.getName(); // 获取当前线程的名字
System.out.println(th_name + " 正在发售第 " + tickets-- + " 张票 ");
}
}
}
}
结果:
这个结果与现实相一致,实现Runnable接口创建多线程中,只创建了一个对象,然后在一个对象里创建了四个线程,在每个线程上都去调用这个Ticket变量,共享这100张车票。而通过继承Thread类实现的多线程是创建多个对象,一个对象一个线程。大多数情况会用第二种情况。
3、实现Runnable接口创建多线程优点:
①避免Java单继承带来的局限性(一个类一旦继承了某一个父类就无法再继承Thread类)
②适合多个相同程序代码的线程去处理同一个资源的情况。
四、前台线程与后台线程
1、前台线程
①当一个Java程序启动时,就会产生一个进程,该进程会默认创建一个线程,在这个线程上会运行main()方法中的代码,这个线程为前台线程。
②新创建的线程都默认为前台线程。
2、后台线程
当某个线程在启动之前调用了setDeamon(true)语句,这个线程就变成了一个后台线程。下面为例子
class DamonThread implements Runnable { // 创建DamonThread类,实现Runnable接口
public void run() { // 实现接口中的run()方法
while (true) {
System.out.println(Thread.currentThread().getName()
+ "---is running.");
}
}
}
public class Example2 {
public static void main(String[] args) {
System.out.println("main线程是后台线程吗?"+ Thread.currentThread().isDaemon());
DamonThread dt = new DamonThread(); // 创建一个DamonThread对象dt
Thread t = new Thread(dt,"后台线程"); // 创建线程t共享dt资源
System.out.println("t线程默认是后台线程吗? "+t.isDaemon()); // 判断是否为后台线程
t.setDaemon(true); // 将线程t设置为后台线程
t.start(); // 调用start()方法开启线程t
for(int i=0;i<10;i++){
System.out.println(i);
}
System.out.println("前台进程结束.end------");
}
}
结果:
若t为前台进程,当开启线程t后,会执行死循环中的打印语句,但将线程t设置为后台线程后,当前台线程死亡后,JVM会通知后台进程,停止执行,由于从接受指令到做出相应,需要一定的时间,因此,会打印几次“后台进程---is running.”,结束程序。