java多线程实现方式

xiaoxiao2021-02-28  63

java内部支持多线程,在JDK1.0的时候就确定了基础的线程模型,在其后的多个版本都是在此基础上面的优化,多线程的实现由多种方式,最常用的是实现runnable接口创建线程

多线程 只有一个执行流程的程序被称为单线程程序,相对而言,拥有多个执行流程的程序,被称为多线程程序。

Java程序运行原理

Java使用Thread类代表线程,所有线程对象都是Thread类或者其子类的实例,new Thread()时JVM会创建一个新的线程(Thread类内部调用实现,反正就是新建了一个线程) ,调用线程对象的start()方法线程启动(java线程启动的固定实现方式,直接调用run()方法不会启动线程,只是普通的方法调用),main方法运行在主线程中,main线程也是守护线程,在程序完全运行结束后才结束,即从头跑到尾的线程,在Thread启动之前的所有程序都是单线程的。

java虚拟机是多线程的,因为除了主线程外,还有垃圾回收线程

线程分类

用户线程就是主线程,用户线程结束完JVM就会停止。

后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。 用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束。

前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。

当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。

线程的创建和启动

Java使用Thread类代表线程,所有线程对象都是Thread类或者其子类的实例。创建线程的方式有四种,分别是: 1)继承Thread类创建线程; 2)实现Runnable接口创建线程; 3)利用Runnable内部类创建线程; 4)使用Callable和Future创建线程。

以上四种方式均可以创建线程,不过它们各有优劣,我将在分别叙述完每一种创建线程的方式后总结概括。

3.1 继承Thread类创建线程 主要步骤为: ① 定义一个类并继承Thread类,此类中需要重写Thread类中的run()方法,这个run()方法就是多线程都需要执行的方法;整个run()方法也叫做线程执行体; ② 创建此类的实例(对象),这时就创建了线程对象; ③ 调用线程对象的start()方法来启动该线程。 举例说明: public class MyThread1 extends Thread { private int a; @Override public void run(){ for(;a<100;a++){ //获取线程的名称并输出 System.out.println("Thread线程创建示例1===="+getName()); } } public static void main(String[] args) { new MyThread1().start(); new MyThread1().start(); } } 上面通过一个简单的例子演示了创建线程的第一种方法(通过继承Thread类创建线程);通过运行以上代码发现有两个线程在并发执行,由于没有对线程进行显示的命名,所以系统默认这两个线程的名称为Thread-0和Thread-1。 那么在上述例子中一共有多少个线程在运行呢?答案是三个! 分别是main(主线程)、Thread-0和Thread-1;我们在多线程编程时一定不要忘记Java程序运行时默认的主线程,main()方法的方法体就是主线程的线程执行体;同理,run()方法就是新建线程的线程执行体。 在程序中如果想要获取当前线程对象可以使用方法:Thread.currentThread(); 如果想要返回线程的名称,则可以使用方法:getName(); 故如果想要获取当前线程的名称可以使用以上二者的搭配形式:Thread.currentThread().getName(); 此外,还可以通过setName(String name)方法为线程设置名字;具体操作步骤是在定义线程后用线程对象调用setName()方法: 在讨论完设置线程名称及获取线程名称的话题后,我们来分析下变量的共享。从以上代码运行结果来看,线程Thread0和线程Thread1分别输出0-99,由此可以看出,使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。 3.2 实现Runnable接口创建线程类 主要步骤为: ① 定义一个类并实现Runnable接口,重写该接口的run()方法,run()方法的方法体依旧是该线程的线程执行体; ② 创建定义的类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象; ③ 调用线程的start()方法来启动该线程。 举例说明: public class MyThread2 implements Runnable { public static void main(String[] args) { MyThread2 m1 = new MyThread2(); Thread t1 = new Thread(m1,"线程1"); Thread t2 = new Thread(m1,"线程2"); t1.start(); t2.start(); } private int a; public void run() { for ( ; a<100 ; a++ ) { System.out.println(Thread.currentThread().getName()+" "+a); } } } 运行上面的程序可以看出:两个子线程的i变量是连续的,也就是说采用Runnable接口的方式创建的两个线程可以共享线程类的实例属性,这是因为我们创建的两个线程共用同一个target(m1),即为两个线程持有同一个对象的锁。 通过对以上两种创建新线程的方法进行比较分析,可以知道两种创建并启动多线程方式的区别是:通过继承Thread类创建的对象即是线程对象,而通过实现Runnable接口创建的类对象只能作为线程对象的target。

3.3 Runnable匿名内部类实现thread

public class MyThread3 { private static int a; public static void main(String[] args) { new Thread(new Runnable(){ @Override public void run(){ for(;a<100;a++){ System.out.println("Thread线程创建示例3===="); } } }).start(); new MyThread3().start(); } }

实现方式同3.2 只不过采用匿名的方式

3.4 通过Callable和Future创建线程 Callable接口是在Java5才提出的,它是Runnable接口的增强版;它提供了一个call()方法作为线程执行体,且call()方法比run()方法更为强大,主要体现在: ① call()方法可以有返回值; ② call()方法可以申明抛出异常。 Java5提供了Future接口来代表Callable接口里call()方法的返回值,并为Futrue接口提供一个FutureTask实现类,此实现类实现了Future接口,并且实现了Runnable接口,可以作为Thread类的target。不过需要提出的是,Callable接口有泛型限制,Callable接口里的泛型形参类型于call()方法返回值类型相同。 主要步骤为:(创建并启动有返回值的线程) ① 创建Callable接口的实现类,并实现call()方法作为线程的执行体,且该call()方法有返回值; //不再是void ② 创建Callable接口实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值; ③ 使用FutureTask对象作为Thread对象的target创建并启动新线程; ④ 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。 举例说明: public class MyThread4 implements Callable<Integer> { public static void main(String[] args) { MyThread4 m1 = new MyThread4();//创建Callable对象 //使用FutureTask来包装Callable对象 FutureTask<Integer> task = new FutureTask<Integer>(m1); Thread t1 = new Thread(task,"有返回值的线程"); t1.start();//启动线程 //获取线程返回值 try { System.out.println("子线程的返回值:"+task.get()); } catch (Exception ex) { ex.printStackTrace(); } } public Integer call()//返回值类型为Integer { int i = 0; for ( ; i<100 ; i++ ) { System.out.println(Thread.currentThread().getName()+" "+i); } return i;//call()可以有返回值 } } 其实,创建Callable实现类与创建Runnable实现类没有太大区别,只是Callable的call()方法允许声明抛出异常,而且允许带返回值。 3.4 四种创建线程方法的对比 由于实现Runnable接口和实现Callable接口创建新线程方法基本一致,这里我们姑且把他们看作是同一类型;这种方式同继承Thread方式相比较,优劣分别为: 1.采用实现Runnable接口和Callable接口的方式创建多线程 ① 优点: 1)实现类只是实现了接口,所以它还可以继承其他类; 2)多个线程可以共享一个target,所以适合多线程处理同一资源的模式,从而可以将CPU、代码和数据分开,较好的体现了面向对象的思想。 ② 缺点: 1)编程比较复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。 2) 采用继承Thread类的方式来创建新线程 ① 优点: 1)编写简单,如果需要访问当前线程,则只需要使用this即可。 ② 缺点: 1)因为线程已经继承了Thread类,所以不能再继承其他类。 3.总结 ① 综合分析,我们一般采用实现Runnable接口和实现Callable接口的方式来创建多线程,更多的时候可以采用Runnable匿名内部类方便的实现多线程。
转载请注明原文地址: https://www.6miu.com/read-35806.html

最新回复(0)