目录
- 线程概念
- 线程的生命周期
- 线程的实现方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 线程的调度与优先级
- 优先级
- 调度
- 线程的数据安全
- synchronized关键字
- 守护线程与定时器
- 线程的常用方法
- 线程池创建方式
线程概念
进程是一个应用程序,线程是一个进程中的执行场景/执行单元,一个进程可以启动多个线程。java程序执行过程中,至少有两个线程并发,一个是执行程序的主线程,一个是垃圾回收线程。进程之间内存独立不共享,线程之间堆内存和方法区内存共享,但栈内存独立,一个线程一个栈,Java中多线程并发可以提高效率。注:单核CPU不能真正实现多线程并发,只是cpu执行速度很快,多个线程之间频繁切换执行,感觉像多线程并发。
线程的生命周期
线程的生命周期:一个线程从创建、启动、执行到停止的完整过程。线程的生命周期中也可产生各种线程状态,如下图:
(图片来源网络)
线程状态说明:
- 初始状态:线程创建完成后进入此状态。此时线程未获得CPU使用权。
- 可执行状态:线程调用start()方法启动线程进入此状态。此时线程需争夺CPU使用权,获得CPU使用权的线程执行run()方法,未获得的需排队。
- 不可执行状态:线程调用了wait()方法、在当前线程中调用另一个线程的join()方法、调用sleep()方法都可进入此状态。此时线程获得的CPU使用权会失去。
- 终止状态:线程执行完成调用stop()方法进入此状态。此时CPU使用权会交出。
线程的实现方式
注意点:start()方法是native方法,JVM会另起一个线程执行run()方法,而直接执行run()方法是本地线程执行。
继承Thread类
步骤:定义一个类继承Thread类,在类中重写run()方法,创建类对象,调用start()方法启动线程。
public static class Test extends Thread{
@Override
public void run() {
System.out.println("继承Thread类");
}
}
public static void main(String[] args) {
new Test().start();
}
实现Runnable接口
步骤:定义一个类实现Runnable接口,在类中重写run()方法,创建类对象,创建Thread类的对象并将类对象作为Thread类构造参数,Thread类对象调用start()方法启动线程。
public static class Test implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable接口");
}
}
public static void main(String[] args) {
new Thread(new Test()).start();
}
实现Callable接口
步骤:定义一个类实现Callable<类型>接口,在类中重写call()方法(此方法含返回值与接口泛型类型一致),创建类对象,创建Future的实现类FutureTask对象并将类对象作为其构造参数(Thread类构造只能接受Runnable实现类对象,不能接受Callable实现类对象), 创建Thread类的对象并将FutureTask类对象作为Thread类构造参数,Thread类对象调用start()方法启动线程,调用get方法获取线程结束之后的结果。
public static class Test implements Callable<String>{
@Override
public String call() throws Exception {
return "实现Callable接口";
}
}
public static void main(String[] args) throws Exception {
Test t = new Test();
FutureTask<String> ft = new FutureTask<>(t);
Thread t1 = new Thread(ft);
t1.start();
// get()一定要在start()之后
String s = ft.get();
System.out.println(s);
}
线程的调度与优先级
优先级
在多线程中,每个线程都被赋予一个优先级,优先级决定CPU执行顺序。其最低优先级为1,默认优先级为5,最高优先级为10。大概率下优先级较高的获取CPU时间片会更长。其获取与设置优先级方法如下:
方法 | 说明 |
void setPriority(int newPriority) | 设置线程新的优先级为newPriority |
int getPriority() | 获取线程的优先级 |
调度
线程调度:在多线程中,如果当前线程在执行中,拥有更高优先级线程进入可执行状态,此时这个更高优先级线程会被立即调度执行。调度分为两种方式,如下:
- 分时方式:CPU资源按时间分配,执行一个线程只能在其分配的时间内执行,一旦超出时间,则将等待下一个时间片的调度执行。
- 独占方式:当线程获取执行权后将一直执行,直到线程执行完毕或由于某种原因(调用yield()、sleep()、wait()方法,用户操作,程序异常等)放弃CPU资源。
线程的数据安全
出现数据安全问题条件:多线程并发,有共享数据,共享数据有修改行为。
易出现线程问题的变量:实例变量(堆)和静态变量(方法区),因为堆和方法区都是多线程共享。
synchronized关键字
synchronized关键字:线程同步锁。会降低程序执行效率。其写法如下:
第一种:在同步代码块中使用
synchronized(线程共享对象){
同步代码块;
}第二种:在静态方法上使用
表示找类锁,类锁永远只有1把。第三种:在实例方法上使用
表示共享对象是this,并且同步代码块为整个方法体。总结:
synchronized最好不要嵌套使用,容易出现死锁。
对象锁:1个对象1把锁,n个对象n把锁。
类锁:n个对象可能就1把锁。
解决数据安全问题:线程同步,线程需要排队执行(不能并发),会牺牲部分效率,但数据是安全的。开发中解决方案如下:
- 尽量使用局部变量代替实例变量和静态变量,因为局部变量属于栈,栈不共享,所以局部变量不会存在线程安全问题。
- 如果不能使用局部变量,可考虑创建多个对象,因为1个对象1把锁,对象不共享。
- 当前两种都不能使用时,则使用synchronized关键字。
守护线程与定时器
守护线程:线程默认为非守护线程,也称用户线程。守护线程可随时结束并且不影响运行结果。当用户线程结束时,守护线程也会立即结束。
最具代表性守护线程:垃圾回收线程。
设置守护线程的方法:线程对象.setDaemon(true);
定时器:间隔特定的时间,执行特定的程序。
定时器的实现方式:
- sleep()方法:设置特定的睡眠时间,到时间后执行任务。
- java.util.Timer类库:可以直接使用其相关方法执行任务,使用较少。
- SpringTask框架:只需简单配置即可完成定时任务。
线程的常用方法
线程的常用方法 | 说明 |
wait() | 线程等待,让线程进入Waiting(无限等待)状态,线程会释放对象锁,一般用在同步方法或同步代码块中。在Object类中 |
sleep() | 线程休眠,让线程进入Time_Waiting(带时间限制的Waiting)状态,需传入一个参数标识线程需要休眠的时间 |
yield() | 线程让步,使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争CPU 时间片 |
interrupt() | 线程中断,如果线程处于Running(执行)状态,只会改变中断标识位,不会真的中断正在运行的线程。如果线程当前处于Timed_Waiting(带时间限制的Waiting)状态,则会让线程抛出InterruptedException异常 |
join() | 在当前线程中调用另一个线程的join()方法,则当前线程转为阻塞状态 |
notify() | 唤醒在对象上等待的单个线程,如果有多个线程在等待,选择其中一个进行唤醒。线程唤醒后不会立即执行,等当前线程执行结束释放锁后,被唤醒线程获得对象锁之后才开始执行。在Object类中 |
notifyAll() | 唤醒在对象上等待的所有线程。在Object类中 |
线程池创建方式
在Java中,并发编程通常都使用创建线程池实现,线程池创建主要分为两大类:通过ThreadPoolExecutor手动创建线程池和通过Executors 执行器自动创建线程池。其具体实现方法如下:
方法 | 说明 |
Executors.newFixedThreadPool | 创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待 |
Executors.newCachedThreadPool | 创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程 |
Executors.newSingleThreadExecutor | 创建单个线程数的线程池,它可以保证先进先出的执行顺序 |
Executors.newScheduledThreadPool | 创建一个可以执行延迟任务的线程池 |
Executors.newSingleThreadScheduledExecutor | 创建一个单线程的可以执行延迟任务的线程池 |
Executors.newWorkStealingPool | 创建一个抢占式执行的线程池(任务执行顺序不确定) |
ThreadPoolExecutor | 手动创建线程池的方式,它创建时最多可以设置 7 个参数 |