什么是线程?
什么是多线程?
大白话:多线程,是允许多个线程并发或并行执行,而不是像单线程一样,顺序执行,前面的执行完了才能执行后面的语句。
并发:交替执行 (一般在单核处理器上)
并行:同时执行 (多核处理器上,多个程序可以同时执行)
如何在程序中创建多条线程?
创建方式一:通过继承Thread,并重写run方法
将类声明为Thread的子类。 此子类应覆盖类Thread的run方法。
然后可以分配和启动子类的实例
创建方式二:通过实现Runnable接口创建,重写run方法
简化写法:
创建方式三:实现callable接口(这种方式常用)
创建步骤如下:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class threadDemo {
public static void main(String[] args) {
// 2、创建一个Callable接口的实现类
Callable<String> c1 = new MyCallable(100);
// 3、把Callable对象封装成一个真正的线程任务对象FutureTask对象
/*
* 未来任务对象的作用:
* a、本质是一个Runnable线程任务对象,可以交给Thread线程对象处理
* b、可以获取到线程任务的执行结果
* **/
FutureTask<String> f1 = new FutureTask<>(c1);
// 4、创建一个线程对象,把未来任务对象作为构造方法参数传递给Thread对象
Thread t1 = new Thread(f1);
// 5、启动线程
t1.start();
//创建第二个线程
Callable<String> c2 = new MyCallable(200);
FutureTask<String> f2 = new FutureTask<>(c2);
Thread t2 = new Thread(f2);
t2.start();
// 6、获取线程执行完毕后返回的结果
try {
//如果主线程执行到这里,发现第一个线程还没执行完,就会一直阻塞,让出cpu,直到第一个线程执行完,才会继续往下执行
System.out.println(f1.get());
} catch (Exception e) {
e.printStackTrace();
}
try {
System.out.println(f2.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 1.定义一个实现类实现Callable接口
class MyCallable implements Callable<String> {
// 定义一个int类型的变量,用于接收外部传递进来的参数
private final 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 "子线程运算结果为1-"+n+":"+sum;
}
}
线程常用方法
import java.lang.Thread;
public class Demo1 {
public static void main(String[] args) {
// 创建线程1
Thread t1 = new MyThread(); // 默认线程名称是Thread-0
// 设置线程名称
t1.setName("1号县城");
// 启动线程
t1.start();
// 打印线程名称
System.out.println(t1.getName());
// 创建线程2
Thread t2 = new MyThread(); // 默认线程名称是Thread-1
t2.setName("2号线程");
t2.start();
System.out.println(t2.getName());
// 哪个线程调用这个代码,这个代码就拿到哪个线程
Thread m = Thread.currentThread(); // 主线程main在调用这句代码,主线程拿到这个代码
m.setName("主线程");
System.out.println(m.getName());
}
}
class MyThread extends Thread{
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"子线程输出:"+i);
}
}
}
Thread的 join方法:线程插队,让调用这个方法的线程先执行
import java.lang.Thread;
public class Demo1 {
public static void main(String[] args) {
// 创建线程1
Thread t1 = new MyThread();
t1.setName("1号线程");
// 启动线程
t1.start();
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"主线程输出:"+i);
if (i==2){
try {
// 插队,等待线程1执行完,再执行后面的语句
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class MyThread extends Thread{
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"子线程输出:"+i);
}
}
}
线程安全问题
1、什么是线程安全问题?
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
//模拟线程安全问题
// 需求描述:小明和小红同时去同一个公共账户取钱
// 创建账户类
public class Account {
private double money;
private String carId;
public Account() {
}
public Account(double money, String carId) {
this.money = money;
this.carId = carId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public String getCarId() {
return carId;
}
public void setCarId(String carId) {
this.carId = carId;
}
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 + "取钱失败,余额不足");
}
}
}
// 创建线程类
public class DrawThread extends Thread {
// 定义一个变量,类型为 Account
private Account acc;
// 构造器
public DrawThread(String name, Account acc) {
super(name);
this.acc = acc;
}
@Override
public void run() {
acc.DrawMoney(100000);
}
}
// 应用
public class Test {
public static void main(String[] args) {
// 账户类
Account acc = new Account( 100000, "123456");
// 线程类,创建小明和小红2个线程,模拟小明和小红2个人同时去取钱
new DrawThread("小明", acc).start();
new DrawThread( "小红", acc).start();
}
}
// 运行后打印结果为:
/**
小明取钱成功,吐出为:100000.0
小红取钱成功,吐出为:100000.0
小明取钱成功,余额为:0.0
小红取钱成功,余额为:-100000.0
**/
2、如何解决线程安全问题?线程同步
线程同步的核心思想:
让多个线程先后依次访问共享资源,这样就可以避免出现线程安全问题
线程同步方案:加锁
2.1 线程同步方式一:同步代码块
syn /sɪn/ abbr. 同步
synch /sɪntʃ/ n.同步;同时,同步
synchro /ˈsɪŋkroʊ/ adj.同步的 n.【电】(自动)同步机
synchronize /ˈsɪŋkrənaɪz/ v.(使)同步,在时间上一致,同速进行
synchronized /ˈsɪŋkrənaɪzd/ v.(使)同步,在时间上一致,同速进行 adj.同步的;同步化的
此时再执行Test类,打印结果为:
小明取钱成功,吐出为:100000.0
小明取钱成功,余额为:0.0
小红取钱失败,余额不足
// 1、实例方法
public class Account {
synchronized (this) {
}
}
// 2、静态方法
public static class Account {
synchronized (Account .class) {
}
}
2.2 线程同步方式二:同步方法
2.3 线程同步方式三:Lock锁
reentrant /riːˈɛntrənt/ 可重入;可重入的;重入;可再入的
线程池
1、线程工作原理
创建几个线程,把需要处理的程序放进任务队列,当有空闲的线程就执行任务队列里面的任务,当线程处于忙碌状态,队列里面的其他任务就等待排序等待
2、如何创建线程池
executor /ɪɡˈzekjətə®/ n.遗嘱执行人(或银行等)
2.1 方式一:通过ThreadPoolExecutor创建线程池
2.1.1 处理Runnable任务
// 定义一个线程任务类实现Runnable接口
public class MyRunnable implements Runnable {
// 重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
// 主类
public class ExecutorServiceDemo1 {
public static void main(String[] args) {
// 1.创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(
3,
5,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
// 2.使用线程池处理任务
Runnable target = new MyRunnable();
pool.execute(target); // 提交第1个任务,创建第1个线程,并启动线程处理这个任务
pool.execute(target); // 提交第2个任务,创建第2个线程,并启动线程处理这个任务
pool.execute(target); // 提交第3个任务,创建第3个线程,并启动线程处理这个任务
pool.execute(target); // 复用线程
pool.execute(target); // 复用线程
// 3.关闭线程池对象:一般不关闭线程池
// pool.shutdown(); // 线程池不再接受新的任务,但会继续处理已经提交的任务
// pool.shutdownNow(); // 线程池不再接受新的任务,并立即停止正在处理的任务
}
}
// 定义一个线程任务类实现Runnable接口
public class MyRunnable implements Runnable {
// 重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(Integer.MAX_VALUE); // 线程休眠
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
// 主类
public class ExecutorServiceDemo1 {
public static void main(String[] args) {
// 1.创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(
3,
5,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3), //任务队列3个
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
// 2.使用线程池处理任务
Runnable target = new MyRunnable();
pool.execute(target); // 提交第1个任务,创建第1个线程,并启动线程处理这个任务
pool.execute(target); // 提交第2个任务,创建第1个线程,并启动线程处理这个任务
pool.execute(target); // 提交第3个任务,创建第1个线程,并启动线程处理这个任务
pool.execute(target); // 线程复用
pool.execute(target); // 线程复用
pool.execute(target); // 线程复用
// 3个核心线程在忙,3个任务队列也排满了,由于设置的maximumPoolSize=5,此时到了创建临时线程的时机
pool.execute(target); // 线程池已满,创建临时线程的时机出现
pool.execute(target); // 线程池已满,创建临时线程的时机出现
// 3个核心线程在忙,3个任务队列也排满了,临时线程也满了,此时到了拒绝策略的时机
pool.execute(target);
// 3.关闭线程池对象:一般不关闭线程池
// pool.shutdown(); // 线程池不再接受新的任务,但会继续处理已经提交的任务
// pool.shutdownNow(); // 线程池不再接受新的任务,并立即停止正在处理的任务
}
}
2.1.2 处理Callable任务
// 定义一个类实现Callable接口
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n)
{
this.n = n;
}
// 实现call方法,定义线程执行体
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return Thread.currentThread().getName() + "计算了1-" + n + "个数相加,结果是:" + sum;
}
}
// 主类
public class ExecutorServiceDemo2 {
public static void main(String[] args) {
// 1.创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(
3,
5,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3), //任务队列3个
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
// 2.使用线程池处理Callable任务
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
Future<String> f5 = pool.submit(new MyCallable(500));
try {
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
System.out.println(f5.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.2 方式二:通过Executors创建线程池
Executors是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
2.3 并发和并行
练习
解答上诉问题:
1、当使用 synchronized 时,需要指定一个锁对象。
2、锁对象可以是任何对象,但必须是所有线程都能访问到的对象(通常是共享资源本身或与共享资源相关的对象)。
3、在代码中,redPacket 是一个共享的集合对象,所有线程都需要从中取出红包金额。因此,redPacket 是多个线程竞争的资源。
4、如果使用 this 作为锁对象,那么每个线程的锁将是自己的实例(即每个 PeopleGetPacket 对象的锁互不相关),无法实现线程间的同步
// 抢红包类
import java.util.List;
public class PeopleGetPacket extends Thread{
private final List<Integer> redPacket;
// 构造函数
public PeopleGetPacket(List<Integer> redPacket, String name) {
super(name);
this.redPacket = redPacket;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
while (true) {
synchronized (redPacket) {
if (redPacket.isEmpty()) {
break;
}
// 随机获取集合下标,并通过下标得到集合中的红包金额
int index = (int) (Math.random() * redPacket.size());
Integer money = redPacket.remove(index);
System.out.println(name + ":抢到了" + money + "元");
if(redPacket.isEmpty()){
System.out.println("活动结束");
break;
}
}
// 这段try catch是为了模拟抢红包写得,实际开发中不会写这段代码
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
// 主类
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class Test {
public static void main(String[] args) {
// 1.创建红包
List<Integer> redPacket = getRedPacket();
// 2.创建100个线程,竞争同一个集合红包
for(int i = 0; i < 100; i++){
new PeopleGetPacket(redPacket,"人"+i).start();
}
}
public static List<Integer> getRedPacket() {
// 创建200个红包,1~30元占80%,31~100元占20%
// 创建一个list集合变量redPacket存放200个红包
Random r = new Random();
List<Integer> redPacket = new ArrayList<>();
for(int i = 0; i < 160; i++){
// 产生1~30的随机红包,并存放到redPacket集合中
redPacket.add(r.nextInt(31));
}
for(int i = 0; i < 40; i++){
// 产生31~100的随机红包,并存放到redPacket集合中
redPacket.add(r.nextInt(70) + 31);
}
return redPacket;
}
}