目录
线程简介(了解)
程序、进程、线程
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指段静态的代码,静态对象。
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态过程:有它自身的产生、存在和消亡的过程。——生命周期
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
并行、并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
使用多线程的优点
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
实现线程的两种方式(掌握)
继承Thread类
- 创建一个继承Thread类的子类
- 重写Thread类的run()
- 实例化子类对象
- 调用Thread类的start()
public class ThreadTest extends Thread{
public int count =10;
@Override
public void run() {
while (true){
System.out.print(count+" ");
if(--count==0){
break;
}
}
}
public static void main(String[] args) {
new ThreadTest().start();
}
}
实现Runnable接口
- 创建一个实现Runnable接口的类
- 实现Runnable的抽象方法:run()
- 创建实现类对象
- 将实现类对象作为参数传递Thread类构造器中,创建Thread对象
- 调用Thread类的start()
线程的生命周期(掌握)
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
- 新建状态
使用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序start()这个线程。 - 就绪状态
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。 - 运行状态
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。 - 阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
。等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
。同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
。其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。 - 死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程的优先级(了解)
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
Thread类的常用方法(会用)
常用方法(重要)
序号 | 方法描述 |
---|---|
1 | public void start() 使该线程开始执行,Java虚拟机调用run()方法。 |
2 | public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
3 | public final void setName(String name) 设置线程名称,与创建线程调用Thread类构造器传name参数功能相同。 |
4 | public String getName() 获取线程名称 。 |
5 | public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。 |
6 | public static void sleep(long time) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。单位:毫秒数。 |
7 | public static Thread currentThread() 返回对当前正在执行的线程对象的引用。 |
8 | public final void join(long time) 等待该线程终止的时间最长为 time 毫秒。 |
不常用方法(了解)
序号 | 方法描述 |
---|---|
1 | public final void setPriority(int priority) 更改线程的优先级。 |
2 | public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
3 | public void interrupt() 中断线程。 |
4 | public final boolean isAlive() 测试线程是否处于活动状态 。 |
5 | public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。 |
线程安全问题(重点)
出现线程安全的问题需要一个前提 : 多个线程同时操作同一个资源
线程执行调用方法run,同一个资源是堆内存的
同步代码块
同步代码块可以解决线程安全问题 : 格式 synchronized关键字
synchronized(任意对象){
//线程操作的共享资源
}
任意对象 : 在同步中这个对象称为对象锁,简称锁,官方的稳定称为 对象监视器
同步方法
当一个方法中,所有代码都是线程操作的共享内容,可以在方法的定义上添加同步的关键字 synchronized , 同步的方法,或者称为同步的函数.
- 同步方法中有对象锁吗 , this对象
- 静态同步方法中有对象锁吗,锁对象是本类.class属性. 这个属性表示这个类的class文件的对象.
@Override
public void run() {
while (true)
sale();
}
private static synchronized void sale(){
// synchronized (Ticket.class) {
if (tickets > 0) {
try {
Thread.sleep(20);//线程休眠,暂停执行
} catch (Exception ex) {
}
System.out.println(Thread.currentThread().getName() + " 出售第" + tickets + "张");
tickets--;
}
// }
}
JDK5新特性Lock锁(重点)
JDK5新的特性 : java.util.concurrent.locks包. 定义了接口Lock.
Lock接口替代了synchronized,可以更加灵活
- Lock接口的方法
- void lock() 获取锁
- void unlock()释放锁
- Lock接口的实现类ReentrantLock
/**
* 优化为juc包的接口Lock
*/
public class Ticket implements Runnable {
//定义票源
private int tickets = 100;
//获取Lock接口的实现类对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true)
sale();
}
private void sale(){
//获取锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(20);//线程休眠,暂停执行
} catch (Exception ex) {
}
System.out.println(Thread.currentThread().getName() + " 出售第" + tickets + "张");
tickets--;
}
//释放锁
lock.unlock();
}
}
线程池ThreadPool(掌握)
线程的缓冲池,目的就是提高效率. new Thread().start() ,线程是内存中的一个独立的方法栈区,JVM没有能力开辟内存空间,和OS交互.
JDK5开始内置线程池
Executors类
-
静态方法static newFixedThreadPool(int 线程的个数)
- 方法的返回值ExecutorService接口的实现类,管理池子里面的线程
-
ExecutorService接口的方法
- submit (Runnable r)提交线程执行的任务
Callable接口
实现多线程的程序 : 接口特点是有返回值,可以抛出异常 (Runnable没有)
抽象的方法只有一个, call
启动线程,线程调用重写方法call
- ExecutorService接口的方法
- submit (Callable c)提交线程执行的任务
- Future submit()方法提交线程任务后,方法有个返回值 Future接口类型
- Future接口,获取到线程执行后的返回值结果
public class MyCall implements Callable<String> {
public String call() throws Exception{
return "返回字符串";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池,线程的个数是2个
ExecutorService es = Executors.newFixedThreadPool(2);
//线程池管理对象service,调用方法啊submit提交线程的任务
MyRunnable my = new MyRunnable();
//提交线程任务,使用Callable接口实现类
Future<String> future = es.submit(new MyCall());//返回接口类型 Future
//接口的方法get,获取线程的返回值
String str = future.get();
System.out.println("str = " + str);
}