day23目录:
多线程
进程的概述和多进程的意义
线程的概述和多线程的意义
JVM运行原理以及JVM启动的线程探讨
实现多线程
线程调度
线程控制
线程安全问题----Lock死锁
23.01_多线程(进程概述及多进程的意义)(理解)
A:线程和进程
要想说线程,首先必须得聊聊进程,因为线程是依赖于进程存在的。
B:进程概述
什么是进程呢?通过任务管理器我们就可以看到进程的存在。
概念:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
C:多进程的意义
单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),
所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。
因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,
所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。
23.02_多线程(线程概述及多线程的意义及并行和并发的区别)(理解)
A:什么是线程
在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。是程序使用CPU的基本单位。所以,进程是拥有资源的基本单位, 线程是CPU调度的基本单位。
B:多线程有什么意义呢?
多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
那么怎么理解这个问题呢?
我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到
CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序
中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们
中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.
C:大家注意两个词汇的区别:并行和并发。
前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。
后者是物理上同时发生,指在某一个时间点同时运行多个程序。
什么是并发 ?
并发 : 指应用能够交替执行不同的任务, 其实并发有点类似于多线程的原理, 多线程并非是如果你开两个线程同时执行多个任务。
执行, 就是在你几乎不可能察觉到的速度不断去切换这两个任务, 已达到"同时执行效果", 其实并不是的, 只是计算机的速度太快, 我们无法察觉到而已. 就类似于你, 吃一口饭喝一口水, 以正常速度来看, 完全能够看的出来, 当你把这个过程以n倍速度执行时..可以想象一下.
什么是并行 ?
并行 : 指应用能够同时执行不同的任务, 例:吃饭的时候可以边吃饭边打电话, 这两件事情可以同时执行
23.03_多线程(Java程序运行原理和JVM的启动是多线程的吗)(理解)
A:Java程序运行原理
Java.exe 命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
所以 main方法运行在主线程中。
B:JVM的启动是多线程的吗: JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
23.04_多线程(多线程程序实现的方式1)(掌握)
A:如何实现多线程:
如何实现呢?
由于线程是依赖进程而存在的,所以我们应该先创建一个进程(JVM)出来。
而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。
但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
但是呢?Java可以去调用C/C++写好的程序来实现多线程程序。
由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
然后提供一些类供我们使用。我们就可以实现多线程程序了。
参考 Thread类
B:多线程程序实现的方式1
a:继承Thread类
b:步骤及代码演示
c:几个小问题:
启动线程使用的是哪个方法
线程能不能多次启动
run()和start()方法的区别
答案:我们启动线程使用不是run方法,而应该是start方法.使该线程开始执行;
Java 虚拟机调用该线程的 run 方法。
为什么要重写run方法?
这个类是一个线程类,那么在这个类中我们可不可以写一些其他的方法呢?
我们可以在写其他的方法,那么其他方法中封装的代码都是需要被我们线程执行的吗? 不一定
那么也就是run方法中封装应该是必须被线程执行的代码.
run方法中的代码的书写原则: 一般是比较耗时的代码
C:案例演示: 多线程程序实现的方式1
public class MyTest {
public static void main(String[] args) {
// Thread 线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
// 我可以使用Java提供的这个Thread类,来创建子线程。
//创建一个线程的常见的方式有两种,
//方式1:
//1.定义一个类,继承Thread类
//2.重写Thread类中的run方法,因为run方法里面的代码是让线程来执行的。
//3.创建自己定义的这个类的对象。
//4.调用start()方法来开启线程。
MyThread th = new MyThread();
//开启线程
// th.run(); //这个不是开启线程的方法,就是普通的创建对象,调用方法。
// th.show();
//正确的开启线程的方式是,调用 start()方法来开启
/*
* start
public void start()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发地运行;当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法)。
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
*/
th.start();
// th.start(); 不要重复的开启同一个线程。
MyThread th2 = new MyThread();
th2.start();
}
}
public class MyThread extends Thread {
@Override
public void run() {
//为什么要重写run方法,是因为子线程开启后,run方法里面的代码是由子线程来执行的。
//一般耗时的操作的代码,就可以放到run方法里面,让线程来执行。
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
public void show() {
System.out.println("show方法");
}
}
23.05_多线程(获取和设置线程对象名称)(掌握)
A:Thread类的基本获取和设置方法
public final String getName()//获取线程名称
public final void setName(String name)//设置线程名称
其实通过构造方法也可以给线程起名字
思考:
如何获取main方法所在的线程名称呢?
public static Thread currentThread()//获取当前执行的线程
/**
* 我们现在是想获取主线程的名称,那么我们可不可以先获取到主线程,
如果我们能获取到主线程,那么我们就可以调用getName方法获取对应的名称.
* 如何获取主线程呢? public static Thread currentThread()返回对当前正在执行的线程对象的引用。
*/
B:案例演示: 获取和设置线程对象名称
public class MyTest {
public static void main(String[] args) {
//获取主线程的名称
//Thread.currentThread(); 获取当前正在执行的线程对象
Thread thread = Thread.currentThread();
thread.setName("主线程");
String name = thread.getName();
System.out.println(name);
//Java 中使用 Thread 创建线程
MyThread th = new MyThread();
//给线程设置名字
th.setName("刘亦菲");
th.start(); //开启线程
}
}
public class MyThread extends Thread {
@Override
public void run() {
//获取当前正在执行的线程对象
Thread thread = Thread.currentThread();
//this 代表子线程对象
System.out.println(this.getClass().getName());
//run方法里面写的是需要线程来执行的代码,一般放的就是耗时代码
for (int i = 0; i < 100; i++) {
//getName() 获取线程的名称
System.out.println(this.getName() + "==" + i);
//System.out.println(Thread.currentThread().getName() + "===" + i);
}
}
}
23.06_多线程(线程调度及获取和设置线程优先级)(了解)
A:线程的执行
假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,
线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
B:线程有两种调度模型:
分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,
优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
C:如何设置和获取线程优先级
public final int getPriority() //获取线程的优先级
public final void setPriority(int newPriority)//设置线程的优先级
D:案例演示: 获取和设置线程优先级
注意事项: 有的时候我们给线程设置了指定的优先级,但是该线程并不是按照优先级高的线程执行,那是为什么呢?
- 因为线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了.但是我们都知道多线程具有随机性,
- 所以有的时候一两次的运行说明不了问题
/**
* 我们现在呢没有给线程设置优先级,那么java采用的是抢占式调度模型,那么这个线程应该存在一个默认的优先级.
* 那么这个默认的优先级是多少呢,以及我们如何来获取线程的优先级.
* 获取线程的优先级:
* public final int getPriority()返回线程的优先级。
* 线程的默认优先级是5
*
* 给线程设置优先级:范围是 1---10
* public final void setPriority(int newPriority)
*/
public class MyTest {
public static void main(String[] args) {
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
th1.setName("线程A");
th2.setName("线程B");
//设置线程的优先级,范围是 1---10
th1.setPriority(Thread.MIN_PRIORITY); //1
th2.setPriority(Thread.MAX_PRIORITY); //10
int priority = th1.getPriority();
int priority1 = th2.getPriority();
System.out.println(priority);
System.out.println(priority1);
th1.start();
th2.start();
}
}
23.07_多线程(线程控制之休眠线程)(掌握)
A:线程休眠: public static void sleep(long millis) 线程休眠
B:案例演示: 线程休眠
public class MyTest {
public static void main(String[] args) throws InterruptedException {
//展示广告页面 60
System.out.println("广告页面");
//让当前的线程休眠
Thread.sleep(1000 * 6);//休眠6s
//跳到主页面
System.out.println("跳到主页面");
MyThread th1 = new MyThread();
th1.setName("线程A");
th1.start();
}
}
public class MyThread extends Thread {
@Override
public void run() {
try {
//让线程休眠 单位是毫秒
Thread.sleep(1000 * 3); //休眠3s
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + "===" + i);
}
}
}
23.08_多线程(线程控制之加入线程)(掌握)
A:加入线程: public final void join()
意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行
注意事项: 在线程启动之后,在调用方法
B:案例演示: 加入线程
public class MyTest {
public static void main(String[] args) throws InterruptedException {
//主线程:就相当于用户线程。
//守护线程:当用户线程执行完了,守护线程就要死亡掉。
//三个线程,并发执行,那么现在我想让三个线程按顺序执行(串行)
MyThread th0 = new MyThread();
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
th0.setName("刘备");
th1.setName("关羽");
th2.setName("张飞");
//设置线程加入,可以让多个线程从并发执行,变为顺序执行(串行)
th0.start();
th0.join(); //在线程开启之后,再加入
th1.start();
th1.join();
th2.start();
th2.join();
}
}
23.09_多线程(线程控制之礼让线程)(了解)
A:礼让线程: public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
B:案例演示: 礼让线程
按照我们的想法,这个礼让应该是一个线程执行一次,但是通过我们的测试,效果好像不太明显.
那是为什么呢?
这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有
抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 900000000; i++) {
Thread.yield();//线程礼让
System.out.println(this.getName() + "===" + i);
}
}
}
该方法效果不明显,最理想状态:几个线程一人一次。
23.10_多线程(线程控制之守护线程)(了解)
A:守护线程: public final void setDaemon(boolean on):
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。
B:案例演示: 守护线程
Java用户线程和守护线程
1.用户线程和守护线程的区别
用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束。而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。
2.用户线程和守护线程的适用场景
由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。
3.创建守护线程
调用线程对象的方法setDaemon(true),设置线程为守护线程。
1)thread.setDaemon(true)必须在thread.start()之前设置。
2)在Daemon线程中产生的新线程也是Daemon的。
3)不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。
因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。
4.Java守护线程和Linux守护进程
两者不是一个概念。Linux守护进程是后台服务进程,没有控制台。
在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了。所以守护进程并非一定需要的。
public class MyTest {
public static void main(String[] args) throws InterruptedException {
//主线程:就相当于用户线程。
//守护线程:当用户线程执行完了,守护线程就要死亡掉。
MyThread th1 = new MyThread();
th1.setName("线程A");
th1.start();
Thread.sleep(100);
//停止线程,现在不推荐使用
th1.stop();
}
}
23.11_多线程(线程控制之中断线程)(了解)
A:中断线程
public final void stop(): 停止线程的运行
public void interrupt(): 中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞
public class MyTest {
public static void main(String[] args) throws InterruptedException {
//主线程:就相当于用户线程。
//守护线程:当用户线程执行完了,守护线程就要死亡掉。
MyThread th1 = new MyThread();
th1.setName("线程A");
th1.start();
Thread.sleep(2000);
//打断子线程的阻塞的状态,比如线程正处于休眠状态,那这个方法,就可以 让他不要在休眠
th1.interrupt();
}
}
23.12_多线程(多线程程序实现的方式2)(掌握)
A:实现Runnable接口 这种方式扩展性强 实现一个接口 还可以再去继承其他类
a:如何获取线程名称
b:如何给线程设置名称
c:实现接口方式的好处
可以避免由于Java单继承带来的局限性。
B:案例演示: 多线程程序实现的方式2
public class MyTest {
public static void main(String[] args) {
//线程创建的第二种方式
//1.定义一个类,实现一个Runable接口
//2.重写Runable接口中的run方法
//3. 创建这个Runable接口的子类对象
//4.创建Thread类对象,把Runable接口的子类对象传进去
//5.调用start方法开启线程
//创建一个任务
MyRunable myRunable = new MyRunable();
//创建一个线程对象,把任务传进来
Thread th = new Thread(myRunable);
//开启线程,线程就会执行任务中的run方法
th.start();
//主线线程中,想要获取子线程执行的结果。
}
}
//Runnable 任务 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
public class MyRunable implements Runnable {
@Override
public void run() {
/* 方法摘要
void run ()
使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。*/
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "====" + i);
}
}
}
public class MyThread extends Thread {
@Override
public void run() {
//获取当前正在执行的线程对象
Thread thread = Thread.currentThread();
//this 代表子线程对象
System.out.println(this.getClass().getName());
//run方法里面写的是需要线程来执行的代码,一般放的就是耗时代码
for (int i = 0; i < 100; i++) {
//getName() 获取线程的名称
System.out.println(this.getName() + "==" + i);
//System.out.println(Thread.currentThread().getName() + "===" + i);
}
}
}
23.12_多线程(多线程程序实现的方式3)(掌握)
A:实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
B:执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
C:实现步骤
1.创建一个类实现Callable 接口
2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3.创建Thread类,将FutureTask对象作为参数传进去
4.开启线程
public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程的第三种方式
/* C:
实现步骤
1. 创建一个类实现Callable 任务接口
2. 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3. 创建Thread类, 将FutureTask对象作为参数传进去
4. 开启线程*/
//创建任务
MyCallable myCallable = new MyCallable();
//可取消的异步计算。利用开始和取消计算的方法、查询计算是否完成的方法和获取计算结果的方法,此类提供了对 Future 的基本实现。
// 仅在计算完成时才能获取结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。
// 可使用 FutureTask 包装 Callable 或 Runnable 对象
FutureTask<Object> task = new FutureTask<>(myCallable);
Thread th = new Thread(task);
th.setName("线程A");
th.start();
//获取子线程执行完之后,返回的结果
Object o = task.get();
System.out.println(o);
//
}
}
public class MyCallable implements Callable<Object> {
@Override
public Object call() throws Exception {
//call方法,让线程来执行的方法
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "===" + i);
}
return 100;
}
}
三种方法的区别:第三种可以抛出异常;带返回值,例如得到sum。例:
public class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
//计算 累加的值
int sum = 0;
for (int i = 1; i <= num; i++) {
sum += i;
}
return sum;
}
}
public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable callable1 = new MyCallable(100);
FutureTask<Integer> task = new FutureTask<>(callable1);
Thread th1 = new Thread(task);
th1.start();
//获取子线程执行完的结果
Integer integer = task.get();
System.out.println(integer);
System.out.println("=================================");
MyCallable callable2 = new MyCallable(1000);
FutureTask<Integer> task2 = new FutureTask<>(callable2);
Thread th2 = new Thread(task2);
th2.start();
//获取子线程执行完的结果
Integer integer2 = task2.get();
System.out.println(integer2);
}
}
Runnable 任务接口 和 Callable<Integer> 任务接口的区别:
Runnable 任务接口中的run方法没有返回值,也不能抛出异常。
Callable<Integer> 任务接口 中存的call方法,是由返回值的,并且可以抛出异常。
执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。FutureTask 是 Future 接口的实现类
23.14_多线程(继承Thread类的方式卖电影票案例)(理解)
A:案例演示
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过继承Thread类实现
分析:
a: 三个窗口其实就是3个线程
b: 定义票的数量100张
c: 创建线程对象,启动线程. 每卖一张这个票数应该--
public class MyTest {
public static void main(String[] args) {
/* A:
案例演示
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过继承Thread类实现*/
//100张票 属于共享资源,三个窗口共同卖
//三个窗口就相当于三个线程。
CellThread th1 = new CellThread("窗口1");
CellThread th2 = new CellThread("窗口2");
CellThread th3 = new CellThread("窗口3");
th1.start();
th2.start();
th3.start();
}
public class CellThread extends Thread {
//设置票为共享资源
static int piao = 100;
public CellThread() {
}
public CellThread(String name) {
super(name);
}
@Override
public void run() {
while (true) {
if (piao > 0) {
System.out.println(Thread.currentThread().getName() + ":正在售卖" + (piao--) + "张票");
}
}
}
}
23.15_多线程(实现Runnable接口的方式卖电影票)(理解)
A:案例演示
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
通过实现Runnable接口实现
public class MyTest {
public static void main(String[] args) {
/*
* 出现线程安全问题,要符合几个条件。
* 1.是不是多线程环境。
* 2.多个线程有没有去操作共享资源。
* 3.是不是有多条语句在操作这个共享资源,也就是对共享资源操作,是不是一个原子性的操作。
* */
/*
* 我们模拟了一下,网络售票延迟,就出现了一些不合理的数据,也就是说,出现了线程安全问题。
*
*
* 1.出现了0票 负数票,这是由于线程的随机性导致,并发导致的。
*
*
* 2.出现了重复票,原因是由于,原子性所导致的,原子性:不可再分割性
* piao-- 他不是一个原子性的操作。
*
* piao-- 要经过三个步骤 读 改 写
* */
CellRunable cellRunable = new CellRunable();
Thread th1 = new Thread(cellRunable, "窗口1");
Thread th2 = new Thread(cellRunable, "窗口2");
Thread th3 = new Thread(cellRunable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
public class CellRunable implements Runnable {
//设置票为共享资源
static int piao = 100;
@Override
public void run() {
while (true) {
//1 th1 th2
if (piao > 0) {
//th2
// th1
//模拟一下网络延迟现象
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在售卖" + (piao--) + "张票");
}
}
}
}
23.16_多线程(买电影票出现了同票和负数票的原因分析)(理解)
A:加入延迟
我们前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,
售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟
改实现接口方式的卖票程序,每次卖票延迟100毫秒
B:出现问题了问题
23.17_多线程(线程安全问题的产生原因分析)(理解)
A:首先想为什么出现问题?(也是我们判断是否有问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
B:如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境。
怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
- 判断一个多线程应用程序是否有问题的标准:
- a: 是否是多线程环境
- b: 是否存在共享数据
- c: 是否存在多条语句同时操作共享数据
-
- 我们现在这个程序是存在问题的,因为它满足上面的标准,那么我们只要将这个标准打乱,那么我们就可以解决这个问题.
- 而上面的标准中a , b是不能打乱的,因此我们只能对c做处理,关键是怎么处理? 如果我们把操作共享数据的多条语句看做
- 成一个整体,当一个线程执行这个整体的时候,其他的线程处于等待状态,也就说当一个线程执行这个整体的时候,其他线程
- 不能进行执行,那么怎么做到这个一点呢?
-
- 需要使用同步代码块:
-
- 格式:
-
- synchronized(对象){//不能在括号了直接new 对象 new 了 就没效果
- 要被同步的代码 ;
- }
*
- 这个同步代码块保证数据的安全性的一个主要因素就是这个对象
注意这个对象 要定义为静态成员变量 才能被所有线程共享
- 需要这个对象被所有的线程对象所共享
- 这个对象其实就是一把锁.
- 这个对象习惯叫做监视器
23.18_多线程(同步代码块的方式解决线程安全问题及解释以及同步的特点及好处和弊端)(掌握)
A:同步代码块的格式
格式:
synchronized(对象){ //同步代码代码块上的锁,是一个互斥锁。
死循环
需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
B:案例演示: 同步代码块的方式解决线程安全问题
C:案例解释: 再次给学生解释一遍如何解决了线程安全问题
D:同步的好处: 同步的出现解决了多线程的安全问题。
E:同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,
因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。
java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。
线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。
获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,
当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,
直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,
但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,
类锁是用于类的静态方法或者一个类的class对象上的。
我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,
所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,
它只是用来帮助我们理解锁定实例方法和静态方法的区别的.
public class MyTest {
public static void main(String[] args) {
/*
* 出现线程安全问题,要符合几个条件。
* 1.是不是多线程环境。
* 2.多个线程有没有去操作共享资源。
* 3.是不是有多条语句在操作这个共享资源,也就是对共享资源操作,是不是一个原子性的操作。
* */
/*
* 我们模拟了一下,网络售票延迟,就出现了一些不合理的数据,也就是说,出现了线程安全问题。
*
*
* 1.出现了0票 负数票,这是由于线程的随机性导致,并发导致的。
*
*
* 2.出现了重复票,原因是由于,原子性所导致的,原子性:不可再分割性
* piao-- 他不是一个原子性的操作。
*
* piao-- 要经过三个步骤 读 改 写
* */
CellRunable cellRunable = new CellRunable();
Thread th1 = new Thread(cellRunable, "窗口1");
Thread th2 = new Thread(cellRunable, "窗口2");
Thread th3 = new Thread(cellRunable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
public class CellRunable implements Runnable {
//设置票为共享资源
static int piao = 100;
//锁对象
static Object obj = new Object();
/*
* 出现了线程安全问题,怎么来解决,
* 我们可以使用一个同步代码块,把有可能出现线程安全问题的代码包裹起来
* synchronized (锁对象){
需要同步的代码
}
* */
/*
synchronized (锁对象){
需要同步的代码
}
锁对像:同步代码块的所对象,可以是Java中任意一个对象
*/
@Override
public void run() {
while (true) {
//同步代码块,包裹有可能会出现线程安全问题的代码
//多个线程,要使用同一个锁对象,要共享,所以要new在前面
//th1 th2 th3
//锁对像:同步代码块的所对象,可以是Java中任意一个对象
//同步代码块锁也可以叫互斥锁
synchronized (obj) {
//th1
// th1 一个线程一旦进入同步代码块,就持有锁,其他线程没有获取到锁对象,就处于等待状态
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在售卖" + (piao--) + "张票");
}
}
//一个线程出了同步同步代码块,会释放锁对象,
}
}
}
23.19_多线程(同步代码块的锁问题以及同步方法的应用和锁问题)(掌握)
A:案例演示: 同步代码块的锁问题
B:同步方法: 就是把同步关键字加到方法上
C:案例演示: 同步方法的锁对象是什么呢?
D:案例演示: 如果是静态方法,同步方法的锁对象又是什么呢?
同步代码块的锁对象: 任意一个对象
同步方法的锁对象: 是this
静态同步方法的锁对象:就是当前类对应的字节码文件对象
同步代码块无法和同步方法共享资源,还是会出现线程安全问题。
锁不住,例:
public class CellRunable implements Runnable {
//设置票为共享资源
static int piao = 100;
//锁对象
static Object obj = new Object();
int i = 0;
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
synchronized (obj) {
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在售卖" + (piao--) + "张票");
}
}
} else {
maiPiao();
}
i++;
}
}
//同步代码块使用的锁对象是任意的Java对象
//方法上加了synchronized 就成为了一个同步方法,同步方法使用的锁对象this
public synchronized void maiPiao() {
//System.out.println(this.getClass().getName());
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在售卖" + (piao--) + "张票");
}
}
}
锁得住,例1:
public class CellRunable implements Runnable {
//设置票为共享资源
static int piao = 100;
//锁对象
static Object obj = new Object();
int i = 0;
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
synchronized (obj) {
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在售卖" + (piao--) + "张票");
}
}
} else {
maiPiao();
}
i++;
}
}
public void maiPiao() {
synchronized (obj) {
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在售卖" + (piao--) + "张票");
}
}
}
}
锁得住,例2:
public class CellRunable implements Runnable {
//设置票为共享资源
static int piao = 100;
//锁对象
static Object obj = new Object();
int i = 0;
@Override
public void run() {
while (true) {
if (i % 2 == 0) {
synchronized (this) {
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在售卖" + (piao--) + "张票");
}
}
} else {
maiPiao();
}
i++;
}
}
//同步代码块使用的锁对象是任意的Java对象
//方法上加了synchronized 就成为了一个同步方法,同步方法使用的锁对象this
public synchronized void maiPiao() {
//System.out.println(this.getClass().getName());
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在售卖" + (piao--) + "张票");
}
}
}
最终版本:
public class CellRunable implements Runnable {
//设置票为共享资源
static int piao = 100;
@Override
public void run() {
while (true) {
maiPiao();
}
}
public synchronized void maiPiao() {
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在售卖" + (piao--) + "张票");
}
}
}
24.01_多线程(JDK5之后的Lock锁的概述和使用)(了解)
A:Lock锁的概述
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
B:Lock和ReentrantLock
void lock() 加锁
void unlock() 释放锁
C:案例演示: Lock锁的使用
建议总是 立即实践,使用 lock 块来调用 try,在之前/之后的构造中,最典型的代码如下:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
public class CellRunable implements Runnable {
//设置票为共享资源
static int piao = 100;
/* Lock 实现提供了比使用
synchronized 方法和语句可获得的更广泛的锁定操作。
*/
static Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//加锁
lock.lock();
try {
if (piao > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":正在售卖" + (piao--) + "张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
}
}
24.02_多线程(死锁问题概述和使用)(了解)
A:死锁问题概述
如果出现了同步嵌套,就容易产生死锁问题
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
同步代码块的嵌套案例
死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态
举例: 中国人和美国人一起吃饭
中国人使用的筷子
美国人使用的刀和叉
中国人获取到了美国人的刀
美国人获取到了中国人的一根筷子
B:案例演示: 死锁问题代码演示
public interface LockUtils {
//定义两把锁对象
public static final Object objA=new Object();
public static final Object objB = new Object();
}
public class MyTest {
public static void main(String[] args) {
MyThread th1 = new MyThread(true);
MyThread th2 = new MyThread(false);
th1.start();
th2.start();
}
}
public class MyThread extends Thread {
boolean flag;
public MyThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (LockUtils.objA) {
System.out.println("true的时候,该线程进来了持有objA");
synchronized (LockUtils.objB) {
System.out.println("true的时候,该线程进来了持有objB");
}
//释放objB锁
}
//释放objA锁
} else { //可重入锁
synchronized (LockUtils.objB) {
System.out.println("false的时候,该线程进来了持有objB");
synchronized (LockUtils.objA) {
System.out.println("false的时候,该线程进来了持有objA");
}
//释放objA锁
}
//释放objB锁
}
}
}
23.21_day23总结
并发和并行的区别
并发(concurrency)和并行(parallellism)是:
- 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
- 解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
- 解释三:并行是在一台处理器上“同时”处理多个任务,并发是在多台处理器上同时处理多个任务。如 hadoop 分布式集群。
所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。
并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
parallel.jpg
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
concurrency.jpg
并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)。
当有多个线程在操作时,如果系统只有一个 CPU,则它根本不可能真正同时进行一个以上的线程,它只能把 CPU 运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态.这种方式我们称之为并发(Concurrent)。
当系统有一个以上 CPU 时,则线程的操作有可能非并发。当一个 CPU 执行一个线程时,另一个 CPU 可以执行另一个线程,两个线程互不抢占 CPU 资源,可以同时进行,这种方式我们称之为并行(Parallel)。
串行:几个线程按顺序执行
补充: