多线程

基本概念:程序、进程、线程

程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即一段静态的代码,静态对象。

进程(process):是程序一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期

  • 如:运行中的QQ,运行中的MP3播放器
  • 程序是静态的,进程是动态的
  • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

线程(thread):进程课进一步细化成为线程,是一个程序内部的一条执行路径。

  • 若一个进程同一时间并行执行多个线程,就是支持多线程的。
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器,线程切换的开销小
  • 一个进程中的多个线程共享相同的内存单元/内存地址空间->他们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程通信变得简单、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患

16.多线程与线程创建 - 图1

单核CPU和多核CPU的理解

  • 单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费,才能通过,那么CPU就好比是收费人员。如果有某个人不想交钱,那么收费人员可以把它“挂起”(晾着他,等他想通了,把钱准备好,再去收费)。但是因为CPU时间片单元特别短,因此感觉不出来。
  • 如果是多核的话,才能发挥多线程的效率。(现在的服务器都是多核的)
  • 一个Java应用程序java.exe.其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

并行与并发

  • 并行:多个CPU同时执行任务。比如多个人同时做不同的事情。
  • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多人做同一件事。

每一个线程有各自独立的栈和程序计数器,一个进程中的多个线程,共享一个线程中的堆和方法区

多线程的创建

方式一:继承于Thread类

**1、创建一个类继承于Thread类的子类

2、重写Thread类的run() —> 将此线程要执行的操作声明在run()中

3、创建Thread类子类的对象

4、通过此对象调用start()**:①启动当前线程 ②、调用当前线程的run()

  1. //1、创建一个类继承于Thread类的子类
  2. class MyThread extends Thread{
  3. //2、重写Thread类的run()
  4. @Override
  5. public void run() {
  6. for(int i=0;i<100;i++){
  7. if(i%2 == 0){
  8. System.out.println(i);
  9. }
  10. }
  11. }
  12. }
  13. public class ThreadTest {
  14. public static void main(String[] args) {
  15. //3、创建Thread类子类的对象
  16. MyThread t1 = new MyThread();
  17. //4、通过此对象调用start()
  18. t1.start();
  19. //问题一:直接调用run方法,只是启动了普通的方法
  20. //t1.run();
  21. //问题二:再开启一个线程,遍历100以内的偶数,不可以让已经start()的线程去执行,会报IllegalThreadStateException
  22. t1.start();
  23. //会报以下的错误!!!
  24. <!--Exception in thread "main" java.lang.IllegalThreadStateException-->
  25. <!-- at java.lang.Thread.start(Thread.java:708)-->
  26. <!-- at com.tfp.thread.ThreadTest.main(ThreadTest.java:35)-->
  27. //如下操作仍然是在main()线程中执行的
  28. for(int i=0;i<100;i++){
  29. if(i%2==0){
  30. System.out.println(i+"============main()===============");
  31. }
  32. }
  33. }
  34. }

注意点

**1、如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。

2、run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU
调度决定。

3、想要启动多线程,必须调用start方法。

4、一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上
的异常“IllegalThreadStateException”。**

16.多线程与线程创建 - 图2

常用方法

1、void start():启动当前线程、调用当前线程的run()方法

2、run():通常需要重写Thread类中的此方法,将创建的线程的操作声明在次方法中
3、static Thread currentThread():静态方法,返回执行当前代码的线程

4、String getName():返回当前线程的名称

5、void setName(String name):设置当前线程名称

6、static void yield():线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法

7、join():当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止。
例如,在线程a中调用线程b的join(),此时线程a就会进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态

8、stop():已过时,当执行此方法时,强制结束当前线程。

9、static void sleep(longmillis):(指定时间:毫秒),让当前线程“睡眠”指定的millis毫秒,在指定的毫秒内是阻塞状态
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常

