一、创建线程和使用线程方式
1.1 继承Thread
让线程类继承自Thread类,然后重写run(),把需要多线程调用的代码放到run()中。但是需要开启线程的时候不能调用run方法,而是需要调用start()方法。
/*** 本类用于演示第一种实现多线程的方式*/
class MyThread extends Thread{// 需要我们使用多进程运行的代码就写在run方法里@Overridepublic void run() { for (int i = 1; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "线程正在运行~~~~~~" + i);}}
}
public class ThreadDemo01 extends Thread{public static void main(String[] args) {MyThread thread = new MyThread();thread.start();for (int i = 1; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "线程正在运行======" + i);}}
}
运行结果:(两个线程同时运行,争抢cpu,每次执行结果不固定)
12.2 实现Runnable接口
需要我们创建的线程类实现Runnable接口。实现run方法,把需要多线程调用的代码写在run方法中。由于Runnable接口中只有一个run方法,所以不能直接调用start()。这个时候需要创建一个Thread类的对象来帮助我们开启线程。创建这个Thread类对象的时候需要将我们的线程类作为参数传递给Thread对象。
/*** 本类用于演示第二种实现多线程的方式*/
class MyThread2 implements Runnable{// 需要我们使用多进程运行的代码就写在run方法里@Overridepublic void run() { for (int i = 1; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "线程正在运行~~~~~~" + i);}}
}public class ThreadDemo02 {public static void main(String[] args) { MyThread2 myThread2 = new MyThread2();// myThread2.run();// 由于Runable接口只有一个run方法,所以实现Runnable接口的这种方式,线程本身没有start方法。// 但是必须要调用start方法才能开启多线程,所以需要创建Thread类的对象,把我们的线程类作为参数传递给构造方法。Thread thread = new Thread(myThread2);thread.start();for (int i = 1; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "线程正在运行~~~~~~" + i);} }
}
运行结果:(两个线程同时运行,争抢cpu,每次执行结果不固定)
2.3 实现Callable接口
采用实现Callable接口实现多线程启动方式以上两种的方式不太一样,下面就看一下怎样启动采用实现Callable接口的线程。
第一步:定义一个类实现Callable接口,并且指定call()返回值的类型。将业务逻辑写在call()方法中。
第二步:创建FutureTask对象,并将Callable对象传入FutureTask的构造方法中。
第三步:实例化Thread对象,并在构造方法中传入FurureTask对象。
第四步:启动线程
/*** 本类用于演示第三种实现多线程的方式*/
class MyCallable implements Callable<String>{@Overridepublic String call() throws Exception {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "线程正在运行~~~~~" + i);}return "MyCallable接口执行完成!";} }
public class ThreadDemo03 {public static void main(String[] args) throws Exception {// 1、创建FutureTask实例,创建MyCallable实例FutureTask<String> futureTask = new FutureTask<>(new MyCallable());// 2、创建Thread实例,来执行futureTaskThread thread = new Thread(futureTask);thread.start();// 3、主线程for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "线程正在运行~~~~~" + i);}// 4、获取执行结果System.out.println(futureTask.get());}
}
运行结果:
1.4 线程池
在一个应用程序中,我们需要多次使用线程,也就意味着,我们需要多次创建并销毁线程。而创建并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,所以,我们就提出了线程池的概念。
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池,从概念以及应用场景中,我们可以看出,线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。
那么,我们应该如何创建一个线程池那?Java中已经提供了创建线程池的一个类:Executor而我们创建时,一般使用它的子类:ThreadPoolExecutor。
第一步:使用Executors获取线程池对象;
第二步:通过线程池获取对象获取线程并执行Runable子类实例;
class MyThread4 implements Runnable{// 需要我们使用多进程运行的代码就写在run方法里@Overridepublic void run() { for (int i = 1; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "线程正在运行~~~~~~" + i);}}
}
public class ThreadDemo04 {public static void main(String[] args) { // 第一步:使用Executors获取线程池对象;ExecutorService executorService = Executors.newFixedThreadPool(10);// 第二步:通过线程池获取对象获取线程并执行Runable实例;executorService.execute(new MyThread4()); for (int i = 1; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "线程正在运行~~~~~~" + i);} }
}
运行结果:
二、总结
2.1 实现接口和继承Thread类比较
(1)接口更适合多个相同的程序代码的线程去共享同一个资源。
实现Runnable接口这种方式,可以把多线程之间相同的操作放到一个Runnable接口的run方法中实现,然后可以通过多个thread来执行同一个Runnable实例。可以达到在一个地方开发,多个地方使用,做到代码的复用。
(2)接口可以避免Java中单继承的局限性。
Java的设计是单继承的设计,如果采用继承Thread的方式实现多线程,则不能继承其他的类,就会错失继承很多父类方法。
(3)接口代码可以被多个线程共享,代码和线程独立。
(4)线程池只能放入实现Runnable或Callable接口的线程,不能直接放入继承Thread的类。
2.2 Runnable和Callable接口比较
A、相同点:
(1)两者都是接口;
(2)都可以用来编写多线程程序;
(3)两者都需要调用Thread.start()启动线程;
B、不同点:
(1)实现Callable接口的线程能返回执行结果;而实现Runnable接口的线程不能返回结果;若需要用这个线程执行任务,不需要控制则建议使用Runnable,若需要动态监控任务则建议使用Callable。
(2)Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法不允许抛异常。
(3)实现Callable接口的线程可以调用Future.cancel取消执行,而实现Runnable接口的线程不能。
注意点:Callable接口支持返回执行结果,此刻需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取“将来”结果;不调用此方法时,主线程不会阻塞。只能在不需要并发的地方调用get()方法。