1、Thread类实现多线程

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。

Java中通过继承Thread类来创建并启动多线程的步骤如下:

1、定义Thread类的子类,并覆写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
2、创建Thread子类的实例,即创建了线程对象
3、调用线程对象的start()方法来启动该线程
代码如下:

自定义线程类:

class  DemoThread extends Thread {
	//定义有线程名称的构造方法
	public DemoThread(String title) {
		// TODO Auto-generated constructor stub
		super(title);
	}
	//覆写父类的run方法,即线程的执行体
	@Override
	public void run() {
		for(int x = 0; x < 10; x++) {
			System.out.println("线程"+getName()+"正在在执行 "+x);
		}
	}

}

虽然线程的执行体是run()方法,但是在实际进行多线程启动时并不能直接调用此方法。由于多线程需要并发执行,所以需要通过操作系统的资源调度才可以执行,这样对多线程的启动就必须调用Thread中的start()方法。

测试类:

public class Demo01{
	public static void main(String[] args) {
		//定义两个线程实例
		Thread mtA = new DemoThread("A");
		Thread mtB = new DemoThread("B");
		//启动两个线程
		mtA.start();
		mtB.start();
	}
	
}

执行结果:

线程B正在在执行 0
线程A正在在执行 0
线程B正在在执行 1
线程A正在在执行 1
线程A正在在执行 2
线程B正在在执行 2
线程A正在在执行 3
线程B正在在执行 3
线程A正在在执行 4
线程B正在在执行 4

要想启动线程就必须依靠Thread类的start()方法执行,线程启动后会默认调用执行体run()方法。(就好比有些家庭会在吃饭前说一句“我要开动啦”,然后就进行“吃饭”)

那为什么不能之间调用run()执行线程呢?

多线程需要操作系统的支持,在start()方法中调用了一个native修饰的start0()方法,使用start()方法启动多线程的操作就可以进行操作系统函数调用。

启动线程的唯一方法就是通过Thread类的start()实例方法

2、Runnable接口实现多线程

使用Thread类的确可以方便地实现多线程,但是考虑到单继承的局限性存在,java提供了Runnable接口来实现多线程,此接口的定义如下:

@FunctionalInterface 	//从jdk1.8引入了Lambda表达式后就变成了函数式接口
public interface Runnable{
	public void run();
}

自定义线程类:

public class RunableDemo implements Runnable{

    @Override
    public void run() {
        int a = 1;
        while (a<20){
            System.out.println(Thread.currentThread().getName()+ a);//Thread.currentThread().getName()为获取当前线程的名字
            a++;
        }
    }
}

继承于Thread类的子类可以直接调用父类中的start()启动线程,而一旦实现Runnable接口,就不再能直接调用start(),所以为了可以继续使用Thread类的启动方法,就需要利用Thread类中的构造方法进行线程对象的包裹。

Thread中的构造方法:public Thread(Runnable target)

Thread类可以接收Runnable对象,就可以使用start()方法了。

测试类:

public class MainThreadDemo {

    public static void main(String[] args) {
        RunableDemo runn=new RunableDemo();
        
        //实例化一个Thread并传入自己的RunableDemo 实例
        Thread thread=new Thread(runn);
        thread.start();

        int a = 1;
        while (a<20){
            //Thread.currentThread().getName()为获取当前线程的名字
            System.out.println(Thread.currentThread().getName()+ a);
            a++;
        }
    }
}

运行结果:

main1
main2
main3
Thread-01
Thread-02
Thread-03
Thread-04
Thread-05
Thread-06
....

其实多运行几遍,你会方法每次运行的结果顺序都不一样,这主要是由于多线程会去抢占CPU的资源,谁抢到了谁就执行,而Main和Thread两个线程一直在争抢。

实际上,当传入一个Runnable target(目标)参数给Thread后,Threadrun()方法就会调用target.run(),参考JDK源代码:

public void run() {  
  if (target != null) {  
   target.run();  
  }  
}
3、Thread与Runnable的区别:

采用继承Thread类方式:

(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。

采用实现Runnable接口方式

1)优点:线程类只是实现了Runable接口,还可以继承其他的类。
在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

多线程开发的本质是在多个线程可以进行同一资源的抢占与处理,而在此种结构中,Thread描述的就是线程对象,而并发资源的描述就可以通过Runnable定义。