10、isAlive():判断当前线程是否还存活

  1. class HelloThread extends Thread{
  2. @Override
  3. public void run() {
  4. for(int i=0;i<100;i++){
  5. if(i%2 == 0){
  6. try {
  7. sleep(1000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. System.out.println(Thread.currentThread().getName()+":" + i);
  12. }
  13. if(i%20 == 0){
  14. yield();
  15. }
  16. }
  17. }
  18. }
  19. public class ThreadMethodTest {
  20. public static void main(String[] args) {
  21. HelloThread t1=new HelloThread();
  22. t1.setName("线程一");
  23. t1.start();
  24. //给主线程命名
  25. Thread.currentThread().setName("主线程");
  26. for(int i=0;i<100;i++){
  27. if(i%2 == 0){
  28. System.out.println(Thread.currentThread().getName()+":" + i);
  29. }
  30. if(i == 20){
  31. try {
  32. t1.join();
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. }
  38. }
  39. }

线程的调度

调度策略

  • 时间片
  • 抢占式:高优先级的线程抢占CPU

Java的调度方法

  • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
  • 对高优先级,使用优先调度的抢占式策略

线程的优先级

线程的优先级

  • MAX_PRIORITY:10(最大优先级)
  • MIN _PRIORITY:1(最小优先级)
  • NORM_PRIORITY:5(默认优先级)

涉及的方法

  • getPriority():返回线程优先级
  • setPriority():改变线程的优先级

说明

  • 线程创建时继承父线程的优先级
  • 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
  1. /**
  2. *例子:多窗口买票
  3. *
  4. */
  5. class Windows extends Thread{
  6. private static int ticket=100;
  7. @Override
  8. public void run() {
  9. while (true){
  10. if (ticket>0) {
  11. System.out.println(getName()+": 卖票,票号为:"+ticket);
  12. ticket--;
  13. }
  14. else{
  15. break;
  16. }
  17. }
  18. }
  19. }
  20. public class WindowsTest {
  21. public static void main(String[] args) {
  22. Windows w1=new Windows();
  23. Windows w2=new Windows();
  24. Windows w3=new Windows();
  25. w1.setName("窗口1");
  26. w2.setName("窗口2");
  27. w3.setName("窗口3");
  28. w1.start();
  29. w2.start();
  30. w3.start();
  31. }
  32. }

方式二:实现Runnable接口

1、创建一个实现了Runnable接口的类

2、实现类去实现Runnable中的抽象方法:run()

3、创建实现类的对象

4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

5、通过Thread类的对象调用start()

  1. //1、创建一个实现了Runnable接口的类
  2. class Mythread implements Runnable{
  3. //2、实现Runnable接口中的抽象方法
  4. @Override
  5. public void run() {
  6. for(int i=0;i<100;i++){
  7. if (i % 2 == 0) {
  8. System.out.println(Thread.currentThread().getName()+":"+i);
  9. }
  10. }
  11. }
  12. }
  13. public class ThreadRunnable {
  14. public static void main(String[] args) {
  15. //3、创建实现类对象
  16. Mythread myThread=new Mythread();
  17. //4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  18. Thread thread = new Thread(myThread);
  19. //5、通过Thread类的对象调用start()
  20. thread.start();
  21. //再启动一个线程
  22. Thread thread1 = new Thread(myThread);
  23. thread1.start();
  24. }
  25. }
  1. /**
  2. * 例子:多窗口卖票(方式二:实现Runnable接口的方式)
  3. *
  4. */
  5. class Windows1 implements Runnable{
  6. private static int ticket=10000;
  7. @Override
  8. public void run() {
  9. while (true){
  10. if (ticket>0) {
  11. System.out.println(Thread.currentThread().getName()+": 卖票,票号为:"+ticket);
  12. ticket--;
  13. }else{
  14. break;
  15. }
  16. }
  17. }
  18. }
  19. public class WindowsTest1 {
  20. public static void main(String[] args) {
  21. Windows1 windows1=new Windows1();
  22. Thread thread = new Thread(windows1);
  23. Thread thread2 = new Thread(windows1);
  24. Thread thread3 = new Thread(windows1);
  25. thread.setName("窗口1");
  26. thread2.setName("窗口2");
  27. thread3.setName("窗口3");
  28. thread.setPriority(Thread.MIN_PRIORITY);
  29. thread3.setPriority(Thread.MAX_PRIORITY);
  30. thread.start();
  31. thread2.start();
  32. thread3.start();
  33. }
  34. }

比较两种创建线程的实现方式

开发中,优先选择:实现Runnable接口的方式

原因:

**1、实现接口的方式没有类的单继承的局限性

2、实现的方式更适合来处理多个下线程共享数据的问题**

联系

通过查看源码可以发现,Thread类也同样实现了Runnable接口,(public class Thread implements Runnable)并实现重写了Runnable接口中的Run()方法。因此两种方法都需要重写run(),将线程要执行的逻辑声明在run()中

JDK5.0新增线程创建方式

新增方式一:实现Callable接口

步骤

1、创建一个实现callable的实现类

  1. class NumberThread implements Callable{
  2. }

2、实现call方法,将此线程需要执行的操作声明在call()中

  1. @Override
  2. public Object call() throws Exception {
  3. //要执行的操作
  4. }

3、创建Callable接口的实现类对象

  1. NumberThread nt=new NumberThread();

4、将Callable接口的实现类对象传递到FutureTask构造器中,创建FutureTask的对象

  1. FutureTask futureTask=new FutureTask(nt);

5、将FutureTask的对象作为参数传递到Thread类的构造器当中,创建Thread对象

  1. new Thread(futureTask).start();

6、获取Callable中call方法的返回值(可选)

  1. futureTask.get()
  1. /**
  2. * 实现callable接口创建线程 ---JDK5.0新增
  3. */
  4. //1、创建一个实现callable的实现类
  5. class NumberThread implements Callable{
  6. //2、实现call方法,将此线程需要执行的操作声明在call()中
  7. @Override
  8. public Object call() throws Exception {
  9. int sum=0;
  10. for(int i=1;i<=100;i++){
  11. if(i%2 == 0){
  12. System.out.println(i);
  13. sum+=i;
  14. }
  15. }
  16. return sum;
  17. }
  18. }
  19. public class CallableTest {
  20. public static void main(String[] args) {
  21. //3、创建Callable接口的实现类对象
  22. NumberThread nt=new NumberThread();
  23. //4、将此对象传递到FutureTask构造器中,创建FutureTask的对象
  24. FutureTask futureTask=new FutureTask(nt);
  25. //5、将FutureTask的对象作为参数传递到Thread类的构造器当中,创建Thread对象,并调用start()
  26. new Thread(futureTask).start();
  27. try {
  28. //6、获取Callable中call方法的返回值
  29. //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
  30. System.out.println("总和为:"+futureTask.get());
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. } catch (ExecutionException e) {
  34. e.printStackTrace();
  35. }
  36. }
  37. }

Future接口

  • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
  • FutureTask是Future接口的唯一实现类
  • FutureTask 同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

与使用Runnable相比,Callable功能更强大

  • 相比run()方法,可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值
  • 需要借助FutureTask类,比如获取返回结果

新增方式二:线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
  • 好处

    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    • 便于线程管理

      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止
      • 等等……

线程池相关API

  • JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
    • void shutdown() :关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

    • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
    • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
    • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
  1. /**
  2. * 线程池方式创建线程 ---JDK5.0新增
  3. */
  4. class NumberThread1 implements Runnable{
  5. @Override
  6. public void run() {
  7. for(int i=1;i<=100;i++){
  8. if(i%2 == 0){
  9. System.out.println(i);
  10. }
  11. }
  12. }
  13. }
  14. public class ThreadPool {
  15. public static void main(String[] args) {
  16. ExecutorService Service = Executors.newFixedThreadPool(10);
  17. //向下转型(用于设置线程池属性,Service为接口对象,无法设置线程池属性,因为是常量)
  18. ThreadPoolExecutor service1=(ThreadPoolExecutor)Service;
  19. //设置线程池大小
  20. service1.setCorePoolSize(15);
  21. service1.execute(new NumberThread1()); //只适用于Runnable
  22. //service1.submit(Callable callable); //适用于Callable
  23. //3、关闭线程池
  24. service1.shutdown();
  25. }
  26. }