一、多线程基础
1、什么是进程
由操作系统执行的计算机程序的实例,是操作系统分配资源的基本单位,每个应用程序都运行在一个进程中。
2、什么是线程
进程内部按照单一顺序执行的控制流,是进程的一项子任务,线程是CPU调度的基本单元。
进程与线程的关系:
一个进程可以包含一个或多个线程,一个线程一定属于某个进程。
程序一旦运行会占用相当一部分资源,所有的线程占用的资源总和==进程占用资源总和。
单线程与多线程的区别:
单线程所有的任务按照顺序一个接一个进行,效率较低
多线程允许程序同时执行多个任务,每个任务由一个独立的线程负责执行,如果一个线程阻塞,其他线程可以继续允许,效率高
3、java中的多线程
(1)Thread类
- 是java中代表线程的类,位于java.lang包下
- 类中包含封装线程具体信息的属性,以及对线程进行操作的方法
- 每个thread类的对象代表一个具体的线程
(2)Thread类的方法
- long getId():返回该线程的默认id
- String getName():返回该线程的名称
- int getPriority():返回线程的优先级
- Thread.state getState():获取线程的状态
- boolean isAlive():测试线程是否处于活动状态
- boolean isDaemon:测试线程是否为守护线程
- boolean isInterrupted():测试线程是否已经中断
(3)创建线程
构造方法:
- public Thread():分配一个新的线程对象
- public Thread(String name):分配一个指定名称的新的线程对象
- public Thread (Runnable target):分配一个带指定目标的新的线程对象
其他方法:
- public static Thread currentThread():返回对当前正在执行的线程对象的引用
- public String getName():获取当前线程名称
- **public void start():此线程开始执行
/**
* @document: 创建并启动线程
* @Author:SmallG
* @CreateTime:2023/8/9+15:42
*/
public class Demo01 {
public static void main(String[] args) {
//获取主线程,获取当前正在执行的线程对象
Thread thread = Thread.currentThread();//----主线程
//输出线程的名字
System.out.println(thread.getId() + "--" + thread.getName());
//创建线程对象
Thread t1 = new Thread();
System.out.println(t1.getId() + "--" + t1.getName());
//创建线程并指定名字
Thread t2 = new Thread("线程1");
System.out.println(t2.getId() + "--" + t2.getName());
t2.start(); //启动线程,没有任何效果
}
}
创建线程还可以继承Thread类来实现线程(不推荐)
/**
* @document: 创建线程
* @Author:SmallG
* @CreateTime:2023/8/9+16:23
*/
public class Demo03 {
public static void main(String[] args) {
//创建线程对象
MyRun2 myRun1 = new MyRun2("线程1");
myRun1.start();
/*
线程1:1
线程1:2
线程1:3
线程1:4
线程1:5
*/
}
}
class MyRun2 extends Thread {
public MyRun2(String name) {
super(name);
}
@Override
public void run() {
//获取当前线程对象
Thread thread = Thread.currentThread();
String threadName = thread.getName();
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + ":" + i);
}
}
}
(4)指定线程执行的内容
/**
* @document: 执行线程
* @Author:SmallG
* @CreateTime:2023/8/9+15:52
*/
public class Demo02 {
public static void main(String[] args) {
MyRun1 run1 = new MyRun1();
//创建线程对象
Thread t1 = new Thread(run1, "线程1"); //给线程一个目标
Thread t2 = new Thread(run1, "线程2"); //给线程一个目标
//启动线程,Jvm会自动调用run()方法
t1.start();
t2.start();
//主线程打印
for (int i = 1; i <= 5; i++) {
System.out.println("main:" + i);
}
}
}
class MyRun1 implements Runnable {
@Override
public void run() {
//获取当前线程对象
Thread thread = Thread.currentThread();
String threadName = thread.getName();
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + ":" + i);
}
}
}
二、线程的状态
1、什么是线程的状态
新建状态
一个线程对象被创建出来时,该线程对象处于新建状态
就绪状态
调用了一个线程对象的start()方法
运行状态
CPU执行了一个线程对象的run()方法时处于运行状态
阻塞状态
分为:等待阻塞、同步阻塞、其他阻塞,当一个运行中的线程暂停运行时,该线程处于阻塞状态
终止状态
线程完成了任务或被终止,处于终止状态
2、线程的优先级
- Thread.MIN_PRIORITY(1)
- Thread.MAX_PRIORITY(10)
- Thread.NORM_PRIORITY(5)
/**
* @document:
* @Author:SmallG
* @CreateTime:2023/8/9+16:50
*/
public class Demo05 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
System.out.println("Hello world " + Thread.currentThread().getName());
}
}, "线程1");
Thread t2 = new Thread(() -> {
for (int i = 1; i <= 10000; i++) {
System.out.println(i + " : " + Thread.currentThread().getName());
}
}, "线程2");
//设置线程的优先级
t1.setPriority(Thread.MIN_PRIORITY); //优先级设为1
t2.setPriority(Thread.MAX_PRIORITY); //优先级设为10
t1.start();
t2.start();
}
}
3、线程的状态操作
- sleep():控制某个线程休眠一段时间
- join():控制某个线程等待该线程执行完成后再执行
- yield():控制一个线程从运行状态切换到就绪状态,用于事当前线程主动让出当次CPU时间片回到Runnable状态,等待分配时间片
- interrupt():向线程发送一个中断通知
sleep():
/**
* @document: 改变线程状态:sleep()---让进程休眠
* @Author:SmallG
* @CreateTime:2023/8/9+17:35
*/
public class Demo06 {
public static void main(String[] args) {
//创建线程对象
Thread t = new Thread(() -> {
System.out.println("正在运行的线程名称:"
+ Thread.currentThread().getName()
+ " 开始时间:" + LocalTime.now());
//休眠两秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在运行的线程名称:"
+ Thread.currentThread().getName()
+ " 结束时间:" + LocalTime.now());
});
t.start();
}
}
join():
/**
* @document: 线程状态----join()等待另一线程完成
* @Author:SmallG
* @CreateTime:2023/8/9+17:48
*/
public class Demo07 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new MyRun3(), "t1");
Thread t2 = new Thread(new MyRun3(), "t2");
Thread t3 = new Thread(new MyRun3(), "t3");
t1.start();
t1.join(2000);
t2.start();
TimeUnit.SECONDS.timedJoin(t2, 2000);
t3.start();
System.out.println("所有线程均死亡");
}
}
class MyRun3 implements Runnable {
@Override
public void run() {
System.out.println("线程开始执行:" + Thread.currentThread().getName());
try {
//休眠2秒
Thread.sleep(2000); //休眠2秒,可读性不好
//休眠2秒
TimeUnit.SECONDS.sleep(2); //java5后的新特性
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程结束执行:" + Thread.currentThread().getName());
}
}
yieId():
/**
* @document: 线程就绪状态--yield
* @Author:SmallG
* @CreateTime:2023/8/10+9:07
*/
public class Demo08 {
public static void main(String[] args) {
Runnable run = () -> {
//获取线程名字
String name = Thread.currentThread().getName();
for (int i = 0; i < 8; i++) {
System.out.println(name + ":" + i);
if (i == 5) {
//让当前线程主动释放CPU的使用权,回到就绪状态
Thread.yield();
}
}
};
//创建线程对象
Thread t1 = new Thread(run, "线程1");
Thread t2 = new Thread(run, "线程2");
t1.start();
t2.start();
}
}
interrupt():
/**
* @document: 线程中断 interrupt
* @Author:SmallG
* @CreateTime:2023/8/10+9:31
*/
public class Demo10 {
public static void main(String[] args) throws InterruptedException {
Runnable run = () -> {
Thread t = Thread.currentThread();
System.out.println("线程启动了");
//循环持续观察线程是否中断
while (!t.isInterrupted()) {
System.out.println("线程仍在活动");
}
System.out.println("线程结束了");
};
Thread t = new Thread(run, "子线程");
t.start();
TimeUnit.MILLISECONDS.sleep(1);
//向子线程发送中断通知
t.interrupt();
}
}
4、其他阻塞状态
- 等待阻塞:与线程的同步相关
- 同步阻塞:与线程的同步相关
- 其他阻塞:因调用线程的sleep方法或join方法而导致的线程阻塞
5、后台线程
- 指的是程序运行时在后台提供的一种通用服务的线程,也就是为其他线程服务的,也称守护线程
- 后台线程不属于程序不可或缺的部分,当所有的非后台线程结束时,程序也就终止了,同时也会终止所有后台线程
- 垃圾回收就是后台线程
- 通过setDaemon(true)来设置线程为后台线程,注意必须在start()方法之前设定,否则会抛出IIegalThreadStateException异常
- 可以使用isDaemon()方法来判断一个线程是否为后台线程
/**
* @document: 守护线程
* @Author:SmallG
* @CreateTime:2023/8/10+10:07
*/
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
Runnable run = () -> {
while (true) {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t = new Thread(run, "子线程");
//设置子线程为后天线程,必须在子线程之前设置
t.setDaemon(true);
t.start();
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(1);
}
System.out.println("主线程结束了");
}
}
三、线程并发安全
1、线程并发安全概述
线程并发安全问题
一个进程下的多个线程共享该进程被分配到的内存空间,也就是一个进程下的数据可能被多个线程同时访问,称为数据共享。
常见的共享数据有:
- 对象的属性
- 类的静态变量
常见的非共享数据有:
- 方法中声明的局部变化
- 方法形参
多个线程并发访问可能会导致意外或错误的行为,称为线程并发安全问题。
/**
* @document: 线程安全问题
* @Author:SmallG
* @CreateTime:2023/8/10+10:20
*/
public class Demo12 {
public static void main(String[] args) {
Account account = new Account();
account.withDraw();
}
}
//银行账户
class Account {
public static double balance = 2000;
/**
* 取钱的方法
*/
public void withDraw() {
//创建线程
MyThread t1 = new MyThread("丈夫", 1500);
MyThread t2 = new MyThread("妻子", 1500);
t1.start();
t2.start();
}
/**
* 生成一个取钱操作的内部类
*/
class MyThread extends Thread {
private String name; //名字
private double money; //取钱金额
public MyThread(String name, double money) {
this.name = name;
this.money = money;
}
@Override
public void run() {
//判断余额是否充足
if (Account.balance > money) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改账户余额
Account.balance = Account.balance - money;
System.out.println(name + "取钱成功!余额还剩" + Account.balance);
}
}
}
}
2、线程互斥
(1)临界区:一个受保护的区域
- 限定一次不能有多个线程同时进入这个区域
- 仅在临界区中的线程可以对共享资源进行操作
- 当已经有一个线程进入临界区时,其他线程必须等待该线程离开临界区后,再进入临界区
(2)线程互斥
指不同线程通过竞争进入临界区(共享的数据和硬件资源)
- 将异步的操作变为同步操作
- 异步操作:多线程并发的操作,相当于各自执行
- 同步操作:有先后顺序的操作,相当于你执行完我再执行
(3)synchronized关键字
用于在程序中设置临界区,以实现线程的互斥,也称为同步锁。
/**
* @document: 线程同步synchronized关键字实现线程同步
* @Author:SmallG
* @CreateTime:2023/8/10+10:44
*/
public class Demo13 {
public static void main(String[] args) {
Account2 account2 = new Account2();
account2.withDraw();
}
}
class Account2 {
public static double balance = 2000;
public static Object lock = new Object(); // 定义一把锁
/**
* 取钱方法
*/
public void withDraw() {
MyThread t1 = new MyThread("SmallG", 1500);
MyThread t2 = new MyThread("SmallY", 1500);
t2.start();
t1.start();
}
class MyThread extends Thread {
private String name;
private double money;
public MyThread(String name, double money) {
this.name = name;
this.money = money;
}
@Override
public void run() {
//加同步锁
//声明一个同步代码块,其中的代码同时只能被一个线程访问
synchronized (Account2.lock) {
if (Account2.balance > money) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Account2.balance -= money;
System.out.println(name + "取钱成功!当前余额为" + Account2.balance);
} else {
System.out.println(name + "取钱失败!余额为" + Account2.balance);
}
}
}
}
}
(4)同步阻塞状态
当一个线程因synchronized关键字进入阻塞状态时,该线程处于同步阻塞状态
3、synchronized方法
- 在使用时需要确认多线程访问的是同一个实例的同步方法,才能实现同步效果
- synchronized也可以修饰静态方法,即静态同步方法,锁定的是类对象
- 每个类都有唯一的一个类对象,可以通过类名.class获取
/**
* @document: synchronized同步方法
* @Author:SmallG
* @CreateTime:2023/8/10+11:29
*/
public class Demo01 {
public static void main(String[] args) {
MyRun1 myRun1 = new MyRun1();
Thread t1 = new Thread(myRun1, "线程1");
Thread t2 = new Thread(myRun1, "线程2");
t1.start();
t2.start();
}
}
class MyRun1 implements Runnable {
int num = 0;
//同步方法
public synchronized void printNum() throws InterruptedException {
if (num > 10) {
return; // 程序结束
}
System.out.println(Thread.currentThread().getName() + " : " + num);
num++;
TimeUnit.SECONDS.sleep(1);
}
@Override
public void run() {
while (num < 11) {
try {
printNum();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
synchronized实现原理
通过对象的锁来实现
4、死锁
相互等待造成死锁,产生死锁有四个因素:互斥条件、不可被剥夺、请求保持、环路等待。
死锁案例一:
/**
* @document: 线程死锁问题
* @Author:SmallG
* @CreateTime:2023/8/10+14:06
*/
public class DeadLockDemo01 {
public static void main(String[] args) {
DeadDemo t1 = new DeadDemo();
DeadDemo t2 = new DeadDemo();
t1.flag = 1;
t2.flag = 0;
new Thread(t1, "线程1").start();
new Thread(t2, "线程2").start();
}
}
class DeadDemo implements Runnable {
public int flag = 1;
//定义两个静态成员,静态成员被所有对象共享
private static Object o1 = new Object();
private static Object o2 = new Object();
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + ": flag =" + flag);
if (flag == 1) {
synchronized (o1) {
System.out.println(name + "获取了o1锁");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ": 线程想获取o2锁");
synchronized (o2) {
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
System.out.println(name + "获取了o2锁");
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("想获取o1锁");
synchronized (o1) {
System.out.println("0");
}
}
}
}
}
死锁案例二:
/**
* @document: 线程死锁问题 卖家与买家问题
* @Author:SmallG
* @CreateTime:2023/8/10+14:21
*/
public class DeadLockDemo02 {
public static void main(String[] args) {
ThreadDeadLock t = new ThreadDeadLock();
new Thread(t).start();
new Thread(t).start();
}
}
//定义卖家
class Seller {
//卖东西的方法
public void sell() {
System.out.println("卖家说:“你给我钱我就把东西给你”");
}
//拿到钱
public void get() {
System.out.println("卖家拿到钱了");
}
}
//定义买家
class Buyer {
//买东西的方法
public void buy() {
System.out.println("买家说:“你给我东西我就给你钱”");
}
//得到东西了
public void get() {
System.out.println("买家得到东西了");
}
}
class ThreadDeadLock implements Runnable {
public int flag = 1;
private Seller seller = new Seller(); //创建卖家
private Buyer buyer = new Buyer(); //创建买家
@Override
public void run() {
//卖家说
if (flag == 1) {
flag = 0;
//卖家说话
synchronized (seller) {
seller.sell();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖家得到买家的钱
synchronized (buyer) {
seller.get(); //卖家得到钱了
}
}
}
if (flag == 0) {
flag = 1;
//买家说话
synchronized (buyer) {
buyer.buy();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (seller) {
buyer.get();
}
}
}
}
}
哲学家就餐问题
五个哲学家五双筷子,
/**
* @document: 哲学家就餐问题
* @Author:SmallG
* @CreateTime:2023/8/10+15:00
*/
public class PhilosopherThread extends Thread {
private Chopsticks left; //左手边的筷子
private Chopsticks right; //右手边的筷子
private int index; //为哲学家编号
//定义一个构造方法
public PhilosopherThread(Chopsticks left, Chopsticks right, int index) {
this.left = left;
this.right = right;
this.index = index;
}
@Override
public void run() {
synchronized (left) {
//每人拿起左手边的筷子
try {
//程序暂停一秒
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(index + "等待拿起右手边的筷子");
synchronized (right) {
System.out.println(index + "拿到了右手边的筷子");
System.out.println("开始吃饭");
}
}
}
public static void main(String[] args) {
//创建5根筷子
Chopsticks chopsticks1 = new Chopsticks();
Chopsticks chopsticks2 = new Chopsticks();
Chopsticks chopsticks3 = new Chopsticks();
Chopsticks chopsticks4 = new Chopsticks();
Chopsticks chopsticks5 = new Chopsticks();
//创建五名哲学家
PhilosopherThread t1 = new PhilosopherThread(chopsticks1, chopsticks2, 1);
PhilosopherThread t2 = new PhilosopherThread(chopsticks2, chopsticks3, 2);
PhilosopherThread t3 = new PhilosopherThread(chopsticks3, chopsticks4, 3);
PhilosopherThread t4 = new PhilosopherThread(chopsticks4, chopsticks5, 4);
PhilosopherThread t5 = new PhilosopherThread(chopsticks5, chopsticks1, 5);
//启动线程,哲学家开始就餐
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
//筷子类
class Chopsticks {
}
**解决死锁问题的三种方案
哲学家就餐问题解决:
/**
* @document: 哲学家就餐问题(死锁解决)
* 1、最少保证
* 最多只有四位哲学家拿起左手边的筷子,最后一名哲学家先拿起右手边的筷子
* 这样能保证每一次有一个科学家和别人拿起的筷子是相反的(循环等待条件不成立)
* @Author:SmallG
* @CreateTime:2023/8/10+15:13
*/
public class PhilosopherThread01 extends Thread {
private Chopsticks left;
private Chopsticks right;
private int index;
public PhilosopherThread01(Chopsticks left, Chopsticks right, int index) {
this.left = left;
this.right = right;
this.index = index;
}
@Override
public void run() {
if (index == 5) {
//最后一名科学家拿起右手边的筷子
synchronized (right) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(index + "等待拿起左手边的筷子");
synchronized (left){
System.out.println(index+"拿到了左手边的筷子开始吃饭!");
}
}
}else{
synchronized (left){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(index + "等待拿起右手边的筷子");
synchronized (right){
System.out.println(index+"拿到了右手边的筷子开始吃饭!");
}
}
}
}
public static void main(String[] args) {
//创建5根筷子
Chopsticks chopsticks1 = new Chopsticks();
Chopsticks chopsticks2 = new Chopsticks();
Chopsticks chopsticks3 = new Chopsticks();
Chopsticks chopsticks4 = new Chopsticks();
Chopsticks chopsticks5 = new Chopsticks();
//创建五名哲学家
PhilosopherThread01 t1 = new PhilosopherThread01(chopsticks1, chopsticks2, 1);
PhilosopherThread01 t2 = new PhilosopherThread01(chopsticks2, chopsticks3, 2);
PhilosopherThread01 t3 = new PhilosopherThread01(chopsticks3, chopsticks4, 3);
PhilosopherThread01 t4 = new PhilosopherThread01(chopsticks4, chopsticks5, 4);
PhilosopherThread01 t5 = new PhilosopherThread01(chopsticks5, chopsticks1, 5);
//启动线程,哲学家开始就餐
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
奇偶互反:
/**
* @document: 哲学家就餐问题解决方案二
* 奇偶互反
* 奇偶数获取相反的顺序,哲学家的编号从1到5,奇数的哲学家先拿起左手边的筷子,
* 偶数哲学家先拿起右手边的筷子
* @Author:SmallG
* @CreateTime:2023/8/10+15:28
*/
public class PhilosopherThread02 extends Thread {
private Chopsticks left;
private Chopsticks right;
private int index;
public PhilosopherThread02(Chopsticks left, Chopsticks right, int index) {
this.left = left;
this.right = right;
this.index = index;
}
public void run() {
if (index % 2 == 0) {
//偶数科学家拿起右手边的筷子
synchronized (right) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(index + "等待拿起左手边的筷子");
synchronized (left) {
System.out.println(index + "拿到了左手边的筷子开始吃饭!");
}
}
} else {
synchronized (left) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(index + "等待拿起右手边的筷子");
synchronized (right) {
System.out.println(index + "拿到了右手边的筷子开始吃饭!");
}
}
}
}
public static void main(String[] args) {
//创建5根筷子
Chopsticks chopsticks1 = new Chopsticks();
Chopsticks chopsticks2 = new Chopsticks();
Chopsticks chopsticks3 = new Chopsticks();
Chopsticks chopsticks4 = new Chopsticks();
Chopsticks chopsticks5 = new Chopsticks();
//创建五名哲学家
PhilosopherThread02 t1 = new PhilosopherThread02(chopsticks1, chopsticks2, 1);
PhilosopherThread02 t2 = new PhilosopherThread02(chopsticks2, chopsticks3, 2);
PhilosopherThread02 t3 = new PhilosopherThread02(chopsticks3, chopsticks4, 3);
PhilosopherThread02 t4 = new PhilosopherThread02(chopsticks4, chopsticks5, 4);
PhilosopherThread02 t5 = new PhilosopherThread02(chopsticks5, chopsticks1, 5);
//启动线程,哲学家开始就餐
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
线程粗放:
/**
* @document: 哲学家就餐问题解决方案三
* 线程粗放:所有科学家同时拿起左右手的筷子
* @Author:SmallG
* @CreateTime:2023/8/10+15:41
*/
public class PhilosopherThread03 extends Thread {
private Chopsticks left;
private Chopsticks right;
private int index;
private String flag; //标记
public PhilosopherThread03(Chopsticks left, Chopsticks right, int index, String flag) {
this.left = left;
this.right = right;
this.index = index;
this.flag = flag;
}
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (flag) {
System.out.println(index + "同时拿起左右手的筷子,开始吃饭");
}
}
public static void main(String[] args) {
//创建5根筷子
Chopsticks chopsticks1 = new Chopsticks();
Chopsticks chopsticks2 = new Chopsticks();
Chopsticks chopsticks3 = new Chopsticks();
Chopsticks chopsticks4 = new Chopsticks();
Chopsticks chopsticks5 = new Chopsticks();
//创建五名哲学家
PhilosopherThread03 t1 = new PhilosopherThread03(chopsticks1, chopsticks2, 1,"hello");
PhilosopherThread03 t2 = new PhilosopherThread03(chopsticks2, chopsticks3, 2,"hello");
PhilosopherThread03 t3 = new PhilosopherThread03(chopsticks3, chopsticks4, 3,"hello");
PhilosopherThread03 t4 = new PhilosopherThread03(chopsticks4, chopsticks5, 4,"hello");
PhilosopherThread03 t5 = new PhilosopherThread03(chopsticks5, chopsticks1, 5,"hello");
//启动线程,哲学家开始就餐
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
5、API的线程安全
StringBuilder是非线程安全的API,在多线程操作StringBuilder的时候会出现问题。
/**
* @document: 非线程安全的API StringBuilder
* @Author:SmallG
* @CreateTime:2023/8/10+16:13
*/
public class Demo03 {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder();
int number = 3;
//创建一个线程对象
Runnable run = () -> {
for (int i = 0; i < 10000; i++) {
builder.append("A");
}
};
//创建一个集合
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < number; i++) {
Thread t = new Thread(run);
threadList.add(t);
t.start();
}
//等待所有线程完成
for (Thread thread : threadList) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("程序执行完成,字符串的长度是" + builder.length());
}
}
StringBuffer是线程安全的,他的关键方法都使用了synchronized关键字进行同步控制,但是他在性能上没有StringBuilder好。
/**
* @document: StringBuffer和StringBuilder的对比
* @Author:SmallG
* @CreateTime:2023/8/10+16:33
*/
public class Demo05 {
public static void main(String[] args) {
StringBuffer buffer = new StringBuffer();
StringBuilder builder = new StringBuilder();
int num = 100000000;
long startTime = System.currentTimeMillis();
for (int i = 0; i < num; i++) {
buffer.append("a");
}
long endTime = System.currentTimeMillis();
System.out.println("StringBuffer耗时:" + (endTime - startTime)+"毫秒");
startTime =System.currentTimeMillis();
for (int i = 0; i < num; i++) {
builder.append("a");
}
endTime =System.currentTimeMillis();
System.out.println("StringBuilder耗时:" + (endTime - startTime)+"毫秒");
}
}
四、练习
1 编写计时线程
编写一个守护线程程序,每隔5秒钟输出当前的时间,主线程结束后计时完毕。
程序运行效果如下所示:
/**
* @document: 编写计时线程
* @Author:SmallG
* @CreateTime:2023/8/10+16:42
*/
public class DaemonDemo {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.CHINA);
Runnable run = () -> {
LocalTime localTime = LocalTime.now();
String data01 = localTime.format(formatter);
System.out.println(data01);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
localTime = LocalTime.now();
String data02 = localTime.format(formatter);
System.out.println(data02);
System.out.println("main线程结束了");
};
Thread t = new Thread(run);
t.start();
}
}
2 【简答题】线程的创建方式
请介绍一下在Java中创建线程的方式有几种,它们有什么区别?
1、继承 Thread 类:
- 创建一个类,继承 Thread 类,并重写 run() 方法,定义线程的执行逻辑。
- 调用子类的 start() 方法启动线程。
- 优点:简单直观,适用于简单的线程任务。
- 缺点:由于Java不支持多重继承,如果一个类已经继承了其他类,就不能再继承 Thread 类。
2、实现 Runnable 接口:
- 创建一个类,实现 Runnable 接口,并实现 run() 方法,定义线程的执行逻辑。
- 创建一个 Thread 对象,将实现了 Runnable 接口的对象作为参数传入。
- 调用 Thread 对象的 start() 方法启动线程。
- 优点:避免了Java单继承的限制,可以更灵活地处理线程任务。
- 缺点:相对于继承 Thread 类,稍微复杂一些。
3 【简答题】run方法和start方法的区别
请介绍一下Java中Thread类的run方法和start方法有什么区别?
在Java中, Thread 类中的 run() 方法和 start() 方法是创建和启动线程的两个重要方法,它们的区别如下:
1、run() 方法:
- run() 方法是 Thread 类中的一个普通方法,用于定义线程的执行逻辑。
- 在使用继承 Thread 类创建线程时,需要重写 run() 方法,将线程要执行的代码逻辑写在 run() 方法中。
- 当调用 Thread 对象的 start() 方法时,会启动新的线程,并在新的线程中调用 run() 方法执行线程的逻辑。
- 在单线程环境中,直接调用 run() 方法并不会启动一个新线程,而是在当前线程中执行 run() 方法的逻辑。
2、start() 方法:
- start() 方法是 Thread 类中的一个特殊方法,用于启动一个新的线程。
- 当调用 Thread 对象的 start() 方法时,会创建一个新的线程,并在新的线程中调用 run() 方法执行线程的逻辑。
- 在多线程环境中,start() 方法会启动新线程并与当前线程并行执行,而不会阻塞当前线程。
综上,run() 方法是用于定义线程的执行逻辑,而 start() 方法是用于启动一个新线程并执行 run() 方法的逻辑。只有通过 start() 方法启动的线程才能真正实现多线程并发执行。
4 两线程打印奇偶数
请设计一个程序,程序开始后会启动2个子线程,一个子线程负责输出10以内的奇数,另一个子线程负责输出10以内的偶数,要求两个线程是按照先后顺序逐步输出。
提示:
1、可以通过sleep()方法和interrupt()方法控制线程执行顺序
2、可以通过继承Thread类的方式,为线程类添加新的属性,来保存该线程对象需要用到的属性
程序运行效果如下所示:
/**
* @document: 两线程打印奇偶数
* @Author:SmallG
* @CreateTime:2023/8/10+19:01
*/
public class OddAndEven {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 1; i < 10; i += 2) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
TimeUnit.MILLISECONDS.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "奇数线程");
Thread t2 = new Thread(() -> {
for (int i = 2; i <= 10; i += 2) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
TimeUnit.MILLISECONDS.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "偶数线程");
t1.start();
TimeUnit.SECONDS.sleep(1);
t2.start();
}
}