多线程
一、进程与线程
1、进程
操作系统中一个程序的执行周期称为一个进程。每个进程中至少包含一个线程。
2、线程
进程中负责程序执行的执行单元。线程本身依靠程序进行运行,线程是程序中的顺序控制流。
与进程相比,线程更“轻量级”,创建、撤销一个线程比启动一个新进程开销小得多;没有进程就没有线程,进程一旦终止,其内的线程也不复存在。
3、单线程
程序中只包含一个线程,主方法其实就是一个主线程。
4、多线程
在一个程序中运行多个任务,目的是更好的利用资源。
5、线程的状态
二、多线程的实现
1、继承Thread类
Thread在Java.lang包中定义,在继承Thread必须覆写run()方法。
(1)创建线程
//线程主体类 class MyThread extends Thread{ private static int num = 0; public MyThread(int num) { num++; } //所有线程从该处开始执行 public void run() { System.out.println("主动创建的第 "+num+"个线程"); } }
(2)创建线程对象启动线程
错误的多线程启动方式:
在有了线程的情况下,很自然会产生线程类的实例化对象再调用其的run()方法。实则我们不能直接调用run()方法,run()方法的调用只会做出顺序打印,和多线程无关。
public class MoreThread4_25 { public static void main(String[] args) { Thread mThread1 = new MyThread("第一个线程"); Thread mThread2 = new MyThread("第二个线程"); Thread mThread3 = new MyThread("第三个线程"); mThread1.run(); mThread2.run(); mThread3.run(); } }
正确的多线程启动方式:
实则我们应该使用Thread类中的start()方法来启动线程。
因为public synchronized void start()此方法会自动调用线程的run()方法。
public class MoreThread4_25 { public static void main(String[] args) { Thread mThread1 = new MyThread("第一个线程"); Thread mThread2 = new MyThread("第二个线程"); Thread mThread3 = new MyThread("第三个线程"); mThread1.start(); mThread2.start(); mThread3.start(); } }
重复执行则会发现,所有的线程对象交替执行
(3)start()方法和run()方法调用的区别
class MyThread extends Thread{ private String info; public MyThread(String info) { this.info = info; } //所有线程从该处开始执行 public void run() { System.out.println("主动创建的第 \""+info+"\" 个子线程的ID:"+Thread.currentThread().getId()); } } public class MoreThread4_25 { public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); Thread mThread1 = new MyThread("第一个线程"); Thread mThread2 = new MyThread("第二个线程"); //正确的多线程启动方式 mThread1.start(); //错误的多线程启动 mThread2.run(); } }
从上图输出结果可以看出:
Thread1和Thread2的线程ID不同,Thread2和主线程的ID相同,此时表明run()方法的创建并不会创建新的线程,而是在主线程中运行run()方法,和普通方法没有什么区别。
Thread1的start()方法在Thread2的run()方法之前调用,但是运行结果是先输出Thread2的run()方法的ID,说明新建创建的线程不会影响主线程后续的执行。
2、Runnable()接口实现多线程
在Java中创建线程除了继承Thread类(具有单继承局限)外,还可以通过实现Runnable()接口来实现类似的功能,但是也要覆写run()方法。
(1)实现Runnable接口
class MyRunnable implements Runnable{ private String info; public MyRunnable(String info) { this.info = info; } @Override public void run() { System.out.println("新建线程名:"+this.info+",ID:"+Thread.currentThread().getId()); } } public class MoreThread4_25{ public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyRunnable myRunnable = new MyRunnable("线程1"); //且此时MyThRunnable类继承的不再是Thread类而实现了Runnable接口 //虽然解决了单继承局限问题,但是没有start()方法被继承了。 //那么此时就需要使用到Thread类提供的构造方法。 Thread thread = new Thread(myRunnable); thread.start(); } }
通过实现Runnable接口来实现多线程实则是:
我们定义了一个子任务,然后交给Thread去执行。
这种实现多线程的方式必须将Runnable作为Thread的参数,仍然是利用Thread类的start()方法创建新线程,完成后续工作,和Thread类似若直接调用其的run()方法和普通方法没什么区别,并不会创建新的线程。
(2)Runnable接口对象的内部类和Lambda表达式实现
/* * Runnable接口对象匿名内部类、Lambda表达式实现 * */ public class MoreThread4_25{ public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); //内部类实现Runnable的接口对象 new Thread(new Runnable() { private String info = "线程1"; @Override public void run() { System.out.println("新建线程名:"+this.info+",ID:"+Thread.currentThread().getId()); } }).start(); //Lambda表达式实现Runnable的接口对象 Runnable runnable = () ->{ String info = "线程2"; System.out.println("新建线程名:"+info+",ID:"+Thread.currentThread().getId()); }; new Thread(runnable).start(); } }
3、Thread和Runnable区别
Runnable接口实现多线程要比Thread类好,因为避免了单继承的局限,Thread类是Runnable接口的子类,所以Thread类一定覆写了Runnable的run()方法。且使用Runnable实现的多线程的程序类可以更好的描述出程序共享的概念。
线程类的继承结构:代理设计模式
(1)使用Thread类实现数据共享
class MyThread extends Thread{ private int ticketNum = 5; public void run() { while(this.ticketNum > 0) { System.out.println("剩余票数: "+this.ticketNum--); } } } public class MoreThread4_25{ public static void main(String[] args) { new MyThread().start(); new MyThread().start(); new MyThread().start(); } }
根据结果可以发现启动三个线程,各自处理各自的数据而没有实现共同处理同一组数据。
要想达到共同处理同一组数据,应将ticketNum置为static变量。
(2)使用Runnable实现数据共享
class MyRunnable implements Runnable{ private int ticketNum = 5; public void run() { while(this.ticketNum > 0) { System.out.println("剩余票数: "+this.ticketNum--); } } } public class MoreThread4_25{ public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); new Thread(myRunnable).start(); new Thread(myRunnable).start(); new Thread(myRunnable).start(); } }
Runnable实现的多线程的程序类可以更好的描述出程序共享的概念
4、Callable实现多线程
从JDK1.5开始追加了新的开发包:java.uti.concurrent。这个开发包主要是进行高并发编程使用的,包含很多在高并发操作中会使用的类。在这个包里定义有一个新的接口Callable。
Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是很多时候需要一些返回值,例如某些线程执行完成后可能带来一些返回结果,这种情况下就只能利用Callable来实现多线程。
class MyCallable implements Callable<String>{ private int ticketNum = 5; public String call() throws Exception { while(this.ticketNum > 0) { System.out.println("剩余票数: "+this.ticketNum--); } return "不好意思,刚好没票"; } } public class MoreThread4_25{ public static void main(String[] args) throws InterruptedException, ExecutionException { FutureTask<String> task = new FutureTask<>(new MyCallable()); new Thread(task).start(); new Thread(task).start(); System.out.println(task.get()); } }