JAVA多线程(一)-----线程的生命周期,创建与控制,
一、线程的创建和启动
1、继承Thread类创建线程类
run()方法的方法体代表了线程需要完成的任务。start()方法来启动该线程。
public class FirstThread extends Thread{ private int i; public void run() { for( ; i < 100 ; i++ ) { //this获得当前线程,getName()返回当前线程的名字 System.out.println(getName() + " " + i); } } public static void main(String[] args) { for(int i = 0; i < 100; i++) { //调用Thread的currentThread()方法获得当前线程 System.out.println(Thread.currentThread().getName()); if(i == 20) { //启动第一个线程 new FirstThread().start(); //启动第二个线程 new FirstThread().start(); } } } }
使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
2、实现Runnable接口创建线程类
public class SecondThread implements Runnable{ private int i; //run方法同样是线程执行体 public void run() { for( ; i < 100 ; i++) { //Runnable,想获取当前线程,只能用Thread.currentThread()方法 System.out.println(Thread.currentThread().getName() + " " + i); } } public static void main(String[] args) { for(int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); if(i == 20) { SecondThread st = new SecondThread(); //通过new Thread(target,name)方法创建新线程 new Thread(st,"新线程1").start(); new Thread(st,"新线程2").start(); } } } }
使用继承Runnable接口的方式创建的多个线程可以共享线程类的实例变量。因为Runnable对象只是线程的target,多个线程共享一个target,所以多个线程可以共享同一个线程类的实例变量
3、使用Callable和Future创建线程
Callable接口是Runnable接口的增强版。以call()方法作为线程执行体,比run()方法更加强大。1.call()方法可以有返回值。2.call()方法可以声明抛出异常
Callable不是Runnable的子接口,所以不能直接作为Thread的target,并且还有一个返回值。
Java5提供了Future接口来代表Callable接口里call()方法的返回值,有一个实现类FutureTask,这个实现类实现了Future接口和Runnable接口----可以作为Thread类的target。
Future接口里定义了如下方法来控制它关联的Callable任务:
boolean cancel(boolean mayInterruptIfRunning):试图取消该Future里关联的Callable任务
V get():返回Callable任务里call()的返回值。调用该方法导致程序阻塞,子线程结束后才会获得返回值
V get(long timeout,TimeUnit unit):最多阻塞timeout和unit指定的时间,超出时间会抛出TimeoutException异常
boolean isCancelled():如果在Callable任务正常完成前被取消,返回true
boolean isDone():如果Callable已经完成,返回true
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class ThirdThread { public static void main(String[] args) { //创建Callable对象 ThirdThread rt = new ThirdThread(); //先使用Lambda表达式创建Callable<Integer>对象 //使用FutureTask来包装Callable对象 FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> { int i = 0; for( ; i < 100 ; i++) { System.out.println(Thread.currentThread().getName() + " 的循环变量i的值: " + i); } //call()方法可以有返回值 return i; }); for(int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " 的循环变量i的值: " + i); if(i == 20) { //实质还是以Callable的对象来创建并启动线程的 new Thread(task,"有返回值的线程").start(); } } try { //获取线程返回值 System.out.println("子线程的返回值: " + task.get()); }catch(Exception ex) { ex.printStackTrace(); } } }
三种创建线程方法对比:
Runnable接口和Callable接口:只实现了接口,还可以继承其他类。共享一个target对象,适合多个相同线程处理同一份资源,将CPU、代码、数据分开
Thread类:不能再继承其他父类。可以直接使用this获得当前线程。
总结:一般推荐采用实现Runnable接口、Callable接口的方式来创建多线程
二、线程的生命周期
1、新建和就绪状态
程序new了一个线程之后,线程就处于新建状态,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。
当线程对象调用了start()之后,线程处于就绪状态,Java虚拟机为其创建方法调用栈和程序计数器,表示线程可以运行了。
至于该线程何时开始运行,取决于JVM里线程调度器的调度。
2、运行和阻塞状态
处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态。
所有现代的桌面和服务器操作系统都采用抢占式调度策略;但一些小型设备如手机则可能采用协作式调度策略,必须由该线程主动放弃所占用的资源。
被阻塞的线程会在合适的时候重新进入就绪状态,等待线程调度器的再次调度。
3、线程死亡
线程会以如下三种方式结束,结束后就处于死亡状态:
run()或call()方法执行完成,线程正常结束。
线程抛出一个未捕获的Exception或Error。
直接调用该线程的stop()方法来结束该线程----容易导致死锁,不推荐使用
isAlive()方法:处于就绪,阻塞,运行,返回true;处于新建,死亡,返回false
三、控制线程
1、join线程
当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止
join():等待被join的线程执行完成。
join(long millis):等待被join的线程的时间最长为millis毫秒,超出时间则不再等待。
public class JoinThread extends Thread{ //提供一个有参数的构造器,用于设置该现成的名字 public JoinThread(String name) { super(name); } public void run() { for(int i = 0; i < 100; i++) { System.out.println(getName() + " " + i); } } public static void main(String[] args) throws Exception { new JoinThread("新线程").start(); for(int i = 0; i < 100; i++) { if(i == 20) { JoinThread jt = new JoinThread("被join的线程"); jt.start(); //main线程调用了jt线程的jt()方法,main线程必须等待jt执行结束才会向下执行 jt.join(); } System.out.println(Thread.currentThread().getName() + " " + i); } } }
2、后台线程
后台线程:在后台运行,为其他线程提供服务。JVM的垃圾回收线程就是经典的后台线程
特征:如果所有的前台线程都死亡,后台线程会自动死亡。
public class DaemonThread extends Thread{ public void run() { for(int i = 0; i < 1000; i++) { System.out.println(getName() + " " + i); } } public static void main(String[] args) throws Exception { DaemonThread t = new DaemonThread(); //将此线程设置成后台线程 t.setDaemon(true); t.start(); for(int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } //-----程序执行到此处,前台线程(main线程 )结束----- //后台线程也应该随之结束 } }
3、线程睡眠:sleep
static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态 。
还有一个与sleep()方法有点相似的yield()静态方法:可以让当前正在执行的线程暂停,但不会阻塞,只是将线程转入就绪状态。
4、改变线程优先级
线程默认与创建它的父线程优先级相同,
Thread类提供了setPriority(int newPriority),用来设置和返回指定线程的优先级,参数是一个整数,范围是1~10之间。或使用三个静态常量:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
Thread.currentThread().setPriority(6)
由于不同的操作系统优先级不相同,为了更好的移植性,推荐使用三个静态常量。
浙公网安备 33010602011771号