多线程是指从软硬件上实现的多条执行流程的技术(多条线程由 CPU 负责调度执行)
public class Test {
public static void main(String[] args) {
// 3. 创建 MyThread 线程类的对象代表一个线程
Thread t = new MyThread();
// 4. 启动线程
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程 main 输出: " + i);
}
}
}
// 1. 让子类继承 MyThread 线程类
class MyThread extends Thread {
// 2. 必须重写 Thread 类的 run 方法
@Override
public void run() {
// 线程要执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程 MyThread 输出: " + i);
}
}
}
// 普通写法
public class Test {
public static void main(String[] args) {
// 3. 创建任务对象
Runnable target = new MyRunnable();
// 4. 把任务对象交给一个线程对象处理
new Thread(target).start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程 main 输出: " + i);
}
}
}
// 1. 定义一个任务类,实现 Runnable 接口
class MyRunnable implements Runnable {
// 2.重写 Runnable 类的 run 方法
@Override
public void run() {
// 线程要执行的任务
for (int i = 0; i < 5; i++) {
System.out.println("子线程 MyRunnable 输出: " + i);
}
}
}
// 匿名内部类写法(即上面代码的简洁版)
public class Test {
public static void main(String[] args) {
// 1. 直接创建 Runnable 接口的匿名内部类形式(任务对象)
// Runnable target = new Runnable() {
// @Override
// public void run() {
// // 2. 在这里写好线程要执行的任务
// for (int i = 0; i < 5; i++) {
// System.out.println("子线程输出: " + i);
// }
// }
// };
// new Thread(target).start();
// 简化形式1:
// new Thread(new Runnable() {
// @Override
// public void run() {
// for (int i = 0; i < 5; i++) {
// System.out.println("子线程输出: " + i);
// }
// }
// }).start();
// 简化形式2:
new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出: " + i);
}
}).start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程输出: " + i);
}
}
}
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws Exception {
// 3. 创建 Callable 对象
Callable<String> call = new MyCallable(100);
// 4. 把对象封装成一个 FutureTack 对象(任务对象)
// 未来任务对象的作用?
// a.是一个任务对象,实现了 Runnable 对象
// b.可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后返回的结果
FutureTask<String> task = new FutureTask<>(call);
// 5. 把任务对象交给一个 Thread 对象
new Thread(task).start();
// 6. 获取线程执行完毕后返回的结果
// 注意:如果执行到这里,假如上面的线程还没执行完毕
// 这里的代码会暂停,等待上面的线程执行完毕才会去获取结果
String res = task.get();
System.out.println(res);
}
}
// 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 {
// 在这里写线程的任务,返回线程执行完毕的结果
// 需求:求 1 ~ n 的和
int sum = 0;
for (int i = 0; i <= n; i++) {
sum += i;
}
return "1到" + n + "的和是:" + sum;
}
}
public class Test {
public static void main(String[] args) {
Thread t1 = new MyThread();
t1.start();
System.out.println(t1.getName()); // 获取t1线程的名字 Thread-0
Thread t2 = new MyThread();
t2.start();
System.out.println(t2.getName()); // 获取t2线程的名字 Thread-1
System.out.println(Thread.currentThread()); // 获取当前线程的名字(当前代码所在的线程是主线程) Thread[main,5,main]
for (int i = 0; i < 3; i++) {
System.out.println("主线程输出>>> " + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("子线程输出>>> " + i);
}
}
}
public class Test {
public static void main(String[] args) throws Exception {
Thread t1 = new MyThread();
t1.setName("1号线程"); // 为线程取名字
t1.start();
t1.join(); // 让主线程等待t1线程执行完才继续往下执行
System.out.println("------");
Thread t2 = new MyThread("2号线程"); // 为线程设置名字的第二种方法(记得重写构造器)
t2.start();
// t2.join();
Thread t = Thread.currentThread();
t.setName("主线程");
for (int i = 0; i < 3; i++) {
System.out.println(t.getName() + "输出>>> " + i);
}
}
}
class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name); // 为线程设置名字的第二种方法
}
@Override
public void run() {
try {
// Thread.sleep(5000); // 让当前的线程,先暂停5000毫秒(5秒)
Thread t = Thread.currentThread();
for (int i = 0; i < 3; i++) {
System.out.println(t.getName() + "输出>>> " + i);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题
public class Test {
public static void main(String[] args) {
// 1. 创建一个账户对象,代表两个人的共享账户
Account account = new Account("ICBC-110", 1000);
// 2. 创建两个线程,分别代表小明、小红,再去同一个账户对象中取钱1千
new MyThread("小明", account).start(); // 小明
new MyThread("小红", account).start(); // 小红
}
}
class MyThread extends Thread {
private Account account;
public MyThread(String name, Account account) {
super(name);
this.account = account;
}
@Override
public void run() {
// 取钱
account.drawMoney(1000);
}
}
class Account {
private String cardId;
private double money;
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
// 取钱功能
public void drawMoney(double money) {
String name = Thread.currentThread().getName(); // 获取线程的名字,判断是谁来取钱了
// 判断余额是否足够
if (this.money >= money) {
System.out.println(name + "取钱成功,金额:" + money + "元");
this.money -= money;
System.out.println(name+"取钱后,余额为 " + this.money + "元");
} else {
System.out.println(name + "取钱失败,余额不足");
}
}
}
// 上述代码的运行结果:
// 小红取钱成功,金额:1000.0元
// 小明取钱成功,金额:1000.0元
// 小明取钱后,余额为 -1000.0元
// 小红取钱后,余额为 0.0元
线程同步:解决线程安全问题的方案
线程同步的思想:让多个线程实现先后依次访问共享资源,从而解决了安全问题
线程同步的常见方案:
加锁的场景方式:
锁对象的使用规范
synchronized(" 请在这里填写‘锁对象’ "){
被上锁(被同步的)代码块......
}
public class Test {
public static void main(String[] args) {
// 1. 创建一个账户对象,代表两个人的共享账户
Account account = new Account("ICBC-110", 1000);
// 2. 创建两个线程,分别代表小明、小红,再去同一个账户对象中取钱1千
new MyThread("小明", account).start(); // 小明
new MyThread("小红", account).start(); // 小红
}
}
class MyThread extends Thread {
private Account account;
public MyThread(String name, Account account) {
super(name);
this.account = account;
}
@Override
public void run() {
// 取钱
account.drawMoney(1000);
}
}
class Account {
private String cardId;
private double money;
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
// 取钱功能
public void drawMoney(double money) {
String name = Thread.currentThread().getName(); // 获取线程的名字,判断是谁来取钱了
// 判断余额是否足够
synchronized (this) {
if (this.money >= money) {
System.out.println(name + "取钱成功,金额:" + money + "元");
this.money -= money;
System.out.println(name+"取钱后,余额为 " + this.money + "元");
} else {
System.out.println(name + "取钱失败,余额不足");
}
}
}
}
同步方法的底层原理:
"同步代码块"好还是"同步方法"好?
public class Test {
public static void main(String[] args) {
// 1. 创建一个账户对象,代表两个人的共享账户
Account account = new Account("ICBC-110", 1000);
// 2. 创建两个线程,分别代表小明、小红,再去同一个账户对象中取钱1千
new MyThread("小明", account).start(); // 小明
new MyThread("小红", account).start(); // 小红
}
}
class MyThread extends Thread {
private Account account;
public MyThread(String name, Account account) {
super(name);
this.account = account;
}
@Override
public void run() {
// 取钱
account.drawMoney(1000);
}
}
class Account {
private String cardId;
private double money;
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
// 取钱功能
// 加上 synchronized 关键字,此方法就变成同步方法了
public synchronized void drawMoney(double money) {
String name = Thread.currentThread().getName(); // 获取线程的名字,判断是谁来取钱了
// 判断余额是否足够
{
if (this.money >= money) {
System.out.println(name + "取钱成功,金额:" + money + "元");
this.money -= money;
System.out.println(name + "取钱后,余额为 " + this.money + "元");
} else {
System.out.println(name + "取钱失败,余额不足");
}
}
}
}
Lock 锁是 JDK 5 开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大
Lock 是接口,不能直接实例化,可以采用它的实现类 ReentranLock 来构建 Lock 锁对象
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
// 1. 创建一个账户对象,代表两个人的共享账户
Account account = new Account("ICBC-110", 1000);
// 2. 创建两个线程,分别代表小明、小红,再去同一个账户对象中取钱1千
new MyThread("小明", account).start(); // 小明
new MyThread("小红", account).start(); // 小红
}
}
class MyThread extends Thread {
private Account account;
public MyThread(String name, Account account) {
super(name);
this.account = account;
}
@Override
public void run() {
// 取钱
account.drawMoney(1000);
}
}
class Account {
private String cardId; // 卡号
private double money; // 钱
// 创建了一个锁对象(每个银行卡都应该有一个对应的锁)
private final Lock lk = new ReentrantLock();
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
// 取钱功能
public void drawMoney(double money) {
String name = Thread.currentThread().getName(); // 获取线程的名字,判断是谁来取钱了
lk.lock(); // 加锁(加锁的前提是:一定要确保可以解锁!)
// 判断余额是否足够
try {
{
if (this.money >= money) {
System.out.println(name + "取钱成功,金额:" + money + "元");
this.money -= money;
System.out.println(name + "取钱后,余额为 " + this.money + "元");
} else {
System.out.println(name + "取钱失败,余额不足");
}
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lk.unlock(); // 解锁
}
}
}