多线程
介绍
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

- 什么是多线程?
有了多线程,我们就可以让程序同时做多件事情
- 多线程的作用?
提高效率
- 多线程的应用场景?
只要你想让多个事情同时运行就需要用到多线程比如:软件中的耗时操作、所有的聊天软件、所有的服务器
- 并发:在同一时刻,有多个指令在单个 CPU 上交替执行
- 并行:在同一时刻,有多个指令在多个 CPU 上同时执行
多线程实现方式(多个线程交替进行)
1.继承 Thread 类实现多线程
MyThread 类
public class MyThread extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "HelloWorld");
}
}
}
ThreadDemo 类
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第一种启动方式:
* 1. 自己定义一个类继承Thread
* 2. 重写run方法
* 3. 创建子类的对象,并启动线程
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
2.实现 Runnable 接口实现多线程
MyRun 类
public class MyRun implements Runnable{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
//获取到当前线程的对象
//Thread t = Thread.currentThread();
//System.out.println(t.getName() + "HelloWorld!");
System.out.println(Thread.currentThread().getName() + "HelloWorld!");
}
}
}
ThreadDemo 类
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第二种启动方式:
* 1.自己定义一个类实现Runnable接口
* 2.重写里面的run方法
* 3.创建自己的类的对象
* 4.创建一个Thread类的对象,并开启线程
*/
//创建MyRun的对象
//表示多线程要执行的任务
MyRun mr = new MyRun();
//创建线程对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
//给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
3.利用Callable接口和Future接口方式实现
MyCallable 类
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//求1-100之间的和
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
}
ThreadDemo 类
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 多线程的第三种实现方式:
* 特点:可以获取到多线程运行的结果
* 1. 创建一个类MyCallable实现Callable接口
* 2. 重写call(是有返回值的,表示多线程运行的结果)
* 3. 创建MyCallable的对象(表示多线程要执行的任务)
* 4. 创建FutureTask的对象(作用管理多线程运行的结果)
* 5. 创建Thread类的对象,并启动(表示线程)
*/
//创建MyCallable的对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
//创建FutureTask的对象(作用管理多线程运行的结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建线程的对象
Thread t1 = new Thread(ft);
//启动线程
t1.start();
//获取多线程运行的结果
Integer result = ft.get();
System.out.println(result);
}
}
多线程三种实现方式对比
| 实现方式 | 优点 | 缺点 |
|---|---|---|
| 继承 Thread 类 | 编程比较简单,可以直接使用 Thread 类中的方法 | 扩展性较差,不能再继承其他的类 |
| 实现 Runnable 接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用 Thread 类中的方法 |
| 实现 Callable 接口 | (补充:可获取线程执行结果) | (补充:编程复杂度较高) |
线程常见的成员方法
| 方法名称 | 说明 |
|---|---|
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
public static void yield() | 出让线程 / 礼让线程 |
public static void join() | 插入线程 / 插队线程 |
守护线程
主线程运行完毕后守护线程也会停止,只有主线程运行时守护线程才有意义,例如在聊天界面传输文件,聊天框为主线程,传送文件为守护线程,聊天关闭文件也不会继续传输

注:
1、如果我们没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始的)
2、如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置
3.当JVM虚拟机启动之后,会自动的启动多条线程
其中有一条线程就叫做main线程
他的作用就是去调用main方法,并执行里面的代码
在以前,我们写的所有的代码,其实都是运行在main线程当中
礼让线程
让出当前cpu执行权,然后重新竞争(Thread.yield())
插入线程
插入到当前线程之前,例如在主线程使用时,它会比主线程先执行
抢占式调度
| 方法名称 | 说明 |
|---|---|
| setPriority(int newPriority) | 设置线程的优先级 |
| final int getPriority() | 获取线程的优先级 |
注:
优先级设置等级为1-10,高优先级只是先执行的概率大,并不会百分百比低优先级的先执行
线程的生命周期

同步代码块
作用:把操作共享数据的代码锁起来,保证线程安全。
synchronized (锁){
操作共享数据的代码
}
特点:
- 锁默认打开,有一个线程进入代码块后,锁自动关闭;
- 代码全部执行完毕,线程退出后,锁自动打开。
public class MyThread extends Thread{
// 所有对象共享的票数据(static保证唯一)
static int ticket = 0; // 0 ~ 99
// 锁对象(static保证所有线程共用同一把锁)也可用当前对象的字节码
static Object obj = new Object();
@Override
public void run() {
while (true){
// 同步代码块:用obj作为锁
synchronized (obj){
if(ticket < 100){
try {
Thread.sleep(10); // 模拟售票延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;//如果不用同步代码块,此处可能被1和2线程同时运行
System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
}else{
break; // 票卖完则退出循环
}
}
}
}
}
同步方法
定义:将synchronized关键字直接加在方法上,实现线程安全的方法。
修饰符 synchronized 返回值类型 方法名(方法参数) {
// 方法体(会被锁住的代码)
}
特点:
会锁住方法内的所有代码;
锁对象不能自定义,由方法类型决定:
- 非静态同步方法:锁对象是
this(当前对象); - 静态同步方法:锁对象是当前类的字节码文件对象(如
类名.class)。
ThreadDemo类
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个售票窗口
利用同步方法完成售票
技巧:同步代码块
*/
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
MyRunnable类
public class MyRunnable implements Runnable {
private int ticket = 0; // 共享票数据
@Override
public void run() {
// 1.循环售票
while (true) {
// 2.调用同步方法(完成售票逻辑)
if (method()) break; // 票卖完则退出循环
}
}
// 非静态同步方法:锁对象是this(当前MyRunnable对象)
private synchronized boolean method() {
// 3.判断票是否卖完
if (ticket == 100) {
return true; // 票已卖完,返回true让run()退出循环
} else {
// 4.模拟售票延迟
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 5.售票逻辑
ticket++;
System.out.println(Thread.currentThread().getName() + "卖出第" + ticket + "张票");
return false; // 票未卖完,继续循环
}
}
}
注:提取方法用快捷键ctrl+alt+m
Lock锁
引入背景:同步代码块 / 方法的锁操作是隐式的,JDK5 后提供Lock锁,可显式控制加锁 / 释放锁,锁定操作更灵活。
核心特点:
- 比
synchronized支持更广泛的锁定操作; - 提供显式方法:
void lock():主动获取锁;void unlock():主动释放锁。
使用说明:
Lock是接口,需用实现类ReentrantLock实例化;ReentrantLock的构造方法:ReentrantLock()→ 创建实例。
ThreadDemo
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个售票窗口
用JDK5的lock实现
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
MyThread
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread {
// 共享票数据(static保证所有线程共享)
static int ticket = 0;
// Lock锁实例(static保证所有线程共用同一把锁)
static Lock lock = new ReentrantLock();
@Override
public void run() {
// 1.循环售票
while (true) {
// 2.显式加锁
lock.lock();
try {
// 3.判断票是否卖完
if (ticket == 100) {
break;
} else {
// 4.模拟售票延迟
Thread.sleep(10);
// 5.售票逻辑
ticket++;
System.out.println(getName() + "在卖第" + ticket + "张票");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 6.显式释放锁(放在finally确保锁一定被释放)
lock.unlock();
}
}
}
}
1278

被折叠的 条评论
为什么被折叠?