举个栗子:现在老师提供了100个苹果发给小朋友,老师叫了3个学生来帮忙发给班里的其他小朋友,那么这个时候“老师”相当拥有一个”资源",三个学生就对同一个资源进行抢占和处理。

代码如下:

自定义一个实现Runnbale接口的类

class  DemoThread implements Runnable {
	private int apple = 100; //定义苹果总数
	@Override
	public void run() {
		for(int x = 0; x < 5; x++) {
			if(this.apple>0) {
				System.out.println("学生"+Thread.currentThread().getName()+"发苹果,当前苹果:"+this.apple--);
			}
		}
	}

}

测试类:

public class Demo01{
	public static void main(String[] args) {
		DemoThread mt = new DemoThread(); 	//定义资源对象
		Thread mtA = new Thread(mt,"A");  	//实例化线程对象
		Thread mtB = new Thread(mt,"B");  	//实例化线程对象
		Thread mtC = new Thread(mt,"C");	//实例化线程对象
		mtA.start();
		mtB.start();
		mtC.start();
	}
	
}

运行结果:

学生B发苹果,当前苹果:100
学生C发苹果,当前苹果:98
学生A发苹果,当前苹果:99
学生A发苹果,当前苹果:95
学生A发苹果,当前苹果:94
学生A发苹果,当前苹果:93
学生C发苹果,当前苹果:96
学生B发苹果,当前苹果:97
学生B发苹果,当前苹果:90
...

小结: 如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

实现Runnable接口比继承Thread类的优势:

1.适合多个相同代码的线程去处理同一个资源。

2.可以避免java中单继承的限制。

3.增加代码的健壮性,实现解耦。代码可以被多个线程共享,代码和数据独立。

4.线程池中只能放入实现Runnable或Callable类线程,不能放入继承Thread的类【线程池概念之后会慢慢涉及】

所以,如果选择哪种方式,尽量选择实现Runnable接口!

4、Callable接口实现多线程

使用Runnable可以解决单继承的局限性问题,但是Runnbale接口实现的多线程会存在一个问题:Runnable接口里面的run()方法不能返回操作结果的,所以为了解决此问题,从JDK1.5开始对于多线程就提供了一个新的接口:java.util.concurrent.Callable,此接口的定义如下:

@FunctionalInterface
public interface Callable<V>{
	public V call() throw Exception;
}

Callable接口定义时设置一个泛型,这样可以避免向下转换类型带来的安全问题。
利用Callable接口实现线程类时,线程的启动依然要通过Thread类来实现。那么Callable和Thread之间的联系在哪呢?(他们的中间商是谁呢?),答案就是FutureTask<V>

FutureTask中的构造方法:FutureTask(Callable callable)

FutureTask可以包装Callable对象,且可以通过方法get()得到返回结果,然后由于FutureTaskRunnable接口的子类,那么其实例对象就可以由Thread包装。这样就将CallableThread联系起来了。

代码如下:

自定义线程类:

package fourteen;

/**
 * 	采用Callable实现多线程,Callable接口有一个带有返回值的call()方法
 * 	要将Callable与Thread联系起来,这里就需要通过FutureTask来实现
 * 	FutureTask是Runnable的子类,可以在Thread类构造方法作为参数实现
 * 	call()方法的返回由get();实现
 */
import java.util.concurrent.Callable;

class MyThread implements Callable<String>{
	@Override
	public String call() throws Exception{   //线程执行方法
		for(int i = 0; i < 10; i++) {
			System.out.println("Hello! Java!");
		}
		return "Welcom to Java World!";	//执行结果
	}
}

测试类:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TestThread {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		MyThread mt =new MyThread(); //定义线程类对象
		FutureTask<String> task = new FutureTask<>(mt);  //将其包装在FutureTask类中
		Thread mt1 = new Thread(task);  //将FutureTask对象包装于Thread中
		mt1.start();		//启动线程
		System.out.println(task.get()); //获取结果
	}
}

运行结果:

Hello! Java!
...
Hello! Java!
Welcom to Java World! Raindrop

Callable接口实现线程的步骤:

  1. 定义线程类实现Callable接口并覆写call()方法
  2. 构建线程类的对象A
  3. 构建FutureTask的对象B包装线程类的对象C
  4. 构建Thread类的对象C包装对象B
  5. 启动对象C,并通过对象B.get()获取返回结果