一、创建线程的方式
(1):通过继承Thread类来创建线程
public class ThreadDemo extends Thread { @Override public void run() { boolean flag=true; while(flag){ for(int i=1;i<10;i++){ System.out.println("当前执行的结果时"+i); } flag=false; } } public static void main(String[] args) { ThreadDemo threadDemo=new ThreadDemo(); threadDemo.start();//通过效用start()方法来启动该线程 } }
简易版:
public class ThreadTest { public static void main(String[] args) { Thread thread = new Thread() { public void run() { System.out.println("该线程被执行了"); } }; thread.start(); } }
(2):通过实现Runnable接口来创建线程;
public class RunnableDemo implements Runnable { @Override public void run() { System.out.println("这个线程被执行了"); } public static void main(String[] args) { Thread thread=new Thread(new RunnableDemo()); thread.start(); } }
简易版:
public class ThreadTest { public static void main(String[] args) { Thread thread = new Thread( new Runnable() { public void run() { System.out.println("该线程执行了"); } } ); thread.start(); } }
(3):通过Callable接口创建线程
public class CallableDemo2 { public static void main(String[] args) { FutureTask<Integer> task=new FutureTask<Integer>((Callable<Integer>) ()->{ return 5; }); new Thread(task,"callableDemo").start();//使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口) try { System.out.println("当前线程的返回值:"+task.get()); //get()方法会阻塞,直到子线程执行结束才返回 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。
call()方法可以有返回值
call()方法可以声明抛出异常
Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。
boolean cancel(boolean mayInterruptIfRunning):试图取消该Future里面关联的Callable任务
V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException
boolean isDone():若Callable任务完成,返回True
boolean isCancelled():如果在Callable任务正常完成前被取消,返回True
(4):使用Executor框架创建线程
public class CallableDemo3 { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); ArrayList<Future<String>> arrayList = new ArrayList<>(); for (int i = 0; i < 10; i++) { Future<String> future = executorService.submit(new CallableDemo4(i)); arrayList.add(future); } for (Future<String> arr : arrayList) { try { System.out.println(arr.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } } class CallableDemo4 implements Callable<String> { private Integer id; public CallableDemo4(Integer id) { this.id = id; } @Override public String call() throws Exception { System.out.println("当前线程的名字时:" + Thread.currentThread().getName()); return "call()方法被自动调用,任务返回的结果是:" + id + "当前线程是:"+Thread.currentThread().getName(); } } /* * 执行结果是: 当前线程的名字时:pool-1-thread-2 当前线程的名字时:pool-1-thread-1 当前线程的名字时:pool-1-thread-4 当前线程的名字时:pool-1-thread-2 当前线程的名字时:pool-1-thread-5 call()方法被自动调用,任务返回的结果是:0当前线程是:pool-1-thread-1 call()方法被自动调用,任务返回的结果是:1当前线程是:pool-1-thread-2 当前线程的名字时:pool-1-thread-3 call()方法被自动调用,任务返回的结果是:2当前线程是:pool-1-thread-3 当前线程的名字时:pool-1-thread-8 call()方法被自动调用,任务返回的结果是:3当前线程是:pool-1-thread-4 call()方法被自动调用,任务返回的结果是:4当前线程是:pool-1-thread-5 当前线程的名字时:pool-1-thread-7 当前线程的名字时:pool-1-thread-6 当前线程的名字时:pool-1-thread-9 call()方法被自动调用,任务返回的结果是:5当前线程是:pool-1-thread-6 call()方法被自动调用,任务返回的结果是:6当前线程是:pool-1-thread-7 call()方法被自动调用,任务返回的结果是:7当前线程是:pool-1-thread-8 call()方法被自动调用,任务返回的结果是:8当前线程是:pool-1-thread-9 call()方法被自动调用,任务返回的结果是:9当前线程是:pool-1-thread-2 * */
提交一个Callable对象给ExecutorService,将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。Executor框架的内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。 Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。
ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务;
使用Executor框架还提供了一系列的方法用来创建线程池,返回的线程池都实现了ExecutorService接口。(后续添加)
二、线程的运行状态
线程一共有六种状态,分别为:NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED
RUNNABLED:运行状态,JAVA 线程把操作系统中的就绪和运行两种状态统一称为“运行中”
BLOCKED:阻塞状态,线程进入等待状态,即线程因为某种原因放弃了 CPU 使用权,阻塞分为以下几种情况:
等待阻塞:运行的线程执行 wait ()方法,jvm 会把当前线程放入到等待队列
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么 JVM会把当前的线程放入到锁池中
其他阻塞:运行的线程执行 Thread.sleep(mills) (millis参数设定睡眠的时间,以毫秒为单位)或者线程的join()方法,或者发出了 I/O请求时,JVM 会把当前线程设置为阻塞状态,当 sleep(mills) 结束、join 线程终止、io 处理完毕则线程恢复
TIME_WAITING:超时等待状态,超时以后自动返回
TERMINATED:终止状态,表示当前线程执行完毕
线程之间的转换:
注:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。
通过代码演示线程的状态:
public class ThreadStatus { public static void main(String[] args) { new Thread(() -> { while (true) { try { TimeUnit.SECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }, "statusDemo1").start(); new Thread(() -> { while(true){ synchronized (ThreadStatus.class){ try { ThreadStatus.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } },"waiting").start(); new Thread(new BlockDemo(),"blockDemo1").start(); new Thread(new BlockDemo(),"blockDemo2").start(); } static class BlockDemo extends Thread{ @Override public void run() { while(true){ synchronized(BlockDemo.class){ try { TimeUnit.SECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
使用命令查看线程的状态:
①: 打开终端或者命令提示符,键入“jps”,(JDK1.5 提供的一个显示当前所有 java进程 pid 的命令),可以获得相应进程的 pid
②:根据上一步骤获得的 pid,继续输入 jstack pid(jstack 是 java 虚拟机自带的一种堆栈跟踪工具。jstack 用于打印出给定的 java 进程 ID 或 core file 或远程调试服务的 Java 堆栈信息)
三、线程的停止
线程的停止并不是通过调用stop()方法就可以停止线程,就拿 stop 来说,stop 方法在结束一个线程时并不会保证线程的资源正常释放,因此会导致程序可能出现一些不确定的状态。优雅的去中断一个线程,在线程中提供了一个 interrupt 方法
①:interrupt()方法;当其他线程通过调用当前线程的 interrupt 方法,即通知该线程可以中端了,至于什么时候中断,取决于当前线程自己。线程通过检查资深是否被中断来进行相应,可以通过 isInterrupted()来判断是否被中断
public class InterruptDemo { private static int i; public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(()->{ while(!Thread.currentThread().isInterrupted()){ i++; System.out.println("当前线程的执行结果是"+i); } }); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt(); System.out.println(thread.isInterrupted()); } }
注:这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅
②:通过Thread.interrupted()方法对设置中断标识的线程复位
public class InterruptDemo1 { public static void main(String[] args) throws InterruptedException { Thread thread=new Thread(()->{ while(true){ boolean flag=Thread.currentThread().isInterrupted(); if(flag){ System.out.println("执行前:"+flag); Thread.interrupted(); System.out.println("执行后:"+Thread.currentThread().isInterrupted()); } } }); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt(); } }
四、线程之间的通信
wait():让当前正在执行的线程进行等待,直到收到notify通知才开始执行;
wait(long):在其他线程调用此对象的 notify()
方法或 notifyAll()
方法,或者超过指定的时间前,导致当前线程等待;
notify():唤醒在此对象监视器上等待的单个线程;
notifyAll():唤醒在此对象监视器上等待的所有线程;
注:wait方法是Object类的方法,让当前执行代码的线程进行等待,该方法用来将当前线程置入预执行的队列,并且在调用wati方法处停止代码的执行,直到接收到的notify通知,在调用wait方法时当前线程必须获得对象锁,调用wait方法后,释放锁,因此wait和notify方法必须在同步代码块或者同步方法来执行;否则会抛出IllegalMonitorStateException异常;如果对处于wait等待的线程调用interrupt方法,会抛出InterruptedException;
notify方法在调用时必须获得锁,如果有多个线程进行等待,那么会随机挑选出一个,对其发出notify通知,并使它等待获取对象锁,在执行notify后,当前线程并不会马上释放对象锁,呈wait状态的线程也不会马上获取对象锁,要等到执行notify的线程将程序执行完毕,也就是突出synchronized代码块当前线程才会释放锁;
public class WaitDemo extends Thread { private Object lock; public WaitDemo(Object lock){ this.lock=lock; } public void run(){ synchronized (lock){ if (MyList.getSize()!=5){ try { System.out.println("当前线程停止运行,进入预执行的队列;当前时间是"+Thread.currentThread()); lock.wait(); System.out.println("线程收到notify通知,执行完成"); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
public class NotifyDemo extends Thread{ private Object lock; public NotifyDemo(Object lock) { this.lock = lock; } public void run() { synchronized (lock) { for (int i = 0; i < 10; i++) { MyList.add(); if (MyList.getSize() == 5) { try { lock.notify(); System.out.println("发出notify通知"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("添加了第"+i+"个元素"); } } } }
public class WatiNotifyDemo { public static void main(String[] args) { Object lock=new Object(); WaitDemo waitDemo=new WaitDemo(lock); NotifyDemo notifyDemo=new NotifyDemo(lock); waitDemo.start(); notifyDemo.start(); } } 结果: 当前线程停止运行,进入预执行的队列;当前时间是Thread[Thread-0,5,main] 添加了第0个元素 添加了第1个元素 添加了第2个元素 添加了第3个元素 发出notify通知 添加了第4个元素 添加了第5个元素 添加了第6个元素 添加了第7个元素 添加了第8个元素 添加了第9个元素 线程收到notify通知,执行完成
注:如果notify通知过早的话,那么会打乱程序正常运行的结果;
public class Test { public static void main(String[] args) throws InterruptedException { Object lock=new Object(); new Thread(()->{ synchronized(lock){ try { System.out.println("notify begin"); lock.notify(); System.out.println("notify end"); } catch (Exception e) { e.printStackTrace(); } } }).start(); Thread.sleep(200); new Thread(()->{ synchronized(lock){ try { System.out.println("wait begin"); lock.wait(); System.out.println("wait end"); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } } 运行结果: notify begin notify end wait begin