到了多线程这块的内容了,说点什么好呢,内容其实可多可少,在这里一些基本概念就不说了,就说几个我认为重点掌握的地方,不然太多了不是。
线程
线程的创建的四种方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 使用线程池
方式一:继承Thread类
- 定义子类继承Thread类。
- 子类中重写Thread类中的run方法。
- 创建Thread子类对象,即创建了线程对象。
- 调用线程对象start方法:启动线程,调用run方法。
class Threads extends Thread{ public void run(){ for(int i = 0; i < 100; i++){ if(i % 2 != 0){ System.out.println(i); } } } } public class MyThread{ public static void main(String[] args) { //创建线程 Threads mt = new Threads(); //启动线程 mt.start(); } }
Thread类有关方法
- void start(): 启动线程,并执行对象的run()方法
- run(): 线程在被调度时执行的操作
- String getName(): 返回线程的名称
- void setName(String name):设置该线程名称
- static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
- static void yield():线程让步(暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法)
- join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止 低优先级的线程也可以获得执行
- static void sleep(long millis):(指定时间:毫秒)令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。抛出InterruptedException异常
- stop(): 强制线程生命期结束,不推荐使用
- boolean isAlive():返回boolean,判断线程是否还活着
方式二:实现Runnable接口
- 定义子类,实现Runnable接口。
- 子类中重写Runnable接口中的run方法。
- 通过Thread类含参构造器创建线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
public class SecondThread implements Runnable { private int i; @Override 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(st, "线程1").start(); new Thread(st, "线程2").start(); } } } }
方式三:实现Callable接口
- 定义子类,实现Callable接口。
- 子类中重写Callable接口中的call方法。
- 通过Thread类含参构造器创建线程对象。
- 将Callable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 调用Thread类的start方法:开启线程,调用Callable子类接口的call方法。
//1.创建一个实现Callable的实现类 class NumThread implements Callable{ //2.实现call方法,将此线程需要执行的操作声明在call()中 @Override public Object call() throws Exception { int sum = 0; for(int i = 1; i <= 100; i++){ if(i % 2 == 0){ System.out.println(i); sum += i; } } return sum; } } public class CallableTest { public static void main(String[] args) { //3.创建Callable接口实现类的对象 NumThread numThread = new NumThread(); //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象 FutureTask futureTask = new FutureTask(numThread); //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start() new Thread(futureTask).start(); //接收返回值 try { //6.获取Callable中call方法中的返回值 //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值 Object sum = futureTask.get(); System.out.println("总和为:"+sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
Callable与使用Runnable相比:
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
方式四:使用线程池
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具
public class ThreadPoolTest { public static void main(String[] args) { //创建一个具有固定线程数(6)的线程池 ExecutorService pool = Executors.newFixedThreadPool(6); //使用Lambda表达式创建Runnable对象 Runnable target = () -> { for(int i = 0 ; i < 100; i++){ System.out.println(Thread.currentThread().getName()+ " 的i值为:"+i); } }; //向线程池提交两个线程 pool.submit(target); pool.submit(target); //关闭线程池 pool.shutdown(); } }
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
- <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
- void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
说到这有人就会提出疑问,那两个线程一起运行那么先执行谁呢?
这就得说关于java线程的调度了
Java的调度方法
- 同优先级线程组成先进先出队列(先到先服务),使用时间片(一段时间换一个)策略。
- 对高优先级,使用优先调度的抢占式(高优先级的线程抢占CPU)策略。
线程的优先级
- 线程的优先级等级
- MAX_PRIORITY:10
- MIN _PRIORITY:1
- NORM_PRIORITY:5
- 涉及的方法
- getPriority() :返回线程优先值
- setPriority(int newPriority) :改变线程的优先级
- 说明
- 线程创建时继承父线程的优先级。
- 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用。
线程的生命周期
在说生命周期前先看看关于线程的几种状态
- 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源。
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能。
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态。
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束。
上图就是五种状态之间的关系以及转换状态所触发的条件。此处就不用程序演示了,
下一篇文章:
Java多线程下(线程的同步(synchronized&lock用法以及区别)、死锁问题的引入以及避免以及线程的通信(生产者、消费者问题))