程序(program)是为完成特定任务、用某种语言编写的一组指令的集合 即指一 段静态的代码,静态对象。
进程(process)是程序的一次执行过程,或是正在运行的一个程序 是一个动态的过程
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread)
进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间 并行
执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
但多个线程操作共享的系统资源可能就会带来安全的隐患
单核CPU,其实是一种假的多线程,因为在一个时间单元内,单核
同时也只能执行一个线程的任务
例如:
晾着他,等他想通了,准备好了钱,再去收费
执行线程
收完费用执行结束
对于大的任务, 也可以进行挂起,或一点点的处理...
一个个的在工作运行,感觉是一起执行的!
如果是多核的话,才能更好的发挥多线程的效率。 CPU一个核就相当于一个工作人员...
现在的服务器都是多核的)
一个Java应用程序java.exe,其实至少有三个线程:
并行:
比如:多个人同时做不同的事
并发:
比如:秒杀、多个人做同一件事
并行与并发: 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。 并发的关键是你有处理多个任务的能力,不一定要同时 并行的关键是你有同时处理多个任务的能力 它们最关键的点就是:是否是『同时』
但:多线程并不一定提高效率: 注意是不一定!
多线程还要计算,cpu 分配不同时间片来执行时,切换的时间...
程序需要同时执行两个或多个任务
校验输入数据
文件上传时间较长可以开个线程缓慢上传… 让用户不用一直等待
可以提高程序响应速度..
JAVA实现多线程方式: 4种
JDK5.0之后新增两种:
Java语言的JVM允许程序运行多个线程,它通过 Java.lang.Thread
类来体现
Java中每个线程都是通过某个特定Thread对象的run()方法来完成操作的 把run()方法的主体称为线程体
通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
构造器:
其实 Thread 类本身也是实现了 Runnable 接口 因此 Therad 不是抽象类(类 重写了Runable接口 run(); 方法
Java 通过 Thread类将线程,所必需的功能都封装了起来
Thread类: 支持多线程类提供了大量的方法,控制操作线程
void Thread(); //分配新的 Thread 对象;
void Thread(Runnable); //分配新的 Thread 对象 参数 Runnable 实现类 对象
void Thread(Runnable,String); //分配新的 Thread 对象 参数1 Runnable 实现类 对象 ,参数2 线程名....
void run(); //通常需要子类重写,将线程执行的操作声明在方法中,对象直接调用单纯执行run()方法; 不是以线程方式;
void start(); //使线程开始执行 启动当前线程;java虚拟机调用 run() 方法;
//注意:一个对象不可以调两次start();方法; 启动两次?? (底层固定好了!)
void sleep(long); //Theread静态方法(); 在指定毫秒数内让当前正在执行线程,休眠(暂停);需要处理异常!!
String getName(); //返回线程 名称;
void setName(String); //修改线程名; 注意:要在 start(); 之前改名;
int getPriority(); //返回线程 优先级; 0-10 默认5; 越高 CPU 获取几率越大,也只是概率~
void setPriority(int); //更改线程 优先级
static Thread.currentTherad(); //Theread类静态方法 返回当前正在执行线程的线程对象Thread;
//注意: main();是java主线程,方法内调用 指main();中的线程;
boolean isAlive(); //检查线程是否属于 运行状态; 需要处理异常!!
void join(); //等待终止线程; 使当前线程运行完毕!
//线程A调用,此时A进入阻塞状态,直到B线程完全执行完,A才结束阻塞;
void interrupt(); //终止线程;
void yieId(); //礼让: 暂停当前线程对象允许其他线程获得CPU资源;
(停一下则立刻就绪!)该线程仍处于就绪状态,系统随机选择:就绪线程运行; 有可能该线程继续获取到资源!
void stop(); //已过时,当执行此方法,强制结束当前线程; 生命周期结束;
void wait(); //线程 run()中调用,当前线程就进入阻塞状态,并开启锁,其它线程可访问数据;
void notify()/notfyAll(); //线程 run()中调用,notify();随机取消一个线程设置wait(); notfyAll();把所有的wait();阻塞线程都取消阻塞;
wait() notfyAll() notfy()
三个方法是在 Java.lang.Object类中
三个方法的调用者必须是:当前同步代码块/同步监视器
否则会出现,IllegalMonitorStateExeption异常;
sleep()和wait()方法的异同:
wait需要由 同步监视器来调用!
Java中的线程分为两类:守护线程
用户线程
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开
守护线程:
是用来服务用户线程的,
通过在 start()
方法前调用 thread.setDaemon(true)
可以把一个用户线程变成一个守护线程
Java垃圾回收就是一个典型的守护线程 main 就是一个用户线程!
若JVM中都是守护线程,当前JVM将退出
守护线程,随着用户线程消亡而消亡
守护线程和其它线程生命周期不同…
线程一般具有五种状态: 创建 就绪 运行 堵塞 死亡
1.创建状态:
//用构造函数 创建 出
( Thread )或实现( Runnable )类 的 类对象; 就是线程 创建;
2.就绪状态:
//调用了 .start(); 方法的 就是线程 就绪;
3.运行状态:
//线程 从就绪到运行 获得了CPU 的资源进入运行状态....失去CUP权限/yieId();就绪专题....
4.堵塞状态:
//一个 正在运行 的线程 因某种原因不在运行时(一个线程 不能获取 CPU资源) 进入 阻塞状态....
//常见: 1. .sleep(int); (休眠) 2.yieId(); (线程显示让出CPU资源) 3.I/O 事件阻塞...等待同步锁...
5.死亡状态:
//线程 的run(); 方法 中代码执行完毕... 不在具有 继续运行的 能力!
JDK中用Thread.State类定义了线程的几种状态: Java线程有6中状态
JDK1.5之前创建新执行线程有两种方法:
注意:
start(); 方法
线程对象.start();
才是正确的多线程就绪方法, Java底层会去开启多线程调用run(); 多线程执行run() 中的步骤!
则将抛出以上 的异常“IllegalThreadStateException”
Thread.java
package com.wsm.thread;
/**
* 多线程的创建,方式一:继承于Thread类
* 1. 创建一个继承于Thread类的子类
* 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
* 3. 创建Thread类的子类的对象
* 4. 通过此对象调用start()
* <p>
* 例子:遍历100以内的所有的偶数
*
*/
//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
//2. 重写Thread类的run()
@Override
public void run() {
//循环输出100行数据
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
//不设置默认线程名,默认Tread-0 递增~
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3. 创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
t1.start();
//问题一:我们不能通过直接调用run()的方式启动线程。
// t1.run();
//问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
// t1.start();
//我们需要重新创建一个线程的对象
MyThread t2 = new MyThread();
t2.start();
//如下操作仍然是在main线程中执行的。
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
}
}
}
}
运行可以看到多线程,交替执行…
匿名子类的方式创建线程
匿名类,当程序只有一处地方使用到该类,则可以创建一个匿名子类 内存用完即丢,快速回收~
创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
public static void main(String[] args) {
//创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
//创建Thread类的匿名子类的方式
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
}
}
继承 Thread 类创建线程:
//使用此方式创建线程类
//此线程类需要继承 Thread类, 并重写类中 run()方法;
//因为Thread类中的 run()方法是,线程要执行的操作任务方法; 所以,线程要执行的操作代码需要写 在 run()方法中; //并通过子类实例.调用 start();方法来启动线程;
操作:
A 类 继承Thread类;
A a = new A(); //创建 A 类实例/线程
a.start(); //启动 线程.....
//创建两个线程就是 A a1 = new A();
//每 new A(); 创建一个即每个线程都是独立类对象..只有static修饰的类属性才共享...
RunnableTest.java
package com.wsm.thread;
/**
* 创建多线程的方式二:实现Runnable接口
* 1. 创建一个实现了Runnable接口的类
* 2. 实现类去实现Runnable中的抽象方法:run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()
*
* 比较创建线程的两种方式。
* 开发中:优先选择:实现Runnable接口的方式
* 原因:1. 实现的方式没有类的单继承性的局限性
* 2. 实现的方式更适合来处理多个线程有共享数据的情况。
*
* 联系:public class Thread implements Runnable
* 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
*/
//1. 创建一个实现了Runnable接口的类
class MThread implements Runnable {
//2. 实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class RunnableTest{
public static void main(String[] args) {
//3. 创建实现类的对象
MThread mThread = new MThread();
//4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
t1.setName("线程1");
//5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
t1.start();
//再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
类内部的数据是可以共享的!
实现Runnable接口:
//因为 Java是单继承的
//所以如果 一个类继承一个类,又要是 线程类,所以:还可以实现接口来 创建线程了;
//Runnable
//在java.lang; 包中 其中 有一个 抽象方法 run();
//通过实现 Runable 接口重写run(); 方法,方法中写代码完成线程要完成操作;
操作:
A 实现了 Runnable接口;
A a = new A(); //创建 A类对象 a;
Thread xc = new Thread(a); //创建 线程 xc;
xc.start(); // 启动线程;
....................
注意:
//如果想要在创建一个线程就不需要
new A();
//只需要在 Thread xc1 = new Thread(a); 即创建新的线程,但每次参数都是实例 a,即创建线程的属性值共享;
//不想共享某些属性,可以重新
A a1 new A();
Thread n = new Thread(a1);
//当然也可以使用:匿名内部类:匿名子类/匿名实现类形式, 调用启动线程; 前提这个线程只用一次;
多线程计算,100以内偶数和
CallableTest.Java
package com.wsm.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程的方式三:实现Callable接口。 --- JDK 5.0新增
*
* 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
* 1. call()可以有返回值的。
* 2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
* 3. Callable是支持泛型的
*/
//1.创建一个实现Callable的实现类
//class NumThread implements Callable<Integer> { //Callable<T> 支持泛型: 实现的call返回值类型,就得是 <T> 泛型指定的类型!
class NumThread implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
// public Integer call() throws Exception { //Integer 可以和 Object 进行自动类型转换!
public Object call() throws Exception { //“默认 Object类型”;
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class CallableTest {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
//FutureTask<T> 支持泛型
//futureTask.get(); 返回的就是 <T> 泛型类型, “默认 Object类型”;
FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
实现Callable 接口:
与使用Runnable相比, Callable功能更强大些
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助 FutureTask 类,比如获取返回结果;
Futrue 接口:
可以对具体Runnable、Callable任务的执行结果进行取消、查询是 否完成、获取结果等
FutrueTask是Futrue接口的唯一的实现类...
FutureTask 实现类:
FutureTask 同时实现了Runnable, Future接口
它既可以作为 Runnable被线程执行, 又可以作为 Future得到 Callable的返回值;
操作:
A 实现了 Callable接口, "重写call方法()";
A a = new A(); "创建A类对象"
将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask<Objetc> futureTask = new FutureTask<Objetc>(a);
将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
...
之后还可以通过 Future接口实现类接口, 调用方法();
Object sum = futureTask.get(); "方法获取方法的返回值!"
开发中常用~
后面单独写一个文章…
线程不安全
线程不安全
ThreadWindow.java
package com.wsm.thread;
//继承Thread类完成窗口买票!
/**
* 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式
* 存在线程的安全问题,待解决。
*/
class Window extends Thread{
/* 使用 static修饰和不用static修饰 */
//private int ticket = 100;
private static int ticket = 100;
/**static
* static 修饰的属性,属于类,该类的所有的对象都共享
* 因为, 继承Thread 实现线程,没一次new 创建线程对象,内部属性不是static修饰, 每次都是一个新的对象100 这样三个窗口就相当于都有100个票
* 而, 不是一共由100 张票的了!
*/
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
public class ThreadWindow {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
使用Static 与 不使用Static
大家各买各的!
static 修饰的票数
三个窗口一共100张票线程不安全
Runnable 可以实现多线程类数据共享!
RunnableWindow.java
package com.wsm.thread;
//实习Runnable接口,实现窗口买票!
/**
* 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
* 存在线程的安全问题,待解决。
*/
class Window1 implements Runnable{
//Runnable实现线程,内部数据可以共享!
private int ticket = 100;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
public class RunnableWindow {
public static void main(String[] args) {
//创建Runnable 实现类
Window1 w = new Window1();
//根据w 创建三个线程,因为都是用的是通一个对象w 所以,内部的实例数据共享! 不需要static修饰可以实现票共享!
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
RunnableWindow举例, (ThreadWindow.java也一样)
Therad 中使用 static修饰
,使多线程数据共享: 三个窗口共买100个票
判断票数大于0进入,并将票100打印
还没有进行 --减减
B线程进来了,因为 票还没有 --
则,输出 100
C同上!买票
static
属性也是数据共享的当 A 符合条件开始取钱时,其它线程不可以参与进来,直到A操作完之后,其它线程才可以进来 即使 A线程阻塞也不可以改变; 必须A执行完毕;
采用: 线程同步来控制多线程执行 (两种方式:
ThreadWindow.Java
package com.wsm.sync;
/* 使用: 同步代码块; */
/**
* 例子:创建三个窗口卖票,总票数为100张.使用继承Thread接口的方式
*
* 1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
* 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
* 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他
* 线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
*
* 4.在Java中,我们通过同步机制,来解决线程的安全问题:
* 方式一:同步代码块
*
* synchronized(同步监视器){
* //需要被同步的代码
* }
* 说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
* 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
* 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
* 要求:多个线程必须要共用同一把锁。
* 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。 `但对于继承,Thread的类,不建议使用this!!`
* 方式二:同步方法。
* 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
* 5.同步的方式,解决了线程的安全问题。---好处
* 操作同步代码时,只能有一个线程参与,其他线程等待。
* 相当于是一个单线程的过程,效率低。(很多时候,安全大于一切!) ---局限性;
*/
class Window extends Thread{
private static int ticket = 100;
@Override
public void run() {
while(true){
synchronized (Window.class){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class ThreadWindow {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
ok,执行线程安全了!
Synchronized 同步代码块内的, 多线程操作会一步步的执行…一次只有一个线程可以操作!
同步代码块:synchronized (中:同步 读:C抠奈zs
使用: synchronized 关键字修饰代码块,称:"同步代码块"
写法:
synchronized(同步建视器){ 需要同步的代码.... };
//操作共享的数据即为,需要被同步的代码:
//卡余额500就是共享数据,线程A B都可以访问...且互相影响; 把存在这些共享数据的操作都包裹在 synchronized(Object){ 代码块 };
同步监视器: (俗称: 锁🔒
"锁",可以是任何对象都可以,但必须得有,
注意:
多线程情况下,要求多个线程用的是同一把锁(对象) `多个人轮流带钥匙去仓库拿东西,仓库的锁得是同一个锁🔒,不然打不开门🚪!`
实现Runnable接口,的多线程可以使用: this "因为Runnable实现类对象可以创建多个线程,this表示的是同一个对象!"
继承Thread类,可以使用: static静态对象 或 类.class; 确保是同一个对象(锁) "继承Therad的实现类,没new 就是一个新的线程,this表示的是不同的对象!"
RunnableWindow.java
package com.wsm.sync;
/* 使用: 同步方法解决; */
class Window1 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show(){//同步监视器:this
//synchronized (this){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
//}
}
}
public class RunnableWindow {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
同步代码方法
其实就是在: 同步代码块的基础上,把同步代码块,定义成了个方法...让重写run(); 内部调用..
写法:
public synchronized void 方法名(){ ..需要同步的代码.. };
//Runnable实现,没有指定 锁(对象),其实就是默认this...
public static synchronized void 方法名(){ ..需要同步的代码.. };
//Thread 继承,没有指定 锁(对象),方法是静态的了,就是默认锁为:类.Class 为当前的类对象了;
注意:
public synchronized void 方法名(){ ..需要同步的代码.. };
Runnable实现,没有指定 锁(对象),其实就是默认this…public static synchronized void 方法名(){ ..需要同步的代码.. };
Thread 继承,没有指定 锁(对象),方法是静态的了,就是默认锁为:类.Class 为当前的类对象了;
RunnableLock.Java
package com.wsm.lock;
import java.util.concurrent.locks.ReentrantLock;
/* RunnableLock 实现Lock锁 */
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
//2.调用锁定方法lock()
//以下的操作,只能允许单线程同行操作... 一个线程调用lock() 除非调用 unlock() 不然其它线程,不能在进入锁执行!
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁方法: unlock()
lock.unlock();
}
}
}
}
public class RunnableLock {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
Lock(锁)
从JDK 5.0开始,Java提供了更强大的线程同步机制
通过显式定义:同步锁对象来实现同步
同步锁使用Lock对象充当
Lock是JDk5.0 新增的一个接口 Java.util.concurrent.locks.Lock
是控制多个线程对共享资源进行访问的工具
锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象 加锁, "线程开始访问共享资源之前应先获得Lock对象"
ReentrantLock类实现了Lock接口 (中:重入 读:瑞啊吹特
语法:
// 创建 ReentrantLock类对象;
ReentrantLock lock = new ReentrantLock();
//对象调用.lock();方法手动给指定代码上锁, 改对象调用之后,下面的方法只允许单线程通行了 ”一次只允许一个线程执行...“
lock.lock();
...需要被同步的代码...
//对象调用.unlock();方法手动开锁: ”其它线程才可以进行改lock() 进行使用!“
lock.unlock();
注意:
//因为,
lock(); 手动上锁
unlock(); 需要 unlock(); 手动关锁,不然锁资源一直不释放会导致死锁 🔒
//为了避免这个问题,建议使用 try-finall 保证, 锁资源的回收
ReentrantLock lock = new ReentrantLock();
try{
lock.lock();
...需要被同步的代码...
}finally(
lock.unlock();
)
lock 锁挺好用的, 只需要将 数据共享的操作代码
进行 lock()
unlock()
即可~
因为:没有synchronized 所以也没有 wait() 不用考虑… 最开始学习时候,看的多了,总是搞混..记乱感觉很难!
相同:
不同:
自动的释放同步监视器
执行顺序
Lock > Synchronized代码块 > Synchronized方法
线程的通信,别被这个名词给吓到了, 归根结底就是多个线程之间的
操作共享数据,数据安全的一个通信过程…
这里使用 synchronized 来进行线程通信,lock 有自己的方式…
CommunicationTest.Java
package com.wsm.threadsync;
/**
* 线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印
*
* 涉及到的三个方法:
* wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
* notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
* notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
*
* 说明:
* 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
* 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
* 否则,会出现IllegalMonitorStateException异常
* 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
*
* 面试题:sleep() 和 wait()的异同?
* 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
* 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
* 2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
* 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
*/
class Number implements Runnable{
private int number = 1;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj) {
//随机唤醒一个级别高的线程
obj.notify();
if(number <= 100){
try {
//使当前线程休眠,但是不释放锁,其它线程任然在 synchronized 锁,外面等待... (需要处理异常!
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印 ++
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得调用如下wait()方法的线程进入阻塞状态, 但会释放锁, 其它线程可以进入方法... 需要 notify() notifyall() 才能唤醒!
//作用:
//为了实现, 多个线程互相通信,A线程休眠,进入锁,并唤醒阻塞线程,让他完成之后的步骤,因为我进入锁了,其它线程又进不来了...
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
交替执行,并操作共享数据!
Java.lang.IllegalMonitorStateException异常
因此这三个方法只能在Object类中声明!
使当前线程进入
阻塞阶段
并释放锁🔒当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒
CPU时间片
对监视器的所有权后才能继续执行。随机唤醒
唤醒正在排队等待同步资源的线程中优先级最高者结束等待线程通信小Demo
开启两个线程,循环输出 100 内的数字
创建:线程A 线程B
1.线程A进行锁🔒, 立刻调用 notify(); 唤醒一个堵塞线程!
2.判断数字是否还在100内~进入: if{}
3.线程A休眠sleep() 10毫秒~
4.打印当前数 并 ++
5.调用 wait(); 线程进入阻塞,并释放了锁... (为了,让线程B 获得锁...
6.B获得锁: 因为A "进入了阻塞,并释放了锁" B获得锁"立刻调用 notify(); 唤醒了堵塞A线程!"
7.B执行了: sleep() 进入了休眠~ "但,sleep() 线程进入阻塞,持有锁...A还是进不来~"
8.B调用 wait(); 线程进入阻塞,并释放了锁...
9.A获得了锁,一次循环执行结束...开始下次循环
10.A唤醒B
11.A休眠 不释放锁...
12.A打印 ++
13.A阻塞 释放锁
14.B获得锁,继续执行 🙃
一直循环, 值则跳出~while() run() 方法结束!
只要记住!
线程调用了 wait() 方法之后,它就不在执行了! 并且也没有了锁🔒!
就处于休眠状态:只有其它线程调用notify() / notifyall()
才能恢复!带锁的方法/代码块 一次只能有一个线程使用!
避免多线程同时操作问题!
相同点:
不同点: