线程(thread)是一个程序内部的一条执行路径。main方法的执行是一条单独的执行路径。程序中如果只有一条执行路径,那么这个程序就是单线程的程序。 1.2 什么是多线程
多线程是指从软硬件上实现多条执行流程的技术。 二、多线程的创建 2.1 继承Thread类
Java是通过java.lang.Thread类来代表线程的。按照面向对象的思想,Thread类提供了实现多线程的方式。过程:
- 定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法创建MyThread类的对象调用线程对象的start()方法启动线程(启动后还是执行run方法)
直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。只有调用start方法才是启动一个新的线程执行。 主线程任务应该放在子线程之后。
public class ThreadDemo1 {
public static void main(String[] args) {
//3.new一个新线程对象
Thread t = new MyThread();
//4.调用start方法启动线程(执行run)
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程输出:" + i);
}
}
}
//1.定义一个线程类继承Thread类
class MyThread extends Thread {
//2.重写run方法,定义线程功能
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程输出:" + i);
}
}
}
2.2 实现Runnable接口
过程:
- 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法。创建MyRunnable任务对象。把MyRunnable任务对象交给Thread处理。调用线程对象的start()方法启动线程。
public class ThreadDemo2 {
public static void main(String[] args) {
//3.创建一个任务对象
MyRunnable task = new MyRunnable();
//4.将任务对象交给线程对象Thread处理
Thread t = new Thread(task);
//5.启动线程
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程输出:" + i);
}
}
}
//1.定义一个线程类,实现Runnable接口
class MyRunnable implements Runnable {
//2.重写run方法,定义线程任务
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程输出:" + i);
}
}
}
2.3 实现Callable接口
前2种线程创建方式都存在一个问题:
他们重写的run方法均不能直接返回结果不适合需要返回线程执行结果的业务场景 过程
- 得到任务对象
- 定义类实现Callable接口,重写call方法,封装要做的事情用FutureTask把Callable对象封装成线程任务对象
public FutureTask<>(Callable call):把Callable对象封装成FutureTask对象public V get():获取线程执行call方法返回结果 优点:
线程任务类只是实现接口,可以继续继承类的实现接口,扩展性强可以在线程执行完毕后去获取线程执行的结果 缺点:编码稍微复杂
public class threadDemo3 {
public static void main(String[] args) {
//3.创建Callable任务对象
Callable call1 = new MyCallable(100);
//4.把Callable任务对象,交给FutureTask对象(实现了Runnable接口,可以调用get方法得到线程执行结果)
FutureTask f1 = new FutureTask(call1);
//5.交给线程执行
Thread t1 = new Thread(f1);
//6.启动线程
t1.start();
//3.创建Callable任务对象
Callable call2 = new MyCallable(200);
//4.把Callable任务对象,交给FutureTask对象(实现了Runnable接口,可以调用get方法得到线程执行结果)
FutureTask f2 = new FutureTask(call2);
//5.交给线程执行
Thread t2 = new Thread(f2);
//6.启动线程
t2.start();
try {
String rs1 = f1.get();
System.out.println("第一个线程执行结果: " + rs1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
try {
String rs2 = f2.get();
System.out.println("第二个线程执行结果: " + rs2);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//1.定义一个任务类,实现Callable接口(声明线程任务执行完毕后返回结果的数据类型)
class MyCallable implements Callable{
//2.重写call方法(任务方法)
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += i;
}
return "子线程的执行结果是: " + sum;
}
}
三、Thread类的常用方法
3.1 设置名称
获取线程名称:getName()设置线程名称:setName()获取当前线程对象:currentThread() 3.2 线程休眠方法
public static void sleep(long time):让当前线程休眠指定的时间后再继续执行,单位为毫秒。
public class ThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 5; i++) {
System.out.println("输出: " + i);
if(i == 3){
//让当前线程进入休眠
Thread.sleep(3000);
}
}
}
}
四、线程安全
4.1 线程安全问题
多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,成为线程安全问题线程安全问题出现的原因
存在多线程并发同时访问共享资源存在修改共享资源 4.2 线程安全问题模拟
ThreadDemo.java
public class ThreadDemo {
public static void main(String[] args) {
//1.定义线程类,创建一个共享的账户对象
Account acc = new Account("share",100000);
//2.创建两个线程对象,代表小明和小红同时进入
new DrawThread(acc,"小明").start();
new DrawThread(acc,"小红").start();
}
}
Account.java
public 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 + "取钱失败");
}
}
}
DrawThread.java
public class DrawThread extends Thread{
private Account acc;
public DrawThread(Account acc,String name) {
super(name);
this.acc = acc;
}
@Override
public void run() {
//小明,小红取钱
acc.drawMoney(100000);
}
}
执行结果
小明来取钱成功,吐出: 100000.0 小红来取钱成功,吐出: 100000.0 小明取钱后剩余:0.0 小红取钱后剩余:-100000.0五、线程同步 5.1 线程同步概述
为了解决线程安全问题线程同步的核心思想:加锁,把共享资源进行上锁,每次只能一个线程进入访问,访问完毕以后解锁,然后其他线程才能进来。 5.2 方式一:同步代码块
作用:把出现线程安全问题的核心代码给上锁原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行锁对象的规范要求
规范:建议使用共享资源作为锁对象对于实例方法建议使用this作为锁对象对于静态方法建议使用字节码(类名.class)对象作为锁对象
synchronized(同步锁对象) {
操作共享资源的代码
}
5.2 方式二:同步方法
作用:把出现线程安全问题的核心方法给上锁原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行底层原理
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码实例方法:默认用this作为所的对象静态方法:默认用类名.class作为锁的对象
修饰符 synchronized 返回值类型 方法名称(形参列表) {
操作共享资源的代码
}
5.3 方式三:Lock锁
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。Lock实现可以获得更广泛的锁定操作Lock锁接口不能直接实例化,这里采用它的实现类ReentrantLock来构建锁对象获得锁对象:public ReentrantLock()获得锁:void lock()释放锁:void unlock()



