写在前面
今天继续讲Java中的进程和线程的知识!
进程和线程概述
进程是正在运行的步伐,是体系进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和体系资源。
线程是进程中的单个次序控制流,是一条执行路径。一个进程如果只有一条执行路径,则称为单线程步伐;如果有多条执行路径,则称为多线程步伐。
Java步伐运行原理
Java命令会启动Java虚拟机,即JVM,等同于启动了一个应用步伐进程。该进程会自动启动一个“主线程”,然后主线程去调用某个类的main方法。因此,main方法运行在主线程中。在此之前的所有步伐都是单线程的。
JVM虚拟机的启动是单线程的还是多线程的?
答:JVM的启动是多线程的,包括主线程和垃圾回收线程等。
多线程实现
- 怎样创建一个线程对象?
- a. 自定义线程类继承Thread类,重写run方法。
- b. 自定义线程类实现Runnable接口,实现run方法。
- c. 自定义线程类实现Callable接口,借助线程池,实现run方法。
这里的run方法是针对一个线程对象它所干的事。
- 怎样启动一个线程?
- 调用start()方法启动,Thread类中有start()方法来控制每个线程的开始。固然也有stop来控制线程的结束。如果单纯调用run方法则不是使用线程的思想来考虑问题,而是简朴的对象调用成员方法!
Thread类的基本方法
- 为什么要重写run()方法?
- 启动线程使用的是哪个方法?
- 使用start()方法启动线程,而不是直接调用run()方法。
- 线程能不能多次启动?
- 不能,线程一旦启动就进入了停当态,之后通过抢占式来夺取运行权。正在运行当中的线程可以通过相关操纵进行阻塞回到停当大概同步该进程。
- run()和start()方法的区别
- run方法描述了线程具体执行的代码体,重写在继承Thread的子类或实现Runnable接口的类中。而start方法用于启动一个新线程,执行该线程的run方法。
Thread类中的成员方法
- 获取线程对象的名字
- public final String getName();
复制代码 - 设置线程对象名字的方式
- a. 通过父类的有参构造方法,在创建线程对象的时间设置名字。
- b. 线程对象调用setName(String name)方法,给线程对象设置名字。
- 获取线程的优先级
- public final int getPriority();
复制代码 - 设置线程优先级
- public final void setPriority(int i);
复制代码
- 怎样获取main方法地点的线程名称?
- 使用静态方法 Thread.currentThread().getName(),如允许以获取任意方法地点的线程名称。
Runnable接口
实现Runnable接口的利益:
- 可以避免由于Java单继承带来的局限性。
- 适合多个相同步伐的代码去处理同一个资源的情况,把线程同步伐的代码、数据有效分离,较好地体现了面向对象的设计思想。
Callable接口
- 和线程池执行Runnable对象的差不多。
- 利益:
- 弊端:
注意
- 启动一个线程的时间,若直接调用run方法,仅仅是普通的对象调用方法,底层不会额外创建一个线程再执行。
- 从执行的结果上来看,Java线程之间是抢占式执行的,谁先抢到CPU执行权谁就先执行。
- 每次运行的结果次序不可预测,完全随机的。
- 每个线程都有优先权。具有较高优先级的线程优先于优先级较低的线程执行。
调度模型
Java线程有两种调度模型:
- 分时调度模型
- 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
- 抢占式调度模型
- 优先让优先级高的线程使用CPU,如果线程的优先级相同,则随机选择一个线程执行。优先级高的线程获取的CPU时间片相对多一些。
注意
演示怎样设置和获取线程优先级
- public final int getPriority();
- public final void setPriority(int newPriority);
复制代码 设置线程优先级通过setPriority(int i)方法,在启动之前设置,优先级范围为1到10。
对象线程控制方法
- 线程休眠
- public static void sleep(long millis);
复制代码 - 线程加入
- public final void join();
复制代码 - 线程礼让
- public static void yield();
复制代码 - 后台线程
- public final void setDaemon(boolean on);
复制代码 - 中断线程
- public final void stop();
- public void interrupt();
复制代码 线程的生命周期
线程的生命周期包括以下几个状态:
- 新建(New):线程对象创建后进入此状态,尚未开始执行。
- 停当(Runnable):线程调用了start()方法后,进入此状态,等候CPU资源。
- 运行(Running):线程获得CPU时间片后,进入此状态,现实执行run()方法中的代码。
- 阻塞(Blocked):线程因等候某个资源而阻塞,例如等候I/O操纵或锁。
- 等候(Waiting):线程在等候另一个线程的特定条件(如等候通知)时处于此状态。
- 超时等候(Timed Waiting):线程在指定的时间内等候,例如调用Thread.sleep()。
- 死亡(Terminated):线程完成执行或因异常终止后进入此状态。
解决线程安全问题的基本思想
问题判断
- 是否是多线程情况?
- 是否有共享数据?
- 是否有多条语句操纵共享数据?
基本思想
让步伐没有安全问题的情况。核心思想是确保同一时间只有一个线程能操纵共享数据。
实现方式
- 同步代码块
- 格式:
- synchronized (对象) {
- // 需要同步的代码
- }
复制代码 - 解释: 同步的根本原因在于锁住的对象。锁对象如同锁的功能,确保同一时间只有一个线程可以或许执行同步代码块。
- 同步的前提:
- 同步的利益: 解决多线程安全问题。
- 同步的弊端: 当线程许多时,判断同步锁的开销高,可能降低步伐运行效率。
同步代码块的对象可以是:
- 任意对象实例
- 当前对象(this)
- 类对象(Class 对象)
- 常量对象
注意事项:
- 选择合适的锁对象:避免死锁,推荐使用实例对象或类对象。
- 锁的粒度:粒度过细会导致性能问题,粒度过粗可能导致不必要的线程阻塞。
- 避免死锁:确保获取锁的次序同等。
- 同步方法
- 实例方法: 锁对象是当前实例 (this)。
- 静态方法: 锁对象是该类的 Class 对象。
建议: 如果锁对象是 this,可以考虑使用同步方法。如果锁对象是其他对象,建议使用同步代码块。
- Lock锁的使用
- 特点: 提供显式的加锁和解锁操纵,避免隐式锁的开销。
- 接口:
- void lock();
- void unlock();
复制代码 - 实现: ReentrantLock 是常用的实现。
- import java.util.concurrent.locks.ReentrantLock;
- public static final ReentrantLock lock1 = new ReentrantLock();
- public static final ReentrantLock lock2 = new ReentrantLock();
复制代码 - 弊端:
死锁问题
- 定义: 两个或更多线程因争夺资源产生的互相等候现象。
线程的等候唤醒机制(生产者消耗者模型)
- 需求: 只有当产品池中有数据时消耗者才去消耗,只有当产品池中没有数据时生产者才去生产。
- 实现: 使用等候唤醒模式,生产者等候消耗者的唤醒才开始生产,消耗者等候生产者的唤醒才开始消耗。
线程的状态转换图
线程组和线程池
线程组
- 定义: Java中使用 ThreadGroup 来表示线程组,允许对一批线程进行分类管理。
- 获取线程组:
- public final ThreadGroup getThreadGroup();
复制代码 - 设置线程分组:
- Thread(ThreadGroup group, Runnable target, String name);
复制代码 线程池
- 长处: 提高性能,淘汰创建和销毁线程的开销,线程池中的线程复用。
- JDK5 之前: 手动实现线程池。
- JDK5 及以后: 提供 Executors 工厂类来创建线程池。
- 方法:
- public static ExecutorService newCachedThreadPool();
- public static ExecutorService newFixedThreadPool(int nThreads);
- public static ExecutorService newSingleThreadExecutor();
复制代码 - ExecutorService 方法:
- Future<?> submit(Runnable task);
- <T> Future<T> submit(Callable<T> task);
复制代码
常用线程池
- 固定巨细线程池
- static ExecutorService newFixedThreadPool(int nThreads);
复制代码
- 单线程池
- static ExecutorService newSingleThreadExecutor();
复制代码
- 缓存线程池
- static ExecutorService newCachedThreadPool();
复制代码
- 调度线程池
- static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
复制代码
匿名内部类方式使用多线程
[code]pool.submit(new Runnable() { @Override public void run() { for (int i = 1; i |