1. 进程和线程的概述
进程:正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源
多进程的意义:提高CPU使用率
线程:是进程中的单个顺序控制流,是一条执行路径
- 单线程——一个进程如果只有一条执行路径,则称为单线程程序
- 多线程——一个进程如果有多条执行路径,则称为多线程程序
多线程的意义:提高应用程序的使用率
Java程序运行原理
- java 命令会启动 java 虚拟机,相当于启动了一个应用程序,即一个进程
- 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法
- 所以 main方法运行在主线程中,在此之前的所有程序都是单线程的
JVM的启动是多线程的,至少启动了主线程和垃圾回收器两个线程
线程是依赖于进程而存在的
2. 多线程的实现方式1
方式1:继承Thread类
步骤:
- 自定义类MyThread继承Thread类
- MyThread类里面重写run()
- 创建线程对象
- 启动线程
并不是类中的所有代码都使用多线程,用run()方法来包含使用线程的那部分代码
run()和start()的区别?
- run()——仅仅是封装被线程执行的代码,直接调用是普通方法
- start()——首先启动了线程,然后再由jvm去调用该线程的run()方法
同一个线程不能被多次调用
获取和设置线程名称的方法:
- public final String getName()——获取名字
- public final void setName(String name)——设置名字(相当于无参构造+Set方法)
- 在MyThread类中写无参和带参构造(继承自Thread)——设置名字
针对不是Thread类的子类如何获取线程对象名称?
- public static Thread currentThread()——返回当前正在执行的线程对象
代码:Thread.currentThread().getName()
线程调度
线程的两种调度模型
分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型
获取和设置线程优先级的方法:
- public final int getPriority()——获取线程优先级
- public final void setPriority(int newPriority)——更改线程优先级
线程优先级范围1~10,默认5
线程优先级仅仅表示优先几率大,不是一定优先
线程控制
线程休眠
- public static void sleep(long millis) ——指定毫秒内让当前正在执行的线程暂停执行
线程加入
- public final void join()——等待该线程终止
线程礼让:让多个线程的执行更和谐,但是不能保证一个线程一次
- public static void yield()——暂停当前正在执行的线程对象,并执行其他线程
后台线程
- public final void setDaemon(boolean on)——将该线程标记为守护线程,正在运行的线程是守护线程时,java虚拟机退出该方法必须在线程启动前使用
中断线程
- public final void stop()——具有不安全性,已过时
- public void interrupt()——将线程状态终止
3. 多线程的实现方案2
方式2:实现Runnable接口
步骤:
- 自定义类MyRunnable实现Runnable接口
- 重写run()方法
- 创建MyRunnable类的对象
- 创建Thread类的对象,并把3步骤的对象作为构造参数传递
获取及设置线程名称
- Thread.currentThread().getName()——只能间接获取,因为不是继承自Thread
- Thread对象调用setName(String name)——设置线程名称
- Thread(Runnable target, String name)——带参构造,造对象的时候直接设置名称
方式2的好处:
- 可以避免由于Java单继承带来的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想
4.线程安全问题
产生的原因:
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
解决多线程安全问题
基本思想:让程序没有安全问题的环境
对于问题3的解决实现:把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
实现方式1:同步代码块
格式
synchronized(对象){需要同步的代码;}
关于对象:
- 该对象是解决线程安全问题的根本原因,类似于锁的功能
- 多个线程只能有一把锁,即一个对象,故不能直接在()中造对象,而应该在之前就造好
- 这个对象可以是任意对象
关于需要同步的代码:
其实就是问题原因3里的代码
同步的前提
- 多个线程
- 多个线程使用的是同一个锁对象
同步的好处:同步的出现解决了多线程的安全问题
同步的弊端:当线程很多时,因为每个线程都会去判断同步上的锁,耗费资源,会降低程序的运行效率
实现方式2:同步方法
格式:就是将同步关键字加到方法上
- 同步代码块的锁对象是任意对象
- 同步方法的锁对象是this
- 静态同步方法的锁对象是类的字节码文件对象
实现方式3:Lock实现
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
- void lock() ——获取锁
- void unlock()——释放锁
实现类:ReentrantLock
同步中的死锁问题
同步弊端
- 效率低
- 如果出现了同步嵌套,就容易产生死锁问题
死锁问题是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
线程间通信问题:针对同一个资源的操作有不同种类的线程(生产者和消费者多线程体现)
注意:
- 不同种类的线程都要加锁
- 不同种类的线程加锁必须是同一把
Java提供的等待唤醒机制
Object类中提供了三个方法,因为这些方法的调用必须通过锁对象调用,而锁对象可以是任意对象,所有必须定义在Object类中
- wait()——等待并立即释放锁
- notify()——唤醒单个线程
- notifyAll()——唤醒所有线程
5. 线程组
线程组:将多个线程组合到一起
可以对一批线程组进行分类管理,Java允许程序直接对线程组进行控制,默认情况下,所有的线程都属于主线程组
Thread类中的方法:
- public final ThreadGroup getThreadGroup() ——返回该线程所属的线程组
ThreadGroup中的方法:
- public final String getName()——返回此线程组的名称
给线程设置分组
- Thread(ThreadGroup group, Runnable target, String name)
6. 线程池
程序启动一个新线程成本比较高,因为它涉及到与操作系统进行交互。而使用线程池可以很好的提高性能。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
- public static ExecutorService newCachedThreadPool()
- public static ExecutorService newFixedThreadPool(int nThreads)
- public static ExecutorService newSingleThreadExecutor()
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
它提供了如下方法
- Future<?> submit(Runnable task)
- <T> Future<T> submit(Callable<T> task)
7. 匿名内部类的方式实现多线程
匿名内部类的格式:其本质是该类或接口的子类对象
new 类名或接口名(){
重写方法;
}