多线程
- 一.创建线程
- 1.多线程创建方式一(Thread)
- 2.多线程创键方式二(Runnable)
- 3.线程创建方式三
- 二.线程安全问题
- 解决办法
- 1.使用同步代码块synchornized
- 2 .使用Lock解决线程安全问题
- 三.总结
- 线程就是程序内部的一条执行流程
一.创建线程
常用的方法
- Thread.currentThread(): 获取当前线程
- Thread.getName(): 获取当前线程的名称
- Thread.setName(): 设置当前线程的名称
- Thread.yield(): 礼让当前正在执行的线程
- Thread.sleep(long millis): 让当前线程暂停millis毫秒
- Thread.interrupt(): 中断当前线程
- Thread.isInterrupted(): 判断当前线程是否被中断
- Thread.currentThread().interrupt(): 中断当前线程
- Thread.join(): 插队,调用这个方法的线程先执行完毕
- Thread.sleep()睡眠
1.多线程创建方式一(Thread)
- 定义一个子类MyThread继承Thread,重写run方法
- 创建Mythread类的对象
- 通过对象调用start方法启动线程,线程开启后,会自动调用线程对象的run方法执行
- JVM会自动开启一个线程,执行main方法,称为主线程
- 主线程中开启了其他线程称为子线程
- 开启了子线程后,子线程会跟我们的主线程争抢资源,谁抢到了谁就先执行,执行一小会用释放,再重新抢占资源
优点: 创建线程简单
缺点: 扩展性不强,不能返回线程执行结果
2.多线程创键方式二(Runnable)
- 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
- 创建MyRunnable任务对象
- 把MyRunnable对象交给Thread处理
- 调用start()方法启动线程
优点
线程任务与线程对象分离,可以让同一线程执行不同的任务
缺点
需要多一个Runnable对象
不能返回线程结果
3.线程创建方式三
- 实现callable<返回值类型>接口,重写call方法
- callable接口在实现的时候需要指定泛型,用来确定返回值的数据类型
- 创建FutureTask对象,构造方法中需要传入callable接口实现类对象
- FutureTask()中提供了一个get(),它是一个阻塞的方法,他会阻塞线程一直从Futurue对象中去获取返回的结果,如果没有获取到会一致阻塞等待
- 创建Thread对象,构造方法中传入futureTask对象
- 调用start()方法启动线程
- 调用get()方法的时候会有异常(最好分开进行try… catch异常捕获,因为当有一条线程出现异常的时候,不会影响到其他线程的返回结果,)
package com.dream.Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo03 {
public static void main(String[] args) {
//1.线程创建的方式三: 实现callable接口,重写call方法
//callable接口在实现的时候需要指定泛型,用来确定返回值的数据类型
//2.创建callable接口实现类的对象
MyThread3 myThread3 = new MyThread3(100);
//3.创建FutureTask对象,构造方法中需要传入callable接口实现类对象
FutureTask<String> futureTask = new FutureTask<>(myThread3);
//futureTask对象是线程任务对象,他的get方法可以在线程运行结束后,获取线程执行结果
//4.创建Thread对象,构造方法中传入futureTask对象
Thread t = new Thread(futureTask);
//5.启动线程
t.start();
MyThread3 myThread3_2 = new MyThread3(200);
FutureTask<String> futureTask_2 = new FutureTask<>(myThread3_2);
Thread t2 = new Thread(futureTask_2);
t2.start();
//6.获取线程执行结果(最好分开进行try... catch异常捕获,因为当有一条线程出现异常的时候,不会影响到其他线程的返回结果,)
try {
String result = futureTask.get();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
try {
String result_2 = futureTask_2.get();
System.out.println(result_2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyThread3 implements Callable<String> {
private int n;
public MyThread3(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
//计算1-n的和返回
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return "1-" + n + "的和为:" + sum;
}
}
二.线程安全问题
- 存在多个线程在同时执行
- 同时访问一个共享资源
- 同时对共享资源做操作
比如两个人访问同一个银行账户,就会出现线程安全问题
package com.dream.threadsafe;
public class MyRunnable implements Runnable {
private Account account = new Account();
@Override
public void run() {
if (account.getBalance() >= 100000) {
account.withdraw(account, 100000);
} else {
System.out.println("余额不足,取款失败");
}
}
}
/**
* 模拟取款
*/
class Account {
private int balance = 100000;// 余额
public int getBalance() {
return balance;
}
public void withdraw(Account acc, int amount) {
System.out.println(Thread.currentThread().getName() + "正在取款" + amount);
balance -= amount;
System.out.println(Thread.currentThread().getName() + "取款成功,余额为" + balance);
}
}
启动线程
public class Test {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, "小明");
Thread t2 = new Thread(mr, "小红");
t1.start();
t2.start();
}
}
解决办法
1.使用同步代码块synchornized
- 同步锁:我们传入的参数要是唯一的对象,
- 但是不要直接使用字符串作为锁对象,因为会导致其它无关线程不能同步线程
- 如果同步锁对象是在实例方法内部,那么锁对象就是this
- 每次抢占到资源都会判断当前锁是否是开启的,开启了就进入执行,关闭则等待
- 如果同步锁对象是在静态方法内部,那么锁对象就是 类名.class
- 也可以直接加在方法的修饰符后面,构成同步方法
2 .使用Lock解决线程安全问题
- Lock是一个接口,需要创建它的实现类对象ReentranLock()是Lock接口的实现类
- 相较于synchronized锁,Lock会更加的灵活
- 他俩都属于重量级锁
private final Lock lk = new ReentrantLock();
public void withdraw(Account acc, double money) {
lk.lock();//上锁
try {
if (this.money >= money) {
//余额足够,取款
System.out.println(Thread.currentThread().getName() + "取款成功");
//更新余额
this.money -= money; //
System.out.println(acc.getCardId() + "余额为:" + this.money);
} else {
System.out.println("余额不足");
System.out.println(Thread.currentThread().getName() + "取款失败");
}
} finally {
lk.unlock();//解锁
}
}
三.总结
本章主要讲解了线程几种线程的创建方式,线程安全问题出现的场景,以及线程安全的解决方式