java多线程总结一:线程的两种创建方式及比较
1.线程的概念:线程(thread)是指一个任务从头至尾的执行流,线程提供一个运行任务的机制,对于java而言,一个程序中可以并发的执行多个线程,这些线程可以在多处理器系统上同时运行。当程序作为一个应用程序运行时,java解释器为main()方法启动一个线程。
2.并行与并发:
(1)并发:在单处理器系统中,多个线程共享CPU时间,而操作系统负责调度及分配资源给它们。
(2)并行:在多处理器系统中,多个处理器可以同时运行多个线程,这些线程在同一时间可以同时运行,而不同于并发,只能多个线程共享CPU时间,同一时间只能运行一个线程。
3.线程的创建:
(1)基础概念:java中每个任务就是一个可运行对象,为了创建任务,必须首先定义任务类,任务类必须实现Runnable接口。而线程本质上讲就是便于任务执行的对象。一个线程的执行过程就是一个任务类中run()方法的执行到结束。
(2)通过Runnable接口创建线程:
a.定义一个任务类实现Runnable接口,实现Runnable接口中的run()方法(run()方法告知系统线程该如何运行),run()方法中定义具体的任务代码或处理逻辑。
b.定义了任务类后,为任务类创建一个任务对象。
c.任务必须在线程中执行,创建一个Tread类的对象,将前面创建的实现了Runnable接口的任务类对象作为参数传递给Tread类的构造方法。
d.调用Tread类对象的start()方法,启动一个线程。它会导致任务的run()方法被执行,当run()方法执行完毕,则线程就终止。
实例代码:
1 package com.muzeet.mutithread; 2 3 //每个任务都是Runable接口的一个实例,任务是可运行对象,线程是便于任务执行的对象。必须创建任务类,重写run方法定义任务 4 public class ThreadDemo1 implements Runnable { 5 private int countDown = 10; 6 @Override 7 //重写run方法,定义任务 8 public void run() { 9 while(countDown-- >0) 10 { 11 System.out.println("$" + Thread.currentThread().getName() 12 + "(" + countDown + ")"); 13 } 14 } 15 //调用start方法会启动一个线程,导致任务中的run方法被调用,run方法执行完毕则线程终止 16 17 public static void main(String[] args) { 18 Runnable demo1 = new ThreadDemo1(); 19 20 Thread thread1 = new Thread(demo1); 21 Thread thread2 = new Thread(demo1); 22 thread1.start(); 23 thread2.start(); 24 25 System.out.println("火箭发射倒计时:"); 26 27 28 } 29 30 }
程序运行结果:
火箭发射倒计时: $Thread-0(9) $Thread-0(8) $Thread-0(7) $Thread-0(6) $Thread-0(5) $Thread-0(4) $Thread-0(3) $Thread-0(2) $Thread-0(1) $Thread-0(0)
同时运行两个任务对象:
public static void main(String[] args) { Runnable demo1 = new ThreadDemo1(); Runnable demo2 = new ThreadDemo1(); Thread thread1 = new Thread(demo1); Thread thread2 = new Thread(demo2); thread1.start(); thread2.start(); System.out.println("火箭发射倒计时:"); }
运行结果:
火箭发射倒计时: $Thread-0(9) $Thread-0(8) $Thread-0(7) $Thread-0(6) $Thread-1(9) $Thread-0(5) $Thread-1(8) $Thread-0(4) $Thread-1(7) $Thread-0(3) $Thread-1(6) $Thread-1(5) $Thread-0(2) $Thread-1(4) $Thread-1(3) $Thread-1(2) $Thread-1(1) $Thread-1(0) $Thread-0(1) $Thread-0(0)
(3)继承Thread类来创建线程:
a.首先创建一个任务类extends Thread类,因为Thread类实现了Runnable接口,所以自定义的任务类也实现了Runnable接口,重新run()方法,其中定义具体的任务代码或处理逻辑。
b.创建一个任务类对象,可以用Thread或者Runnable作为自定义的变量类型。
c.调用自定义对象的start()方法,启动一个线程。
示例代码:
1 package com.muzeet.mutithread; 2 3 //每个任务都是Runable接口的一个实例,任务是可运行对象,线程即可运行对象。必须创建任务类,重写run方法定义任务 4 public class ExtendFromThread extends Thread { 5 private int countDown = 10; 6 @Override 7 //重写run方法,定义任务 8 public void run() { 9 while(countDown-- >0) 10 { 11 System.out.println("$" + this.getName() 12 + "(" + countDown + ")"); 13 } 14 } 15 //调用start方法会启动一个线程,导致任务中的run方法被调用,run方法执行完毕则线程终止 16 17 public static void main(String[] args) { 18 19 ExtendFromThread thread1 = new ExtendFromThread(); 20 ExtendFromThread thread2 = new ExtendFromThread(); 21 thread1.start(); 22 thread2.start(); 23 24 System.out.println("火箭发射倒计时:"); 25 26 27 } 28 29 }
运行结果:
火箭发射倒计时: $Thread-0(9) $Thread-0(8) $Thread-0(7) $Thread-0(6) $Thread-0(5) $Thread-0(4) $Thread-0(3) $Thread-0(2) $Thread-0(1) $Thread-0(0) $Thread-1(9) $Thread-1(8) $Thread-1(7) $Thread-1(6) $Thread-1(5) $Thread-1(4) $Thread-1(3) $Thread-1(2) $Thread-1(1) $Thread-1(0)
一个线程等待另一个线程结束后再执行:当执行PrintNum这个任务时,打印到数字50时,转而去执行打印字符C这个任务,知道线程thread4执行完才继续执行打印数字任务。
1 package com.muzeet.testThread; 2 3 public class PrintNum implements Runnable { 4 5 private int lastNum; 6 7 public PrintNum(int n) 8 { 9 lastNum = n; 10 } 11 12 @Override 13 public void run() { 14 // TODO Auto-generated method stub 15 Thread thread4 = new Thread(new PrintChar('c', 40)); 16 thread4.start(); 17 try { 18 for(int i=1;i<=lastNum;i++) 19 { 20 System.out.println(" " + i); 21 if(i == 50) 22 { 23 24 thread4.join(); 25 26 } 27 } 28 } catch (InterruptedException e) { 29 // TODO Auto-generated catch block 30 e.printStackTrace(); 31 } 32 } 33 34 }
4.两种方法的比较(转载)
首先分析两种方式的输出结果,同样是创建了两个线程,为什么结果不一样呢?
使用实现Runnable接口方式创建线程可以共享同一个目标对象(TreadDemo1 tt=new TreadDemo1();),实现了多个相同线程处理同一份资源。当第一个线程执行完任务后,countDown已经为0,所以第二个线程就不会输出。而继承Thread创建线程的方式,new出了两个任务类对象,有各自的成员变量,相互之间不干扰。
然后再看一段来自JDK的解释:
Runnable
接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run
的无参数方法。
设计该接口的目的是为希望在活动时执行代码的对象提供一个公共协议。例如,Thread
类实现了Runnable
。激活的意思是说某个线程已启动并且尚未停止。
此外,Runnable
为非 Thread
子类的类提供了一种激活方式。通过实例化某个Thread
实例并将自身作为运行目标,就可以运行实现Runnable
的类。大多数情况下,如果只想重写run()
方法,而不重写其他 Thread
方法,那么应使用Runnable
接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。(推荐使用创建任务类,并实现Runnable接口,而不是继承Thread类)
采用继承Thread类方式:
(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
采用实现Runnable接口方式:
(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。