Java多线程机制

237 阅读4分钟

JAVA多线程编程

1.1 进程、线程、多线程

  • 通过并发与并行抢占CPU资源
  • Java 的多线程机制 - 通过 Java 虚拟机实现

线程分为用户线程和守护进程(如GC垃圾回收)。每个 Java 程序有一个缺省的主线程(main方法)。从 main 方法开始执行时,再创建的线程成为程序其他线程。 虚拟机在所有线程结束后才会结束 Java 程序。

1.2 线程的生命周期

  • 新建状态 调用 start() 进入Runable
  • Runable:
    • Running <=> Ready (调度切换)
  • 阻塞(中断)状态
    • Timed Waiting: (限期等待)如sleep,wait,join方法
    • Waiting (无限期等待)
    • Blocked (阻塞状态,通过排他锁的方式实现)
  • Terminated (死亡状态)

1.3 线程调度与优先级

  • 抢占调度模型机制
  • 优先级
    • 线程优先级时一个1~10的整数,默认每个线程都会分配5
    • setPriorit(int grade) 、 getPriority()
    • 并不能绝对保证执行顺序,具体看CPU调度

1.4 线程创建方法

  • Thread 子类 (java.lang包)
    • 须重写 run 方法,其在分配CPU资源时会自动执行
    • 子类.start() 调用开始运行
  • Thread 类
    • Thread( Runnable target )
    class Demo implements Runnable{
        //注意接口中的访问性不可以降低
        public void run(){}
    }
    ...
    Demo demo = new Demo();
    Thread mythread = new Thread(demo);

Runnable 是一个接口,实例对象指向所创建线程的目标对象,调用 start 方法后同上会自动调用 run 方法。
此处还涉及到一个静态代理的概念,如Runnable方式简介通过 Thread创建线程就是种代理。静态代理的条件及好处:

  1. 代理和实体类实现了同一个接口
  2. 使得实体类可以充分解放,交给代理完成部署,自己完成本职工作
  • calloc 线程池批量注册创建线程

1.5 线程中常用方法

  • run()、start()
  • sleep(int millsecond) 使线程放弃CPU资源
  • isAlive() 测试进程是否激活,新建+死亡 return false
  • currentTread() 类方法
  • interrupt()

正在执行的其他进程可使用要唤醒线程.interrupt(),通过产生异常的形式结束休眠。 如无限期等待线程可捕捉到main方法抛出的InterruptedException被唤醒而重新排队。

注:一个正在运行的线程不要再重新new了,否则先前的实体会变成垃圾且不会被垃圾回收掉,如果突然释放可能会引发错误甚至导致设备损坏

1.6 Lamda表达式

实现函数式接口,使得代码简洁。

  • 变化过程:类 -> 静态内部类 -> 局部内部类 -> 匿名类 -> 表达式
  • 表达式简化
    • 去参数类型 注:多个参数,一去都去,且加()
    • 去代码段 注:一个语句时,多条须加{}
    • 去类名+方法名

1.7 操作线程:停止、休眠、礼让 ...

  • stop() 注:一般使用标志位控制run中循环次数,不推荐使用JDK的stop方法
  • sleep(int minseconds)、wait 等
  • yeild() 注:正在执行的进程会回到就绪队列排队,但执行结果取决于CPU调度,即不一定礼让成功
  • join() (线程插队,会造成其他线程阻塞)
  • getState() (线程监测状态)

1.8 守护线程

线程分为用户线程和守护线程(如垃圾回收,后台记录操作,监控内存等),虚拟机不用维护守护进程。

Thread.setDaemon(default false) //默认false(用户进程),true(守护进程)

1.9 线程同步机制

每个对象都有一把锁(锁机制)

  1. 线程的不安全性

  2. sleep 可以放大问题,使用线程 sleep 使并发的线程同时进行而避免CPU太快导致问题捕捉不到

  3. synchronized 关键字的两种方式 同步方法、同步块(隐式锁)

    优化:加方法锁会影响执行效率,代码分为只读代码和修改代码,故只需给修改代码加锁即可 同步块可以是给任意对象加锁的,而同步方法默认是给 this 对象加锁

synchronized(obj){...} //使用方法
  1. 死锁

死锁的产生原因:互相抱着双方想下一步获取的资源而又不松手

  1. 显式锁 - Lock

JDK 5.0 始,更强的线程同步机制,通过显式定义锁对象,实现了java.util.concurrent.locks.Lock接口,比较常用的是其实现类ReentrantLock(可重入锁)。

class demo{
    ReentrantLock lock = new ReentrantLock();
    try{
        lock.lock();
        ...
    }finally{
        lock.unlock();
    }
}
  1. JUC 并发安全类集合(拓展)

1.10 线程通信

synchronized只是实现了线程同步,而未实现不同线程间的通信。

  1. OBJ 对象的方法
  • hashCode()、toString() ...

以下是只能在同步方法/代码块中使用(否则会抛出异常)的方法,用于解决线程通信的方法

  • wait()、wait(long timeout)
  • notify()、notifyAll()
  1. 生产者消费者问题
  2. 管程法与信号灯法(即信号量取反)

1.11 线程池

线程池相关API

  • ExecutorService 线程池接口,有子类如ThreadPoolExecutor
    • void execute(Runnable ommand): 执行任务/命令,无返回值,一般用来执行Runnable
    • Future submit(Callabletask): 有返回值,一般用来执行Callable
    • void shutdown(): 关闭线程池
  • Executors 工具类、线程池的工厂类,用于创建并返回不同类型的线程池