3.1_15 JavaSE入门 P14 【多线程】同步、匿名内部类、死锁、生命周期
相关链接
- Excel目录
目录
- P14 【进阶】多线程、同步、匿名内部类、死锁、生命周期
- 1 概述
- 1.1 并行&并发
- 1.2 主方法是多线程的吗?
- 2 多线程实现
- 2.1 方式一:继承Thread类
- 2.1.1 细节及注意事项
- 2.1.2 Thread类的成员
- 2.2 方式二:实现Runnable接口
- 2.3 方式三:结合线程池使用(实现Callable接口)
- 3 多线程安全问题&解决方案
- 3.1 多线程案例(模拟买票)
- 3.1.1 继承Thread类方式
- 3.1.2 实现Runnable接口方式
- 3.2 多线程安全问题解决
- 3.2.1 使用同步代码块
- 3.2.1.1 继承Thread类方式
- 3.2.1.2 实现Runnable接口方式
- 4 匿名内部类
- 4.1 三种实现方式
- 4.2 开发中的应用
- 5 同步
- 5.1 同步代码块
- 5.2 同步方法
- 5.2.1 静态方法锁对象
- 5.2.2 非静态方法锁对象
- 6 多线程的难点
- 6.1 死锁
- 6.2 多线程的生命周期
- 7.面试题
P14 【进阶】多线程、同步、匿名内部类、死锁、生命周期
- 今日要掌握的内容:
- 1.【应用】多线程概述 & 多线程实现
- a.【理解】能够阐述进程与线程的概念
- b.【应用】能够独立写出线程的两种实现方式
- c.【理解】能够阐述两种线程实现方式的优缺点
- 2.【理解】多线程安全问题产生 & 解决方案
- a.【应用】能够分析多线程共享资源产生的安全问题
- b.【应用】能够使用同步代码块解决多线程的安全问题
- c.【应用】能够使用同步方法解决多线程的安全问题
- d.【应用】匿名内部类练习
1 概述
- 进程 (可执行文件, 程序(例如: .exe)
- 进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。
- 一个程序运行后至少有一个进程,进程有多条执行路径, 合称为: 多线程
- 简单理解为:
车
- 线程 进程的执行路径(执行单元)
- 线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线的,这个应用程序也可以称之为多线程程序。
- 8核CPU:等于有八个线程可以同时运行各类进程
- 简单理解为:
车道
- 单线程
- 进程只有一条执行路径,这种执行方式叫单线程
- 简单理解为:
一辆车在一条车道上跑
- 多线程
- 一个程序运行后至少有一个进程,进程有多条执行路径, 合称为: 多线程
案例代码一 多线程概述
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/12* @introduction 多线程概述** main:主线程* system.out:标准的输出流(黑色字体)。 可以理解为:这是一个线程。* system.err:标准的错误流(红色字体)。 可以理解为:这也是一个线程。** 1.一台电脑上可以有多个进程,这些进程之间的数据是相互隔离的* //例如:qq.exe wechat.exe* 2.一个进程可以有多条线程,* //例如:往QQ群共享放一个文件,该群中的所有用户都可以下载**/
public class Demo1MultiThread {public static void main(String[] args) {System.out.println(1);System.out.println(2);System.out.println(3);System.err.println(4);System.err.println(5);System.out.println(6);System.out.println(1/0);//多次执行结果顺序不相同,因为out和err是两个线程,两个线程在抢夺资源,谁抢到资源谁就先执行程序/*45Exception in thread "main" java.lang.ArithmeticException: / by zeroat com.groupies.base.day14.a.Demo01_多线程概述.main(Demo01_多线程概述.java:27)1236*//*123645Exception in thread "main" java.lang.ArithmeticException: / by zeroat com.groupies.base.day14.a.Demo01_多线程概述.main(Demo01_多线程概述.java:29)*/}
}
1.1 并行&并发
- 并行
- 两个(多个)线程同时执行. (提前: 需要多核CPU)
- 并发
- 两个(多个)线程同时请求执行, 但是同一瞬间, CPU只能执行一个
- 于是就安排它们交替执行, 因为时间间隔非常短, 我们看起来好像是同时执行的, 其实不是
多线程并行和并发的区别
1.2 主方法是多线程的吗?
主方法是由JVM虚拟机调用的,执行顺序为:从上到下、从左到右。
案例代码二 多线程实现方式一 继承Thread类 【a.线程类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/11* @introduction 主方法中代码的执行是多线程的吗?** 测试方法:* 如果主方法是单线程的,则进入method死循环方法无法进入到下一步function方法* 如果主方法是多线程的,则进入method死循环方法不影响其他线程进入下一步的function方法* 测试结果:* 未进入function方法,程序循环打印method方法内容* 结论:* 主方法是单线程的*/
public class Demo2MainThreadTest {public static void main(String[] args) throws InterruptedException {/**///测试类1method();//测试类2function();}/*** @introduction 测试类1*/public static void method() throws InterruptedException {while (true){Thread.sleep((long) 500.00);System.out.println("method");}}/*** @introduction 测试类2*/public static void function() throws InterruptedException {while (true){Thread.sleep((long) 500.00);System.out.println("function");}}
}
2 多线程实现
- 多线程的实现方式
- 方式一:继承Thread类
- 方式二:实现Runnable接口
- 方式三:结合线程池使用(实现Callable接口) — 了解即可
2.1 方式一:继承Thread类
该如何创建线程呢?通过API中搜索,查到Thread类。通过阅读Thread类中的描述。Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
1 创建线程的步骤
- 1)定义一个类(MyThread)继承Thread;
- 2)重写Thread#run()方法;
- 3)把要执行的代码放入run()方法;
- 4)在测试类中,创建线程对象
- 5)调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法
- 6)CPU执行程序的随机性
- 7)统一线程不能重复开启,否则会报IllegalThreadStateException异常
2 多线程的实现方式
方式一:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
//Thread //返回该线程的名称。 String getName() //改变线程名称,使之与参数 name 相同。 void setName(String name)
3 CPU执行程序的随机性
案例代码三 多线程实现方式一 继承Thread类 【a.线程类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/11* @introduction 多线程实现方式一 继承Thread类 线程类** 创建线程的步骤:* 1) 定义一个类(MyThread)继承Thread;* 2) 重写Thread#run()方法;* 3) 把要执行的代码放入run()方法;* 4) 在测试类中,创建线程对象* 5) 调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法* 6) CPU执行程序的随机性* 7) 统一线程不能重复开启,否则会报IllegalThreadStateException异常** 多线程的实现方式:* 方式1:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例** Thread* String getName() 返回该线程的名称。* void setName(String name) 改变线程名称,使之与参数 name 相同*/
//1)定义一个类(MyThread)继承Thread;
public class Demo3MyThread extends Thread {//2) 重写Thread#run()方法;@Override/*** @introduction 该线程要执行的操作,打印10次指定内容*/public void run() {for (int i = 0; i < 10; i++) {//3) 把要执行的代码放入run()方法;System.out.println(getName() + ":" + i);}}
}
案例代码三 多线程实现方式一 继承Thread类 【b.测试类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/11* @introduction 多线程实现方式一 测试类** 创建线程的步骤:* 1) 定义一个类(MyThread)继承Thread;* 2) 重写Thread#run()方法;* 3) 把要执行的代码放入run()方法;* 4) 在测试类中,创建线程对象* 5) 调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法* 6) CPU执行程序的随机性* 7) 统一线程不能重复开启,否则会报IllegalThreadStateException异常** 多线程的实现方式:* 方式1:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例** Thread* String getName() 返回该线程的名称。* void setName(String name) 改变线程名称,使之与参数 name 相同。*/
public class Demo3MyThreadTest {public static void main(String[] args) {//4) 在测试类中,创建线程对象Demo3MyThread mt = new Demo3MyThread();Demo3MyThread mt2 = new Demo3MyThread();//修改线程名字mt.setName("张三");mt2.setName("李四");//如果调用run()方法,只是普通的方法调用,并不会以多线程方式运行/*张三:0张三:1张三:2张三:3张三:4张三:5张三:6张三:7张三:8张三:9李四:0李四:1李四:2李四:3李四:4李四:5李四:6李四:7李四:8李四:9*/mt.run();mt2.run();System.out.println("==========================");//5) 调用start方法,开启线程必须调用start()方法,该方法会自动去调用run()方法mt.start();mt2.start();//6) CPU执行程序的随机性/*张三:0张三:1李四:0李四:1李四:2李四:3李四:4李四:5张三:2张三:3张三:4张三:5张三:6张三:7张三:8张三:9李四:6李四:7李四:8李四:9*///7)统一线程不能重复开启,否则会报IllegalThreadStateException异常try {mt.start();} catch (IllegalThreadStateException e) {//e.printStackTrace();System.out.println("IllegalThreadStateException: 非法线程状态异常");}}
}
2.1.1 细节及注意事项
Q:为什么同样的代码,在有的电脑上执行,可能结果看着还是像单线程的?
A:有些电脑的CPU机制对多线程任务进行优化(常见联想、小米电脑)
先判断单核CPU线程量有多大,如果优化机制判断当前进程仅需一个CPU即可完成,则会使用单个CPU处理多线程任务
张三:0
张三:1
张三:2
张三:3
张三:4
张三:5
张三:6
张三:7
张三:8
张三:9
李四:0
李四:1
李四:2
李四:3
李四:4
李四:5
李四:6
李四:7
李四:8
李四:9
2.1.2 Thread类的成员
构造方法:public Thread();public Thread(String name);public Thread(Runnable target);public Thread(Runnable target,String name);成员方法:run(); //里边定义的是线程要执行的代码,该方法会自动被start()方法调用start(); //开启线程,会自动调用run();getName();setName(); sleep(); //休眠线程,单位是:毫秒(1s=1000ms)currentThread; //获取当前正在执行的线程对象(的引用)
2.2 方式二:实现Runnable接口
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。
为何要实现Runnable接口,Runable是啥玩意呢?继续API搜索。
查看Runnable接口说明文档:Runnable接口用来指定每个线程要执行的任务。包含了一个 run 的无参数抽象方法,需要由接口实现类重写该方法。
创建线程的步骤
1)定义一个类(MyRunnableThread),实现Runnable接口;
2)重写Runnable#run()方法;
3)把要执行的代码放入run()方法中;
4)创建Runnable接口的子类对象;
MyRunnableThread mrt = new MyRunnableThread();
5)并将其作为参数传入Thread类的构造,创建线程对象
Thread th = new Thread(mrt);
6)开启线程
th.start();
案例代码四 多线程实现方式一 实现Runnable接口 【a.Runnable子类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/11* @introduction 多线程实现方式二 实现Runnable接口 Runnable实现类** 可以理解为:Thread类的资源类** 创建线程的步骤。* 1) 定义一个类(MyRunnableThread),实现Runnable接口;* 2) 重写Runnable#run()方法;* 3) 把要执行的代码放入run()方法中;* 4) 创建Runnable接口的子类对象;* MyRunnableThread mrt = new MyRunnableThread();* 5) 并将其作为参数传入Thread类的构造,创建线程对象* Thread th = new Thread(mrt);* 6) 开启线程* th.start();** Thread.currentThread().getName()* Thread.currentThread() 得到当前thread对象* Thread.currentThread().getName() 得到当前thread对象的名称*/
//1) 定义一个类(MyRunnableThread),实现Runnable接口;
public class Demo4MyRunnableThread implements Runnable {//姓名String name;/*** @introduction 带参构造* @param name 姓名*/public Demo4MyRunnableThread(String name) {this.name = name;}@Override/*** @introduciton 2、覆盖接口中的run方法 (Runnable接口的实现类必须实现run方法)*/public void run() {for (int i = 0; i < 10; i++) {//Thread t = Thread.currentThread();//System.out.println(t.getName() + ":" + i);//链式编程System.out.println(Thread.currentThread().getName() + ":" + name + i);}}
}
案例代码四 多线程实现方式一 实现Runnable接口 【b.测试类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/12* @introduction 多线程实现方式二 实现Runnable接口 测试类** 创建线程的步骤。* 1) 定义一个类(MyRunnableThread),实现Runnable接口;* 2) 重写Runnable#run()方法;* 3) 把要执行的代码放入run()方法中;* 4) 创建Runnable接口的子类对象;* MyRunnable mr = new MyRunnable();* 5) 并将其作为参数传入Thread类的构造,创建线程对象* Thread th = new Thread(mr);* 6) 开启线程* th.start();**/
public class Demo4MyRunnableThreadTest {public static void main(String[] args) throws InterruptedException {//4、将Runnable接口的子类对象作为参数传递给Thread类的构造函数Demo4MyRunnableThread mrt1 = new Demo4MyRunnableThread("张三");Demo4MyRunnableThread mrt2 = new Demo4MyRunnableThread("李四");Demo4MyRunnableThread mrt3 = new Demo4MyRunnableThread("林青霞");Demo4MyRunnableThread mrt4 = new Demo4MyRunnableThread("张曼玉");//3、创建Thread类的对象//5、调用Thread类的start方法开启线程//将Runnable接口的子类对象作为参数传递给Thread类的构造函数时,多线程方式new Thread(mrt1).start();new Thread(mrt2).start();/*Thread-0:张三0Thread-1:李四0Thread-1:李四1Thread-1:李四2Thread-0:张三1Thread-1:李四3Thread-1:李四4Thread-0:张三2Thread-1:李四5Thread-0:张三3Thread-1:李四6Thread-0:张三4Thread-1:李四7Thread-0:张三5Thread-1:李四8Thread-0:张三6Thread-1:李四9Thread-0:张三7Thread-0:张三8Thread-0:张三9*/Thread.sleep(500);/*main:林青霞0main:林青霞1main:林青霞2main:林青霞3main:林青霞4main:林青霞5main:林青霞6main:林青霞7main:林青霞8main:林青霞9main:张曼玉0main:张曼玉1main:张曼玉2main:张曼玉3main:张曼玉4main:张曼玉5main:张曼玉6main:张曼玉7main:张曼玉8main:张曼玉9*///如果调用run()方法,只是普通的方法调用,并不会以多线程方式运行mrt3.run();mrt4.run();}
}
2.3 方式三:结合线程池使用(实现Callable接口)
暂略
3 多线程安全问题&解决方案
- 出现的问题
- 出现负数
- 出现重复值
- 解决方案
- 采用同步代码块解决
3.1 多线程案例(模拟买票)
- 需求
- 四个窗口,卖100张票
- 买票动作的思路
- A:如果当前票数大于0则继续销售
- B:为了加大出现错误的概率,我们加入:休眠线程 1s = 1000ms
- C:如果有票,就正常的卖票即可
- 第x张票,出现负数票的原因: while判断 + 休眠线程
- 假设现在是最后一张票了,tickets的值应该是1,此时
- 如果线程1抢到了资源,线程1休眠,此时还没有进入卖票的步骤
- 此时线程2,线程3…也抢到了资源,由于票还没有卖出去,所以tickets为1
- 线程2,线程3…可以正常通过while判断,也进入休眠的流程
- 休眠时间结束,程序继续运行
- 假设线程1先抢到票,打印:正在出售1号票,然后会把tickets的值改为:0
- 假设线程2后抢到票,打印:正在出售0号票,然后会把tickets的值改为:-1
- 假设线程3后抢到票,打印:正在出售-1号票,然后会把tickets的值改为:-2
- 假设线程4后抢到票,打印:正在出售-2号票,然后会把tickets的值改为:-3
- 第x张票,出现重复值的原因:tickets–
- tickets-- 相当于 tickets = tickets - 1
- tickets-- 做了3件事:
- A:读值,读取tickets的值
- B:改值,将tickets的值 - 1
- C:赋值,将修改后的值重新赋值给tickets
- 还没有来得及执行C的动作,此时别的线程抢走资源了,就会出现重复值
3.1.1 继承Thread类方式
案例代码五 模拟买票的实现 【a.线程类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/13* @introduction 模拟买票的实现 线程类** 思路:* 1.因为是四个窗口同时卖票,通过多线程卖票(extends Thread)* 2.定义一个静态变量(tickets),记录票数* 3.需要区分不同窗口,所以要指定每个线程名称* 4.因为是四个窗口,所以需要创建四个线程对象 给线程自定义名字* 5.开启线程* 6.让负数票几率变大->* 6.1 增加票数 不好找* 6.2 每次卖票前线程sleep();**/
//2.因为是四个窗口同时卖票,通过多线程卖票(extends Thread)
public class Demo5TicketSellMyThread extends Thread {//1.定义一个变量(tickets),记录票数private static int tickets = 100;//因为是共享数据,所以用static修饰/*** @introduction 3.需要区分不同窗口,所以要指定每个线程名称* @param window 窗口名称*/public Demo5TicketSellMyThread(String window) {super(window);}@Overridepublic void run() {/** 买票动作的思路* A:如果当前票数大于0则继续销售* B:为了加大出现错误的概率,我们加入:休眠线程 1s = 1000ms* C:如果有票,就正常的卖票即可*///A:如果当前票数大于0则继续销售while (tickets > 0) {//B 每次卖票前线程sleep();try {sleep(100 );} catch (InterruptedException e) {e.printStackTrace();}//C:如果有票,就正常的卖票即可/*** 不指定范围,默认在this范围内查找getName方法,当前类找不到则会进入super(父类)继续寻找,如果还找不到则会报错*///System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets-- + "张票");//System.out.println(super.getName() + ":正在出售第" + tickets-- + "张票");//System.out.println(this.getName() + ":正在出售第" + tickets-- + "张票");System.out.println(getName() + ":正在出售第" + tickets-- + "张票");/** 第x张票,出现负数票的原因: while判断 + 休眠线程* 假设现在是最后一张票了,tickets的值应该是1,此时* 如果线程1抢到了资源,线程1休眠,此时还没有进入卖票的步骤* 此时线程2,线程3..也抢到了资源,由于票还没有卖出去,所以tickets为1* 线程2,线程3..可以正常通过while判断,也进入休眠的流程** 休眠时间结束,程序继续运行* 假设线程1先抢到票,打印:正在出售1号票,然后会把tickets的值改为:0* 假设线程2后抢到票,打印:正在出售0号票,然后会把tickets的值改为:-1* 假设线程3后抢到票,打印:正在出售-1号票,然后会把tickets的值改为:-2* 假设线程4后抢到票,打印:正在出售-2号票,然后会把tickets的值改为:-3*//** 第x张票,出现重复值的原因:tickets--* tickets-- 相当于 tickets = tickets - 1* tickets-- 做了3件事:* A:读值,读取tickets的值* B:改值,将tickets的值 - 1* C:赋值,将修改后的值重新赋值给tickets* 还没有来得及执行C的动作,此时别的线程抢走资源了,就会出现重复值*/}}
}
案例代码五 模拟买票的实现 【b.测试类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/13* @introduction 模拟买票的实现 测试类*/
public class Demo5TicketSellMyThreadTest {public static void main(String[] args) {//测试:卖票的动作//4.因为是四个窗口,所以需要创建四个线程对象 给线程自定义名字Demo5TicketSellMyThread mt1 = new Demo5TicketSellMyThread("窗口1");Demo5TicketSellMyThread mt2 = new Demo5TicketSellMyThread("窗口2");Demo5TicketSellMyThread mt3 = new Demo5TicketSellMyThread("窗口3");Demo5TicketSellMyThread mt4 = new Demo5TicketSellMyThread("窗口4");//5.开启线程mt1.start();mt2.start();mt3.start();mt4.start();/*** ...* 窗口3:正在出售第3张票* 窗口1:正在出售第1张票* 窗口3:正在出售第1张票* 窗口4:正在出售第0张票* 窗口2:正在出售第-1张票*/}
}
3.1.2 实现Runnable接口方式
(见3.2.1.2 实现Runnable接口方式)
3.2 多线程安全问题解决
3.2.1 使用同步代码块
- 格式
synchronized(锁对象){//要加锁的代码
}/* 锁对象:* 1)同步代码块的锁对象可以是任意类型的对象* 2)必须使用【同一把锁】,否则可能出现锁不住的情况* * 同一把锁: * √-1) static修饰的静态对象(static Object obj = new Object();) 但不建议使用* √-2) 当前类对象的字节码文件(类名.Class) * ×-3) this (当前类对象) 每个线程new一个新的对象,所以不是同一把锁 */
3.2.1.1 继承Thread类方式
案例代码六 模拟买票的Thread方式实现-解决安全问题 【a.线程类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/13* @introduction 模拟买票的Thread方式实现-解决安全问题 线程类** 多线程安全问题解决* 使用同步代码块 synchronized* synchronized(锁对象){* //要加锁的代码* }** 锁对象:* 1)同步代码块的锁对象可以是任意类型的对象* 2)必须使用【同一把锁】,否则可能出现锁不住的情况*/
//2.因为是四个窗口同时卖票,通过多线程卖票(extends Thread)
public class Demo6TicketSellMyThread extends Thread {//1.定义一个变量(tickets),记录票数private static int tickets = 100;//因为是共享数据,所以用static修饰,在类加载前就放入静态常量池,被所有对象共享//模拟一个锁对象,可以实现同步代码块,但没必要为了同步代码块专门new一个obj,直接使用本类的.class文件即可static Object obj = new Object();public Demo6TicketSellMyThread(String window) {super(window);}@Overridepublic void run() {while (true) {/* 锁对象:* 1)同步代码块的锁对象可以是任意类型的对象* 2)必须使用【同一把锁】,否则可能出现锁不住的情况*/synchronized (Demo6TicketSellMyThread.class) {//不能直接锁在while条件上,否则会导致一个窗口卖完所有票if (tickets < 1) {break;}//Btry {sleep(20);} catch (InterruptedException e) {e.printStackTrace();}//CSystem.out.println(getName() + ":正在出售第" + tickets-- + "张票");}}}
}
案例代码六 模拟买票的Thread方式实现-解决安全问题 【b.测试类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/13* @introduction 模拟买票的Thread方式实现-解决安全问题 测试类** 多线程安全问题解决* 使用同步代码块 synchronized* synchronized(锁对象){* //要加锁的代码* }** 锁对象:* 1)同步代码块的锁对象可以是任意类型的对象* 2)必须使用【同一把锁】,否则可能出现锁不住的情况* 同一把锁 -> 当前类对象的字节码文件*/
public class Demo6TicketSellMyThreadTest {public static void main(String[] args) {//测试:卖票的动作//4.因为是四个窗口,所以需要创建四个线程对象 给线程自定义名字Demo6TicketSellMyThread mt1 = new Demo6TicketSellMyThread("窗口1");Demo6TicketSellMyThread mt2 = new Demo6TicketSellMyThread("窗口2");Demo6TicketSellMyThread mt3 = new Demo6TicketSellMyThread("窗口3");Demo6TicketSellMyThread mt4 = new Demo6TicketSellMyThread("窗口4");//5.开启线程mt1.start();mt2.start();mt3.start();mt4.start();/** 通过同步代码块【synchronized(类名.class){}】方式解决线程安全问题** ...* 窗口1:正在出售第7张票* 窗口1:正在出售第6张票* 窗口1:正在出售第5张票* 窗口4:正在出售第4张票* 窗口4:正在出售第3张票* 窗口4:正在出售第2张票* 窗口4:正在出售第1张票*/}
}
3.2.1.2 实现Runnable接口方式
Runnable和Thread方式区别
1) 因为四个Thread对象共用一个Runnable对象,所以tickets可以不用static修饰。
2) 因为四个Thread对象共用一个Runnable对象,所以锁对象可以是当前类this。
案例代码七 模拟买票的Runnable接口方式实现-解决安全问题 【a.Runnable子类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 模拟买票的Runnable接口方式实现-解决安全问题 Runnable的实现类** 可以理解:MyRunnable是资源类*/
public class Demo7TicketSellMyRunnableThread implements Runnable {//1.定义变量,记录票//private static int tickets = 100; //因为是共享数据,所以用static修饰,在类加载前就放入静态常量池,被所有对象共享private int tickets = 100; //因为四个Thread对象共用一个Runnable对象,所以可以不用static修饰//2.模拟卖票@Overridepublic void run() {while (true) {//synchronized (Demo7TicketSellMyRunnableThread.class) {synchronized (this) {//因为四个Thread对象共用一个Runnable对象,所以锁对象可以是当前类if (tickets < 1) {break;}try {//sleep(20);Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}//System.out.println(getName() + ":正在出售第" + tickets-- + "张票");//getName()是Thread类的方法,不能使用//static Thread currentTrhead():返回当前正在执行的线程对象的引用System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets-- + "张票");}}}
}
案例代码七 模拟买票的Runnable接口方式实现-解决安全问题 【b.测试类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 模拟买票的Runnable接口方式实现-解决安全问题 测试类***/
public class Demo7TicketSellMyRunnableThreadTest {public static void main(String[] args) {Demo7TicketSellMyRunnableThread mr = new Demo7TicketSellMyRunnableThread();Thread t1 = new Thread(mr,"窗口1");Thread t2 = new Thread(mr,"窗口2");Thread t3 = new Thread(mr,"窗口3");Thread t4 = new Thread(mr,"窗口4");//开启线程t1.start();t2.start();t3.start();t4.start();}
}
4 匿名内部类
- 内部类:类里边还有一个类,里边那个类叫内部类,外边那个类叫外部类。
- 成员内部类:定义在成员位置的内部类(类中,方法外)
- 局部内部类:定义在局部位置的内部类(只定义在局部范围内,如:函数内,语句内等,只在所属的区域有效。)
匿名内部类
概述:就是没有名字的局部内部类
格式:
new 类名或者接口名(){//重写类或者接口中 所有的 抽象方法; }
本质:
- 专业版:就是一个继承了类或者实现了接口的 匿名的子类对象
- 大白话:匿名内部类不是类,而是子类对象
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 匿名内部类 成员内部类&局部内部类*/
//外部类
public class AnonymousInnerClass {//成员变量String name;//成员内部类class Inner1 {}//成员方法public void show() {//局部变量,基本类型int num = 10;//局部变量,引用类型Object obj = new Object();//局部变量,引用类型,局部内部类class Inner2 {}}
}
4.1 三种实现方式
案例代码八 匿名内部类实现抽象类 【a.抽象类Animal】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 匿名内部类实现抽象类 抽象类Animal**/
public abstract class Demo8AbstractAnimal {//有抽象方法的类一定是抽象类,抽象类不一定有抽象方法public abstract void eat();
}
案例代码八 匿名内部类实现抽象类 【b.Animal实现类Cat】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 匿名内部类实现抽象类 Animal实现类Cat*/
public class Demo8Cat extends Demo8AbstractAnimal {@Overridepublic void eat() {System.out.println("我是Cat类的eat方法,猫吃鱼");}
}
案例代码八 匿名内部类实现抽象类 【c.测试类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 匿名内部类实现抽象类 测试类** 需求:调用 Demo8Animal#eat();*/
public class Demo8AnonymousInnerClass {public static void main(String[] args) {/*** 方式一:【普通类】实现* A:定义一个Cat类,继承Animal* B:重写eat()方法* C:多态方式创建Cat类的对象,调用eat()方法*/Demo8AbstractAnimal an1 = new Demo8Cat();an1.eat();/*** 方式二:【匿名对象】实现*/new Demo8Cat().eat();System.out.println("*********************");/*** 方式三:【匿名内部类】实现*/new Demo8AbstractAnimal() {public void eat() {System.out.println("我是匿名内部类的方式实现的,猫吃鱼");}}.eat();}
}
4.2 开发中的应用
- 匿名内部类在实际开发中的应用
- 1)当对象方法(成员方法)仅调用一次的时候
- 2)可以作为方法的实参进行传递
- 3)采用多态方式实现多次调用
- 个人建议
当接口中或抽象类中的抽象方法仅有一个的时候,就可以考虑使用匿名内部类(如果有多个方法,匿名调用一个方法也要同时实现其他方法)
new Demo9InterfaceJumping(){@Overridepublic void jump() {//我会跳,1)当对象方法(成员方法)仅调用一次的时候System.out.println("我会跳,1)当对象方法(成员方法)仅调用一次的时候");}@Overridepublic void eat() {}...}.jump();
案例代码九 匿名内部类在实际开发中的应用 【a.接口类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 匿名内部类在实际开发中的应用 接口*/
public interface Demo9InterfaceJumping {public abstract void jump();
}
案例代码九 匿名内部类在实际开发中的应用 【b.测试类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 匿名内部类在实际开发中的应用 测试类** 1)当对象方法(成员方法)仅调用一次的时候(如果调用多次,就采用子类对象实现方法,再调用子类对象#方法)* 2)可以作为方法的实参进行传递* 3)采用多态方式实现多次调用*/
public class Demo9AnonymousInnerClass {public static void main(String[] args) {//对,Demo9InterfaceJumping#jump 调用一次//1)当对象方法(成员方法)仅调用一次的时候(如果调用多次,就采用子类对象实现方法,再调用子类对象#方法)new Demo9InterfaceJumping(){@Overridepublic void jump() {//我会跳,1)当对象方法(成员方法)仅调用一次的时候System.out.println("我会跳,1)当对象方法(成员方法)仅调用一次的时候");}}.jump();//2)可以作为方法的实参进行传递show(new Demo9InterfaceJumping() {@Overridepublic void jump() {//我会跳,2)可以作为方法的实参进行传递System.out.println("我会跳,2)可以作为方法的实参进行传递");}});//3)采用多态方式实现多次调用Demo9InterfaceJumping jm =new Demo9InterfaceJumping(){ //多态 (接口指向子类对象)@Overridepublic void jump() {System.out.println("我会跳,3)采用多态方式实现多次调用");}};/** 我会跳,3)采用多态方式实现多次调用* 我会跳,3)采用多态方式实现多次调用* 我会跳,3)采用多态方式实现多次调用*/jm.jump();jm.jump();jm.jump();}//2)可以作为方法的实参进行传递public static void show(Demo9InterfaceJumping jm) {jm.jump();}
}
5 同步
- 同步和效率的问题
- 线程安全(线程同步),效率低
- 线程不安全(线程不同步),效率高
- 概述/作用
- 多线程(环境),并发, 操作同一数据,有可能引发安全问题,就需要用到同步解决
5.1 同步代码块
格式
synchronized(锁对象){//要加锁的代码 }/* 锁对象:* 1)同步代码块的锁对象可以是任意类型的对象* 2)必须使用【同一把锁】,否则可能出现锁不住的情况* * 同一把锁: * √-1) static修饰的静态对象(static Object obj = new Object();) 但不建议使用* √-2) 当前类对象的字节码文件(类名.Class) * ×-3) this (当前类对象) 每个线程new一个新的对象,所以不是同一把锁 */
5.2 同步方法
- 静态方法
- 锁对象:该类的字节码文件对象(类名.Class)
- 非静态方法
- 锁对象:this
5.2.1 静态方法锁对象
- 静态方法
- 锁对象:该类的字节码文件对象(类名.Class)
案例代码十 静态方法锁对象:该类的字节码文件对象 【a.方法类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 静态方法锁对象:该类的字节码文件对象 方法类*/
public class Demo10 {/** 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?* 静态方法:* 锁对象:该类的字节码文件对象* 非静态方法:* 锁对象:this** 思路:* 1.创建两个线程* 2.分别调用Demo类的两个方法* 一个线程调用Demo10#method1(),method1()用同步代码块* 一个线程调用Demo10#method2(),method2()用同步方法* 3.为了让效果更明显,用while(true)循环*/public static void method1(){//synchronized (锁对象) {//synchronized (String.class) { //method1锁对象是String.Class时 【没锁住】synchronized (Demo10.class) { //method1锁对象是Demo10.Class(该类的字节码)时 【锁住了】System.out.print("黑");System.out.print("马");System.out.print("程");System.out.print("序");System.out.print("员");System.out.print("\r\n");}}//静态方法锁对象:该类的字节码文件对象(Demo10.class)public synchronized static void method2(){System.out.print("i");System.out.print("t");System.out.print("c");System.out.print("a");System.out.print("s");System.out.print("t");System.out.print("\r\n");}
}
案例代码十 静态方法锁对象:该类的字节码文件对象 【b.测试类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 静态方法锁对象:该类的字节码文件对象 测试类*/
public class Demo10SynchronizedTarget {public static void main(String[] args) {/** 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?* 静态方法:* 锁对象:该类的字节码文件对象* 非静态方法:* 锁对象:this** 思路:* 1.创建两个线程* 2.分别调用Demo类的两个方法* 一个线程调用Demo10#method1(),method1()用同步代码块* 一个线程调用Demo10#method2(),method2()用同步方法* 3.为了让效果更明显,用while(true)循环*///1.创建一个线程new Thread() {public void run() {//2.一个线程调用Demo10#method1(),method1()用同步代码块//3.为了让效果更明显,用while(true)循环while (true) Demo10.method1();}}.start();//1.创建一个线程new Thread() {public void run() {//2.一个线程调用Demo10#method2(),method2()用同步方法//3.为了让效果更明显,用while(true)循环while (true) Demo10.method2();}}.start();/** method1锁对象是String.Class时 【没锁住】** 黑itcast* 马程序员* 黑马程序员* 黑马程序员itcast*//** method1锁对象是Demo10.Class(该类的字节码)时 【锁住了】** itcast* itcast* 黑马程序员* 黑马程序员*/}
}
5.2.2 非静态方法锁对象
- 非静态方法
- 锁对象:this
案例代码十一 非静态方法锁对象:this 【a.方法类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 非静态方法锁对象:this 方法类*/
public class Demo11 {/** 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?* 静态方法:* 锁对象:该类的字节码文件对象* 非静态方法:* 锁对象:this** 思路:* 1.创建两个线程* 2.分别调用Demo类的两个方法* 一个线程调用Demo11#method1(),method1()用同步代码块* 一个线程调用Demo11#method2(),method2()用同步方法* 3.为了让效果更明显,用while(true)循环*/public void method1(){//synchronized (Demo11.class) { //method1锁对象是Demo11.Class(该类的字节码)时 【没锁住】synchronized (this) { //method1锁对象是this时 【锁住了】System.out.print("黑");System.out.print("马");System.out.print("程");System.out.print("序");System.out.print("员");System.out.print("\r\n");}}//非静态方法锁对象:thispublic synchronized void method2(){System.out.print("i");System.out.print("t");System.out.print("c");System.out.print("a");System.out.print("s");System.out.print("t");System.out.print("\r\n");}
}
案例代码十一 非静态方法锁对象:this 【b.测试类】
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 静态方法锁对象:该类的字节码文件对象 测试类*/
public class Demo11SynchronizedTarget {public static void main(String[] args) {/** 如何印证通动态同步方法 和 非静态同步方法的锁对象呢?* 静态方法:* 锁对象:该类的字节码文件对象* 非静态方法:* 锁对象:this** 思路:* 1.创建两个线程* 2.分别调用Demo类的两个方法* 一个线程调用Demo11#method1(),method1()用同步代码块* 一个线程调用Demo11#method2(),method2()用同步方法* 3.为了让效果更明显,用while(true)循环*///jdk1.8这里默认了关键字final Demo11 demo = new Demo11();Demo11 demo = new Demo11();//demo = new Demo11(); //因为demo是final修饰的,所以不能重新赋值new Thread() {public void run() { while (true) demo.method1(); }}.start();new Thread() {public void run() { while (true) demo.method2(); }}.start();/** method1锁对象是Demo11.Class(该类的字节码)时 【没锁住】** 黑马itcast* itcast程序员* 黑马程序员*//** method1锁对象是this时 【锁住了】** itcast* itcast* 黑马程序员* 黑马程序员*/}
}
6 多线程的难点
6.1 死锁
死锁
- 1)死锁需要两个线程,两把锁
- 2)
- 一个线程先拿锁A,再拿锁B
- 另一个线程先拿锁B,再拿锁A
- 3)为了让效果更明显,用while(true)改进
案例代码十二 死锁的实现
package com.groupies.base.day14;/*** @author GroupiesM* @date 2021/05/17* @introduction 死锁的实现** 1)死锁需要两个线程,两把锁* 2)* 一个线程先拿锁A,再拿锁B* 另一个线程先拿锁B,再拿锁A* 3)为了让效果更明显,用while(true)改进**/
public class Demo12DeadLock {public static final String LOCKA = "锁A";public static final String LOCKB = "锁B";public static void main(String[] args) {/*new Thread(new Runnable() {@Overridepublic void run() {}}){}.start();*/new Thread() {public void run() {//3)为了让效果更明显,用while(true)改进while (true) {//2)一个线程先拿锁A,再拿锁Bsynchronized (LOCKA) {System.out.println("线程一获取到" + LOCKA + ",等待" + LOCKB);synchronized (LOCKB) {System.out.println("线程一获取到" + LOCKB + ",成功进入小房间...");}}}}}.start();new Thread() {public void run() {//3)为了让效果更明显,用while(true)改进while (true) {//2)另一个线程先拿锁B,再拿锁ASystem.out.println("线程二获取到" + LOCKB + ",等待" + LOCKA);synchronized (LOCKB) {synchronized (LOCKA) {System.out.println("线程二获取到" + LOCKA + ",成功进入小房间...");}}}}}.start();/** 死锁的实现:线程一与线程二互相等待对方手里的那把锁** 线程一获取到锁B,成功进入小房间...* 线程一获取到锁A,等待锁B* 线程一获取到锁B,成功进入小房间...* 线程二获取到锁B,等待锁A* 线程一获取到锁A,等待锁B*/}
}
6.2 多线程的生命周期
1.新建:创建线程对象
2.就绪:线程对象已经启动了,但是还没有获取到CPU的执行权
3.运行(有可能会发生阻塞或者等待状态) :获取到了CPU的执行权阻塞:没有CPU的执行权,回到就绪
4.死亡:代码运行完毕,线程消亡//唤醒在此对象监视器上等待的单个线程(随机唤醒)
Object#notify()
//唤醒在此对象监视器上等待的所有线程(随机唤醒)
Object#notifyAll()
//在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
Object#wait()
线程生命周期(一)
1.新建:创建线程对象
2.就绪:线程对象已经启动了,但是还没有获取到CPU的执行权
3.运行(有可能会发生阻塞或者等待状态) :获取到了CPU的执行权阻塞:一般指IO流阻塞,没有CPU的执行权,回到就绪等待:一般是可控的,等待另一线程先执行完毕或等待
4.死亡:代码运行完毕,线程消亡//唤醒在此对象监视器上等待的单个线程(随机唤醒)
Object#notify()
//唤醒在此对象监视器上等待的所有线程(随机唤醒)
Object#notifyAll()
//在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
Object#wait()
//等待(插队) ->
//主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。
Thread#join()
//等待(休眠),单位毫秒
Thread#sleep()
线程生命周期(二)
7.面试题
- 1.多线程并行和并发的区别是什么
并行:两个(多个)线程同时执行. (提前: 需要多核CPU)
并发:两个(多个)线程同时请求执行, 但是同一瞬间, CPU只能执行一个于是就安排它们交替执行, 因为时间间隔非常短, 我们看起来好像是同时执行的, 其实不是
- 2.Java程序是多线程的吗?
是,因为至少开启了main(主线程),GC(垃圾回收线程)
- 3.多线程的两种实现方式之间的区别是什么?
//Java中的继承特点:类只能单继承,接口可以多继承
继承Thread类:好处:代码相对比较简单 //因为是继承Thread类,所以可以直接使用Thread类中的非私有成员(成员变量,成员方法)弊端:扩展性相对比较差 //因为是继承,而Java中类之间的继承只能单继承,不能多继承,但是可以多层继承实现Runnable接口:好处:扩展性相对比较强 弊端:代码相对比较繁琐 //修饰符
因为多个Thread对象共用一个Runnable对象,所以可以不用static修饰票数tickets。
//锁对象
因为多个Thread对象共用一个Runnable对象,所以锁对象可以是当前类this。(Thread类锁对象一般是当前类的字节码文件)
- 4.多线程的执行特点是什么?
随机性、延迟性延迟性的原因:因为CPU在做着高效的切换
延迟性的体现:关闭QQ后,当前已打开的QQ对话框会延迟几秒才会退出
- 5.多线程的默认命名规则是什么
命名规则:Thread-${索引编号} //Thread-0 Thread-1
索引编号:从0开始
- 6.匿名内部类(本质是一个对象)
内部类:概述:类里边还有一个类,里边那个类叫内部类,外边那个类叫外部类。分类:成员内部类:定义在成员位置的内部类(类中,方法外)局部内部类:定义在局部位置的内部类(只定义在局部范围内,如:函数内,语句内等,只在所属的区域有效。)匿名内部类:概述:就是没有名字的局部内部类格式:new 类名或者接口名(){//重写类或者接口中 所有的 抽象方法;}本质:专业版:就是一个继承了类或者实现了接口的 匿名的子类对象大白话:匿名内部类不是类,而是子类对象匿名内部类在实际开发中的应用:1)当对象方法(成员方法)<font color=red>仅调用一次</font>的时候2)可以作为方法的实参进行传递3)采用多态方式实现多次调用个人建议当接口中或抽象类中的抽象方法仅有一个的时候,就可以考虑使用匿名内部类(如果有多个方法,匿名调用一个方法也要同时实现其他方法)new Demo9InterfaceJumping(){@Overridepublic void jump() {System.out.println("我会跳");} @Overridepublic void eat() {}...}.jump();
- 7.实现Runnable接口的原理
实现原理:多态
背景: 多线程的第一种实现方式是:继承Thread类,因为我们自定义的类(MyThread)是Thread类的子类所以MyThread类的对象调用start()方法的时候,自动调用MyThread#run()但是MyRunnable类是实现了Runnable接口,而Runnable#run()、Thread#start()没有关系问:为什么Thread#start(),会自动调用Runnable接口的子类(MyRunnable)中的run()方法呢?简化版的源码://测试类中的代码:public static void main(String[] args){MyRunnable mr = new MyRunnable();Thread th = new Thread(mr);th.start();//问:为什么会自动调用MyRunnable#run();?}//简化版的源码:public class Thread{private Runnable target; //new MyRunnable();public Thread(Runnable target){this.target = target; //new MyRunnable();}public void run(){if(target != null){target.run(); //new MyRunnable().run();}}}//Thread源码:public class Thread implements Runnable {/* What will be run. */private Runnable target;/*** Allocates a new {@code Thread} object. This constructor has the same* effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}* {@code (null, target, gname)}, where {@code gname} is a newly generated* name. Automatically generated names are of the form* {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.** @param target* the object whose {@code run} method is invoked when this thread* is started. If {@code null}, this classes {@code run} method does* nothing.*/public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);}/*** Initializes a Thread with the current AccessControlContext.* @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean)*/private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null, true);}/*** Initializes a Thread.** @param g the Thread group* @param target the object whose run() method gets called* @param name the name of the new Thread* @param stackSize the desired stack size for the new thread, or* zero to indicate that this parameter is to be ignored.* @param acc the AccessControlContext to inherit, or* AccessController.getContext() if null* @param inheritThreadLocals if {@code true}, inherit initial values for* inheritable thread-locals from the constructing thread*/private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name;Thread parent = currentThread();SecurityManager security = System.getSecurityManager();if (g == null) {/* Determine if it's an applet or not *//* If there is a security manager, ask the security managerwhat to do. */if (security != null) {g = security.getThreadGroup();}/* If the security doesn't have a strong opinion of the matteruse the parent thread group. */if (g == null) {g = parent.getThreadGroup();}}/* checkAccess regardless of whether or not threadgroup isexplicitly passed in. */g.checkAccess();/** Do we have the required permissions?*/if (security != null) {if (isCCLOverridden(getClass())) {security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);}}g.addUnstarted();this.group = g;this.daemon = parent.isDaemon();this.priority = parent.getPriority();if (security == null || isCCLOverridden(parent.getClass()))this.contextClassLoader = parent.getContextClassLoader();elsethis.contextClassLoader = parent.contextClassLoader;this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();/*****************************************************//*****************************************************///关键代码this.target = target;/*****************************************************//*****************************************************/setPriority(priority);if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Stash the specified stack size in case the VM cares */this.stackSize = stackSize;/* Set thread ID */tid = nextThreadID();}@Overridepublic void run() {if (target != null) {target.run();}}}
- 8.public static final有没有顺序要求
没有,可以随意调换顺序
- 9.jdk1.8新特性,当匿名内部类访问其所在方法的局部变量时,该变量必须加final修饰,为什么?
//5.2.2 非静态方法锁对象
为了延长该变量的生命周期
但是jdk1.8起,final可以不写,因为程序会默认加上public static void main(String[] args) {/** 局部方法随着main方法调用结束,被GC回收* 此时成员方法method1()尚未被GC回收,就会导致demo.method1找不到demo引用对象* 所以需要用final修饰所在方法的局部变量,将其放入静态常量池,延长生命周期*/Demo demo = new Demo();new Thread() {public void run() { while (true) demo.method1(); }}.start();
}
- 10.手写一个死锁代码
见6.1
- 11.多线程的生命周期是什么?
见6.2
12.数据结构 - 对象创建的过程
21/05/18
M
3.1_15 JavaSE入门 P14 【多线程】同步、匿名内部类、死锁、生命周期相关推荐
- java多线程同步与死锁_浅析Java多线程中的同步和死锁
Value Engineering 1基于Java的多线程 多线程是实现并发机制的一种有效手段,它允许编程语言在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间相互独立,且与进程一样拥有独立 ...
- 线程(线程基本概念、java实现多线程、使用多线程、线程的生命周期、线程同步、线程死锁)
(一)线程基本概念 一. 程序, 进程, 线程的概念 程序: 使用某种语言编写一组指令(代码)的集合,静态的 进程: 运行的程序,表示程序一次完整的执行, 当程序运行完成, 进程也就结束了 个人电脑: ...
- Java多线程(一)——多线程实现方法和生命周期
目录 一.引言 二.多进程与多线程 三.实现方法 1.继承Thread接口 2.实现runnable接口,传入Thread运行 3.注意 四.生命周期 五.总结 一.引言 之前多线程了解的基本是皮毛, ...
- iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用(上)
2017-07-08 remember17 Cocoa开发者社区 目的 本文主要是分享iOS多线程的相关内容,为了更系统的讲解,将分为以下7个方面来展开描述. 多线程的基本概念 线程的状态与生命周期 ...
- 05Vue.js快速入门-Vue实例详解与生命周期
Vue的实例是Vue框架的入口,其实也就是前端的ViewModel,它包含了页面中的业务逻辑处理.数据模型等,当然它也有自己的一系列的生命周期的事件钩子,辅助我们进行对整个Vue实例生成.编译.挂着. ...
- Vue入门指南-05 Vue实例的生命周期(快速上手vue)
生命周期钩子 = 生命周期函数 = 生命周期事件 实例创建期间的生命周期函数 // 刚初始化了一个空的实例对象, 这时候只有默认的一些生命周期函数和默认事件, 其他都未创建. // 如果要调用 met ...
- Spring Boot Vue Element入门实战(十)Vue生命周期
本博客属作者原创,未经允许禁止转载,请尊重原创!如有问题请联系QQ509961766 前面9篇文章基本上完成了vue 静态页面的入门,包括列表展示,路由动态加载菜单,echarts图表的一些使用,后面 ...
- java多线程同步与死锁,廖雪峰Java11多线程编程-2线程同步-3死锁
在多线程编程中,要执行synchronized块,必须首先获得指定对象的锁. 1.Java的线程锁是可重入的锁 public void add(int m){ synchronized (lock){ ...
- iOS多线程全套:线程生命周期,多线程的四种解决方案,线程安全问题,GCD的使用,NSOperation的使用(下)
2017-07-08 remember17 Cocoa开发者社区 7NSOperation的理解与使用 No.1:NSOperation简介 NSOperation是基于GCD之上的更高一层封装,NS ...
最新文章
- docker 报错 /usr/bin/docker-current: Error response from daemon: driver failed programming external
- leetcode_486. Predict the Winner
- SQL group by 和 order by 、where、having
- 流式机器学习算法的入门和认知
- 网络:TIME-WAIT
- CNCF 宣布成立应用交付领域小组,正式开启云原生应用时代
- lisp精要(2)-基础(1)
- xp系统蓝屏代码7b_遇到系统问题,三种常见处理方法你更pick谁
- 八大排序算法的Python实现
- Redis——史上最强【集群】入门实践教程
- Mysql中Key与Index的区别
- 安岳天气预报软件测试,安岳天气预报15天
- solaris如何启动ssh服务
- python函数可以提高代码执行速度吗_Python代码运行速度慢?这五种方法很管用
- 235.二叉搜索树的最近公共祖先
- JDK 11JAVA11下载分享
- C语言if语句的基本用法
- 【py】pandas
- 使用计算机要遵循哪些规则,中国大学MOOC:\\\在计算机网络的定义中,把众多计算机有机连接起来要遵循规定的约定和规则,称之为( )。\\\;...
- 时间焦虑:为时已晚?