前置知识

  • 程序(program)

概念:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。

  • 进程(process)

概念:程序的一次执行过程,或是正在运行的一个程序。
说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

  • 线程(thread)

概念:进程可进一步细化为线程,是一个程序内部的一条执行路径。
说明:线程作为调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),线程切换的开销小。

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

多线程介绍

多线程

  • Java虚拟机允许应用程序并发地运行多个执行线程。
  • 每个线程都有一个优先级,每个线程都可以选择是否标记为一个守护程序。
  • 当某个线程中运行的代码创建一个新Thread对象时,会实例化优先级以及实例化是否为守护程序。



    当Java虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的main方法)。
    Java虚拟机会继续执行线程,直到下列任一情况出现时为止:

  • 调用了Runtime类的exit()方法,并且安全管理器允许退出操作发生。

  • 非守护线程的所有线程都己停止运行,无论是通过从对run方法的调用中返回,还是通过抛出一个传播到run方法之外的异常。


补充:

  1. 线程之间彼此独立,不会互相影响
  2. jvm关闭的前提是所有非守护线程已经运行完毕,然后进行释放资源。

使用多线程:

创建新执行线程的第一种:

执行步骤
1,定义一个类继承Thread类。
2,覆盖Thread类中的run()方法。
3,直接创建Thread的子类对象创建线程。
4,调用start( )方法,start方法有两个作用:①启动当前线程 ②调用当前线程的run( )方法

运行实例
并发编程 - 图1

创建新执行线程的第二种


执行步骤

  1. 实现Runable接口
  2. 覆盖其run( )方法
  3. 通过Thread类创建线程对象,这里假设叫做 t
  4. 将实现Ranable接口类作为对象传进Thread的构造函数中,如new Thread ( t)
  5. 最后调用start方法将线程开启执行(start中调用的是t的run方法)


    并发编程 - 图2

实现 Runnable接口的好处:

  1. 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务的封装成对象
  2. 避免了java单继承的局限性(java支持多实现)。所以,创建线程的第二种方式较为常用

创建新执行线程的第三种

实现Callable接口。

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

实现Callable接口的方式创建多线程比实现Runnable接口更强大:

  1. call()可以返回值的。
  2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
  3. Callable是支持泛型的

创建新执行线程的第四种

开发中用的比较多的一种方式

  1. public class ThreadPool {
  2. public static void main(String[] args) {
  3. //1. 提供指定线程数量的线程池
  4. ExecutorService service = Executors.newFixedThreadPool(10);
  5. ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
  6. //设置线程池的属性
  7. // System.out.println(service.getClass());
  8. // service1.setCorePoolSize(15);
  9. // service1.setKeepAliveTime();
  10. //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
  11. service.execute(new NumberThread());//适合适用于Runnable
  12. service.execute(new NumberThread1());//适合适用于Runnable
  13. // service.submit(Callable callable);//适合使用于Callable
  14. //3.关闭连接池
  15. service.shutdown();
  16. }
  17. }

好处:

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

线程控制

中断线程:

当run方法执行完后,线程就会退出。但有时run方法是永远不会结束的,如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。
那么我们究竟应该如何停止线程呢?

1、线程正常运行状态

在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。

2、线程处于了冻结状态(wait方法),这时无法读取标记

此时可以使用interrupt()方法在线程阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。 更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。

释放执行权yield( )

暂停当前正在执行的线程对象,并执行其他线程。
可以理解为释放执行权,重新参与CPU竞争

强制执行权Join()

join方法会使得当前线程获得执行资格(join方法会抛出InterruptedException异常)
主函数会等该线程运行完毕后,才会再往下执行剩余代码。

阻塞线程sleep()

让进程进入阻塞状态,以毫秒为单位。

  • 注意:sleep抛出的异常只能用try-catch处理,因为Thread类中的run方法并没有throws,我们是继承于Thread类,因此也不能往上抛出异常

线程的优先级:

  • MAX_PRIORITY:10
  • MIN _PRIORITY:1
  • NORM_PRIORITY:5 —>默认优先级

如何获取和设置当前线程的优先级:

  • getPriority():获取线程的优先级
  • etPriority(int p):设置线程的优先级

说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。

线程的分类

用户线程:

守护线程:

  • setDaemon(true) 将当前线程标记为守护线程


    将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机可以立即退出。该方法必须在启动线程前调用

线程的生命周期

image.png

  • 新建:当一个 Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
  • 就绪:处于新建状态的线程被 start后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
  • 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
  • 阻塞:在某种特殊情況下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
  • 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束