01. 多线程实现代码的回顾
/*
多线程的实现方式
1. 定义一个类,然后这个类继承Thread
2. 在这个类中重写Thread类的run方法,并在run方法中定义线程要执行的任务。
3. 创建这个Thread子类对象。
4. 通过Thread子类对象调用start方法,启动线程,线程会执行run方法。
*/
public class Demo01Thread {
public static void main(String[] args) {
System.out.println("main...start");
//创建这个Thread子类对象。
MyThread mt = new MyThread();
//通过Thread子类对象调用start方法
mt.start();
//输出100行HelloWorld
for(int i = 0; i < 100; i++) {
System.out.println("HelloWorld:" + i);
}
}
}
public class MyThread extends Thread{
public void run() {
//输出
for(int i = 0; i < 100; i++) {
System.out.println("HelloJava:" + i);
}
}
}
02. 多线程的执行流程
参考 Java基础学习14:【线程、同步、线程状态】中的图文章源自JAVA秀-https://www.javaxiu.com/734.html
03. 多线程的内存图
参考 Java基础学习14:【线程、同步、线程状态】中的图文章源自JAVA秀-https://www.javaxiu.com/734.html
04. Thread中的常见方法
/*
Thread中的常见方法:
构造方法:
Thread(): 空参数构造方法。
Thread(String name): 参数需要一个字符串,这个字符串表示线程的名字
其他方法:
String getName(): 获取线程的名字。
void setName(String name): 设置线程名字
static Thread currentThread(): 获取当前的线程对象。
static void sleep(long millis): 线程休眠, 参数为休眠的毫秒值。
*/
public class Demo01ThreadMethod {
public static void main(String[] args) {
//创建一个MyThread对象
MyThread m = new MyThread("王叔叔");
//void setName(String name): 设置线程名字
//m.setName("狗蛋");
m.start();
//new MyThread().start();
//new MyThread().start();
//new MyThread().start();
//static Thread currentThread(): 获取当前的线程对象。 该方法是通过哪个线程调用的,那么 获取到的就是哪个线程对象。
Thread t = Thread.currentThread();
System.out.println("main线程的名字是:" + t.getName());
}
}
public class MyThread extends Thread{
//提供一个构造方法,在这个构造方法中调用Thread一个参数是字符串的构造方法
public MyThread(String name) {
//Thread(String name): 参数需要一个字符串,这个字符串表示线程的名字
super(name);
}
public void run() {
////String getName(): 获取线程的名字。
System.out.println(getName() + "执行了");
}
}
/*
static void sleep(long millis): 线程休眠, 参数为休眠的毫秒值。
*/
public class Demo02Sleep {
public static void main(String[] args) throws InterruptedException {
System.out.println("开始");
//让线程休眠5秒钟
Thread.sleep(2000);
System.out.println("结束");
}
}
05. 多线程的第二种实现方式
/*
多线程的第二种实现方式
1. 定义一个类,然后实现Runnable接口。
2. 重写Runnable接口中的run方法,要在run方法中定义线程要执行的任务。
3. 创建Runnable实现类对象。
4. 创建Thread线程对象, 并且将Runnable实现类对象作为参数传递。
5. 调用线程对象的start方法,启动线程。
*/
public class Demo01RunnableTest {
public static void main(String[] args) {
//创建Runnable实现类对象。
//MyTask并不是线程类,所以创建的并不是线程对象,MyTask表示的是线程要执行的任务。
MyTask mt = new MyTask();
//创建Thread线程对象, 并且将Runnable实现类对象作为参数传递。
Thread t = new Thread(mt);//传递mt表示将来线程执行的是mt中的run方法
//调用线程对象的start方法,启动线程。
t.start();
//在main线程中输出HelloWorld
for(int i = 0; i < 100; i++) {
System.out.println("main线程在输出HelloWorld:" + i);
}
}
}
/*
MyTask继承了Runnable,但是MyTask他不是一个线程类。
在Java中,Thread才表示线程类, 而这个MyTask和Thread没有关系,所以不是线程类。
这个MyTask可以看成线程任务类, 因为实现了Runnable接口,Runnable接口中只有一个run方法,表示线程 要执行的任务。
*/
public class MyTask implements Runnable{
//重写run方法,在run方法中定义线程要执行的任务。
public void run() {
//输出100行HelloJava
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"输出HelloJava:" + i);
}
}
}
06. 多线程第二种实现方式的好处
第二种方式更好: 1. 解决了Java中类与类之间只能单继承的局限性。 2. 降低了耦合性(关联性, 降低了类与类之间的关联) 3. Runnable接口中只有一个run方法,没有start,getName,sleep这些方法, Runnable接口的功 能更加纯粹,我们只需要在里面关注线程要执行的任务就可以了, 在设计模式中有一个原则是单一职责 原则, 实现接口的方式更加符合这个单一职责原则。 4. 更加有利于实现资源的共享。
07. 匿名内部类实现多线程
/*
匿名内部类: 临时定义某个子类,并创建该子类的对象。
格式:
new 父类或接口() {
//要重写的方法
}
举例:
new Person() {
}
这个代码创建的并不是Person对象,创建的是Person子类的对象, 但是这个子类到底是谁,我们不知 道,因为他是匿名的。
*/
public class Demo01Inner {
public static void main(String[] args) {
//继承Thread类 + 匿名内部类 完成多线程。
Thread t = new Thread() {
public void run() {
System.out.println(Thread.currentThread().getName() + "线程执行了");
}
};
//调用start方法,启动线程
t.start();
//继承Thread类 + 匿名内部类 + 匿名对象完成多线程
new Thread() {
public void run() {
System.out.println(Thread.currentThread().getName() + "线程执行了");
}
}.start();
//实现Runnable接口 + 匿名内部类实现多线程
Runnable r = new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "线程执行了");
}
};
new Thread(r).start();
//实现Runnable接口 + 匿名内部类 + 匿名对象的方式实现多线程。
new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "线程执行了");
}
}).start();
}
}
08. 卖票案例引发的线程安全问题
/*
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个
(本场电影只能卖100张票)。
我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)
需要窗口,采用线程对象来模拟;需要票,Runnable接口实现类来模拟
我们使用三个线程一起卖票,一起卖100张票。
*/
public class Demo01TicketTest {
public static void main(String[] args) {
//创建Ticket对象
Ticket t = new Ticket();
//创建三个线程,执行卖票任务
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
/*
如果多个线程同时操作共享数据那么就有可能引发线程安全问题。
*/
public class Ticket implements Runnable{//因为Ticket实现了Runnable接口,所以Ticket也表示线程要执行的任务。
//定义变量,表示票的数量
int num = 100;
//在run方法中定义线程要执行的任务, 线程要执行的任务是卖票。 只要有票,那么就一直卖。
public void run() {
//因为线程要一直卖票,所以定义死循环
while(true) {
//进行判断,如果票的数量大于0,那么表示有票,那么就卖票
if(num > 0) {
//磨磨唧唧,掏身份证,用了20毫秒
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" + num);
num--;
}
}
}
}
09. 线程安全问题出现的原因
参考 Java基础学习14:【线程、同步、线程状态】中的图文章源自JAVA秀-https://www.javaxiu.com/734.html
10. 同步代码块解决线程安全问题以及注意事项
/*
synchronized 叫做同步,我们可以使用这个关键字来解决线程安全问题。
synchronized可以修饰代码块,也可以修饰方法。
如果synchronized修饰代码块,那么它就叫做同步代码块。
格式;
synchronized(锁对象) {
//代码块内容
}
锁对象就是一个标记,没有其他作用,
锁对象可以是任何对象, 可以是String,可以是ArrayList或者Person或者Student....
作用:
同步代码块可以保证只有持有锁的线程才能够进入到这个代码块中。
同步代码块虽然可以保证线程安全,但是会牺牲效率。
*/
public class Demo02TicketTest {
public static void main(String[] args) {
//创建Ticket2对象
Ticket2 t = new Ticket2();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
public class Ticket2 implements Runnable{
int num = 100;
//创建一个对象,表示锁对象。 该对象仅仅起到一个标记的作用。
//锁对象一定要是唯一的, 多个线程用的必须是同一个锁
Object lock = new Object();
public void run() {
while(true) {
//当一个线程执行到synchronized同步代码块时,会先看一下这个同步代码块上面还有没有锁。
//如果这个同步代码块上面还有锁, 那么这个线程就会获取到这个锁,然后进入到同步代码块。
//如果这个同步代码块上面没有锁, 那么这个线程会一直在同步代码最开始位置等着, 什么时候有 了,什么时候才可能进去。
synchronized(lock) {
if(num > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" + num);
num--;
}
}
//当线程离开同步代码块, 那么线程会释放掉锁。
//释放掉锁之后,其他线程就还可以去竞争这个锁,哪个线程抢到,那么哪个线程就可以进入到同步代 码块执行。
}
}
}
11. 同步代码块解决线程安全问题的分析
参考 Java基础学习14:【线程、同步、线程状态】中的图文章源自JAVA秀-https://www.javaxiu.com/734.html
12. 同步上厕所的原理
参考 Java基础学习14:【线程、同步、线程状态】中的图文章源自JAVA秀-https://www.javaxiu.com/734.html
13. 同步方法解决线程安全问题
/*
如果synchronized修饰方法,那么这个方法就是一个同步方法,
同步方法也可以解决线程安全问题。
格式:
修饰符 synchronized 返回值类型 方法名(参数列表) {
方法体;
}
同步方法相当于把整个方法体都加了同步代码块。
同步方法也是有锁的
如果这个方法是非静态的, 那么锁对象是this
如果这个方法是静态的,那么锁对象是类名.class(字节码对象,反射讲解)
同步方法和同步代码块都可以解决线程安全问题:
同步代码块的好处: 使用起来更加灵活。
同步方法的好处:语法简洁。
*/
public class Demo03TicketTest {
public static void main(String[] args) {
//创建Ticket3对象
Ticket3 t = new Ticket3();
//创建线程,并卖票
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
public class Ticket3 implements Runnable{
int num = 100;
Object lock = new Object();
public void run() {
while(true) {
sell();
}
}
//定义方法,用来卖票
public void sell() {
synchronized(this) {
if(num > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" +num);
num--;
}
}
}
//如果一个方法的整个方法体都加了同步代码块,那么我们就可以使用同步方法表示,
public synchronized void sell2() {
if (num > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" + num);
num--;
}
}
}
14. Lock接口解决线程安全问题
/*
在JDK5之后,多了一个Lock接口, 里面提供了一些方法可以手动的获取锁以及释放锁。
void lock(): 获取锁。
void unlock(): 释放锁。
Lock接口常见的是类是 ReentrantLock
*/
public class Demo04TicketTest {
public static void main(String[] args) {
//创建Ticket4对象
Ticket4 t = new Ticket4();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
public class Ticket4 implements Runnable{
int num = 100;
//创建Lock对象,用来获取锁和释放锁
Lock lock = new ReentrantLock();
public void run() {
while(true) {
//获取锁
lock.lock(); //通过lock对象调用lock方法获取锁
if(num > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖票:" + num);
num--;
}
//释放锁
lock.unlock();//通过lock对象调用unlock手动释放锁
}
}
}
15. 线程状态的介绍
/*
线程的状态
新建(NEW): 刚刚创建出来的线程对象处于此状态
运行(RUNNABLE): 正在运行的线程处于此状态。(调用start方法后会变成运行)
受阻塞(BLOCKED):等待获取锁的线程处于此状态。
无限等待(WAITING): 当调用了wait()方法后,线程会变成无限等待。
计时等待(TIMED_WAITING): 当调用sleep(毫秒值),wait(毫秒值)方法后会进入到计时等待
退出(TERMINATED): 当线程的run方法执行完了,或者调用了线程的stop方法,线程会变成退出状态。
*/
public class Demo01ThreadState {
}
16. 线程的状态图
参考 Java基础学习14:【线程、同步、线程状态】中的图文章源自JAVA秀-https://www.javaxiu.com/734.html
17. wait以及notify方法的介绍
/*
wait可以让线程等待, notify可以唤醒等待的线程,
这两个方法一般用于线程通信, 或者称为等待唤醒机制。
wait和notify方法是属于Object的,而不是Thread
void wait(): 让线程等待,直到其他线程唤醒它。
void wait(long timeout): 让线程等待, 知道其他线程唤醒它或者参数指定的时间(毫秒值)已过。
void notify(): 唤醒一个线程。
void notifyAll(): 唤醒所有的线程。
上面的方法不能通过对象直接调用, 上面代码要放在同步代码中, 并且要通过锁对象进行调用。
通过那个锁调用的notify方法,那么唤醒的就是通过哪个锁调用wait方法等待的线程。
*/
18. 等待唤醒机制案例的分析
参考 Java基础学习14:【线程、同步、线程状态】中的图文章源自JAVA秀-https://www.javaxiu.com/734.html
public class Demo02Thread {
public static void main(String[] args) throws InterruptedException {
//创建包子对象
BaoZi baoZi = new BaoZi();
//创建包子铺对象
BaoZiPu baoZiPu = new BaoZiPu(baoZi);
//创建吃货对象
ChiHuo chiHuo = new ChiHuo(baoZi);
//新建线程并执行了。
new Thread(baoZiPu).start();
new Thread(chiHuo).start();
}
}
public class BaoZiPu implements Runnable{
//因为包子铺要用到包子,并且包子铺用的包子要和吃货用的包子是同一个包子,所以可以在成员位置定义 BaoZi, 并且将来再 从外界传递过来一个包子
BaoZi baoZi;
//定义构造方法,参数接收一个外界传递过来的BaoZi对象
public BaoZiPu(BaoZi baoZi) {
this.baoZi = baoZi;
}
//我们可以在run方法中定义包子铺要执行的任务。 包子铺要执行的任务是生产包子,并且等着吃货吃包子。
public void run() {
//因为包子铺要一直生产包子,那么所以可以使用死循环
while(true) {
//因为包子铺和吃货操作的是同一个包子,所以要加同步,保证线程安全
synchronized (baoZi) {//因为包子铺和吃货操作的是同一个包子,所以包子是唯一的,可以把 包子当成锁对象。
//判断当前有没有包子, 如果有包子, 那么要等待吃货吃掉这个包子
if(baoZi.flag) {
//条件成立表示有包子,那么包子铺就等待.
try {
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有包子,如果没有包子,那么包子铺就生产包子
System.out.println("包子铺生产了一个包子");
//把标记改为true表示有包子了
baoZi.flag = true;
//通知吃货吃包子。
baoZi.notify();
}
}
}
}
public class ChiHuo implements Runnable{
//吃货需要操作包子,并且操作的包子和包子铺生产的包子是同一个,所以这个包子对象要通过外界传递过来
BaoZi baoZi;
public ChiHuo(BaoZi baoZi) {
this.baoZi = baoZi;
}
//我们要在run方法中定义吃货要执行的任务,吃货执行的任务是吃包子,并且等着包子铺生产包子。
public void run() {
//因为吃货要一直吃包子,所以可以使用死循环。
while(true) {
//因为吃货吃的包子和包子铺操作的是同一个包子, 多个线程操作共享数据,需要加同步保证线程安 全。
synchronized (baoZi) {
//判断有没有包子,如果没有包子, 那么吃货就要等待
if(!baoZi.flag) {
//如果条件成立,表示没有包子,那么我们就让吃货等待
try {
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有包子,那么吃货就吃包子
System.out.println("吃货了一个包子");
//把标记改为false,表示没有包子了.
baoZi.flag = false;
//调用notify方法唤醒包子铺,通知包子铺生产包子
baoZi.notify();
}
}
}
}
public class BaoZi {
//定义变量,表示包子是否存在
boolean flag = false;
}
20. 等待唤醒机制的执行流程分析
参考 Java基础学习14:【线程、同步、线程状态】中的图文章源自JAVA秀-https://www.javaxiu.com/734.html
继续阅读
速蛙云 - 极致体验,强烈推荐!!!购买套餐就免费送各大视频网站会员!快速稳定、独家福利社、流媒体稳定解锁!速度快,全球上网、视频、游戏加速、独立IP均支持!基础套餐性价比很高!这里不多说,我一直正在使用,推荐购买:https://www.javaxiu.com/59919.html

资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
评论