前言
什么是线程?
线程(Thread)是一个程序内部的一条执行流程。程序中如果只有一条执行流程,那这个程序就是单线程的程序。
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
多线程是什么?
多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)
如何在程序中创建出多条线程?
Java是通过java.lang.Thread 类的对象来代表线程的。
多线程的创建方式一:继承Thread类
① 定义一个子类 MyThread 继承线程类 java.lang.Thread ,重写 run() 方法② 创建 MyThread 类的对象③ 调用线程对象的 start() 方法启动线程(启动后还是执行 run 方法的)优点:编码简单缺点:线程类已经继承Thread, 无法继承其他类 ,不利于功能的扩展 。
public class ThreadDemo1 {
// main方法启动时,默认是由一条主线程负责执行。
public static void main(String[] args) {
// 3、创建线程类的对象代表一个线程。
Thread t = new MyThread();
// 4、启动这个线程对象,才真正开启了一个线程。(线程会自动执行run方法来跑)
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("main线程输出 ===> " + i);
}
}
}
// 1、定义一个线程类继承Thread类。
class MyThread extends Thread{
// 2、重写Thread类提供的run方法(线程执行任务的方法)
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出 ===> " + i);
}
}
}
多线程的创建方式二:实现Runnable接口
① 定义一个线程任务类 MyRunnable 实现 Runnable 接口,重写 run() 方法② 创建 MyRunnable 任务对象③ 把 MyRunnable 任务对象交给 Thread 处理。④ 调用线程对象的 start() 方法启动线程优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。缺点:需要多一个Runnable对象。
public class ThreadDemo2 {
public static void main(String[] args) {
// 3、用任务类创建任务对象
Runnable target = new MyRunnable();
// 4、把线程任务对象交给一个线程对象。
// a、我们就得到一个线程了
// b、线程启动后,可以去执行任务对象的run方法执行。
Thread t = new Thread(target);
// 5、启动线程
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("main线程输出:" + i);
}
}
}
// 1、定义一个线程任务类实现Runnable接口
class MyRunnable implements Runnable{
// 2、重写run方法。
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出:" + i);
}
}
}
多线程的创建方式三:利用Callable接口、FutureTask类来实现
① 、创建任务对象定义一个类实现 Callable 接口,重写 call 方法,封装要做的事情,和要返回的数据。把 Callable 类型的对象封装成 FutureTask (线程任务对象)。② 、把线程任务对象交给 Thread 对象。③ 、调用 Thread 对象的 start 方法启动线程。④ 、线程执行完毕后、通过 FutureTask 对象的的 get 方法去获取线程任务执行的结果。优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强; 可以在线程执行完毕后去获取线程执行的结果。缺点:编码复杂一点。
public class ThreadDemo4 {
public static void main(String[] args) {
// 3、创建Callable对象
Callable<String> call = new MyCallable(100);
// 4、创建一个真正的任务对象叫:未来任务对象。
// 未来任务对象的作用:
// a、未来任务对象本质是一个Runnable类型的对象,可以交给线程了!
// b、可以在线程执行完毕后去获取线程执行完毕后返回的结果
FutureTask<String> task = new FutureTask<>(call);
// 5、把未来任务对象交给线程对象。
// public Thread(Runnable task)
Thread t = new Thread(task);
// 6、启动线程
t.start();
Callable<String> call2 = new MyCallable(200);
FutureTask<String> task2 = new FutureTask<>(call2);
Thread t2 = new Thread(task2);
t2.start();
// 怎么获取线程执行完毕后的结果呢?建议大家在全部线程启动后再拿结果
try {
// 注意:当主线程执行到这里取第一个线程的结果时,如果第一个线程没有执行完毕
// 会暂定等待第一个线程执行完毕后,再往下走!
String rs = task.get();
System.out.println(rs);
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
String rs = task2.get();
System.out.println(rs);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
// 1、定义一个类实现Callable接口。提前声明线程任务执行完毕后返回的结果类型。
class MyCallable implements Callable<String>{
private int n;
public MyCallable(int n) {
this.n = n;
}
// 2、重写call方法
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return "子线程求出了1-" + n + "的和是:" + sum;
}
}
一、多线程常用方法
Thread提供的常用方法 | 说明 |
---|---|
public void run() | 线程的任务方法 |
public void start() | 启动线程 |
public String getName() | 获取当前线程的名称,线程名称默认是Thread-索引 |
public void setName(String name) | 为线程设置名称 |
public static Tread currentThread() | 获取当前执行的线程对象 |
public static void sleep(long time) | 让当前执行的线程休眠多少毫秒后,再继续执行 |
public final void join() | 让调用这个方法的线程先执行完 |
Thread 提供的常见构造器 | 说明 |
---|---|
pubilc Thread(String name) | 可以为当前线程指定名称 |
public Thread(Runnable target) | 封装Runnable对象成为线程对象 |
public Thread (Runnable target,String name) | 封装Runnable对象成为线程对象,并指定线程名称 |
下面我们演示一下getName()
、setName(String name)
、currentThread()
、sleep(long time)
这些方法的使用效果。
public class MyThread extends Thread{
public MyThread(String name){
super(name); //1.执行父类Thread(String name)构造器,为当前线程设置名字了
}
@Override
public void run() {
//2.currentThread() 哪个线程执行它,它就会得到哪个线程对象。
Thread t = Thread.currentThread();
for (int i = 1; i <= 3; i++) {
//3.getName() 获取线程名称
System.out.println(t.getName() + "输出:" + i);
}
}
}
再测试类中,创建线程对象,并启动线程
public class ThreadAPIDemo1 {
public static void main(String[] args) {
// 目标:理解Thread类的常用方法
Thread t1 = new MyThread("1号->");
// t1.setName("1号");
t1.start();
System.out.println("t1 - name :" + t1.getName());
Thread t2 = new MyThread("2号->");
// t2.setName("2号");
t2.start();
System.out.println("t2 - name :" + t2.getName());
// 主线程的名称???
// 哪个线程执行这个代码,这个方法就返回哪个线程对象给你
Thread m = Thread.currentThread();
m.setName("最牛的线程");
System.out.println("m - name :" + m.getName());
}
}
执行上面代码,效果如下图所示,我们发现每一条线程都有自己了名字了。
t1 - name :1号->
t2 - name :2号->
m - name :最牛的线程
1号->线程输出 ===> 0
1号->线程输出 ===> 1
1号->线程输出 ===> 2
1号->线程输出 ===> 3
1号->线程输出 ===> 4
2号->线程输出 ===> 0
2号->线程输出 ===> 1
2号->线程输出 ===> 2
2号->线程输出 ===> 3
2号->线程输出 ===> 4
二、线程安全问题
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
线程安全问题出现的原因?1、存在多个线程在同时执行2、同时访问一个共享资源3、存在修改该共享资源
三、线程同步方案
为了解决前面的线程安全问题,我们可以使用线程同步思想。同步最常见的方案就是加锁,意思是每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动释放锁,然后其他线程才能再加锁进来。
Java提供了三种方案:
1、同步代码块
作用
:把
访问共享资源的
核心代码给上锁
,以此保证线程安全。
原理
:
每次
只允许
一个线程
加锁后
进入
,
执行完毕后自动解锁,其他线程才可以进来执行
注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
public static void main(String[] args) {
synchronized("同步锁") {
//访问共享资源的核心代码
}
}
2、同步方法
作用
:把
访问共享资源的
核心
方法
给上锁
,以此保证线程安全。
原理
:
每次只能一个线程进入
,
执行完毕以后自动解锁,其他线程才可以进来执行
修饰符 synchronized 返回值类型 方法名称(形参列表) {
//操作共享资源的代码
}
3、Lock锁
Lock
锁是
JDK5
开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock
是接口,不能直接实例化,可以采用它的实现类
ReentrantLock
来构建
Lock
锁对象。
![](https://i-blog.csdnimg.cn/blog_migrate/6074f4b126c1c0686a8ee34ddb65b60b.png)
Lock的常用方法:
四、线程池
1、什么是线程池?
线程池就是一个可以 复用线程的技术 。
2、不使用线程池的问题
用户每发起一个请求,后台就需要创建 一个 新 线程 来处理,下次新任务来了肯定又要创建新线程处理的, 而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来, 这样会严重影响系统的性能。
3、如何得到线程池对象?
方式一:使用 ExecutorService 的实现类 ThreadPoolExecutor 自创建一个线程池对象。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,RejectedExecutionHandler handler){}
//参数一:corePoolSize : 指定线程池的核心线程的数量。
//参数二:maximumPoolSize:指定线程池的最大线程数量。
//参数三:keepAliveTime :指定临时线程的存活时间。
//参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)
//参数五:workQueue:指定线程池的任务队列。
//参数六:threadFactory:指定线程池的线程工厂。
//参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
Executors
是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
![](https://i-blog.csdnimg.cn/blog_migrate/325a7d5a6bf9bc2b6efe122644d239b5.png)