Java重修笔记 第五十六天 坦克大战(六)多线程基础 - 线程同步、死锁

devtools/2024/9/23 0:49:57/
  • 多线程同步机制

        多线程编程中,一些敏感数据可能会被多个线程同时访问造成数据混乱(例如票数),使用线程同步机制,通过锁对象(对象实例或类实例)的方式来保证该段代码在任意时刻,最多只能由一个线程来访问,从而保证了数据的安全性。

  • synchronized 实现线程同步
1. 修饰普通方法

        同步普通方法本质上锁的是:调用该方法的实例对象

        (1)不加锁,同一个对象多个线程一起执行,互不干扰
java">public class Synchronized {public static void main(String[] args) {// 不加锁,同一个对象多个线程一起执行,互不干扰MyClass01 myClass01 = new MyClass01();new Thread(myClass01).start();new Thread(myClass01).start();}
}class MyClass01 implements Runnable {// 两个线程同时进入 run 方法@Overridepublic void run() {method01();method02();}public void method01() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");}public void method02() {System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");}
}

运行结果:(结果不固定)

        (2)不加锁,不同对象多个线程一起执行,互不干扰
java">public class Synchronized {public static void main(String[] args) {// 不加锁,不同对象多个线程一起执行,互不干扰MyClass01 myClass0101 = new MyClass01();MyClass01 myClass0102 = new MyClass01();new Thread(myClass0101).start();new Thread(myClass0102).start();}
}class MyClass01 implements Runnable {// 两个线程同时进入 run 方法@Overridepublic void run() {method01();method02();}public void method01() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");}public void method02() {System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");}
}

运行结果:(结果不固定)

        (3)给普通方法加锁,锁住了同一个对象实例 this,线程阻塞
java">public class Synchronized03 {public static void main(String[] args) {// 给普通方法加锁,锁住了同一个对象实例 this,线程阻塞MyClass02 myClass02 = new MyClass02();new Thread(myClass02).start();new Thread(myClass02).start();}
}class MyClass02 implements Runnable {// 两个线程同时执行到 run 方法@Overridepublic void run() {method01();method02();}// 这个方法在两个线程中都锁住了 this 对象// 所以必须等第一个线程执行完 method01 方法, 下一个线程才能执行public synchronized void method01() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");}public void method02() {System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");}
}

运行结果:

        (4)给普通方法加锁,锁住了不同的对象实例,互不干扰
java">public class Synchronized03 {public static void main(String[] args) {// 给普通方法加锁,锁住不同的对象实例,MyClass02 myClass0201 = new MyClass02();MyClass02 myClass0202 = new MyClass02();new Thread(myClass0201).start();new Thread(myClass0202).start();}
}class MyClass02 implements Runnable {// 两个线程同时执行到 run 方法@Overridepublic void run() {method01();method02();}// 锁住两个不同的实例对象不会阻塞public synchronized void method01() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");}public void method02() {System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");}
}

运行结果:(结果不固定)

 2. 修饰静态方法

        同步静态方法本质上锁的是:调用该方法的类对象

        (1)不加锁,同一个对象多个线程一起执行,互不干扰
java">public class Synchronized03 {public static void main(String[] args) {// 不加锁,同一个对象多个线程一起执行,互不干扰MyClass03 myClass03 = new MyClass03();new Thread(myClass03).start();new Thread(myClass03).start();}
}class MyClass03 extends Thread {// 两个线程同时执行到 run 方法@Overridepublic void run() {method01();method02();}public static void method01() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");}public static void method02() {System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");}
}

运行结果:(结果不固定)

        (2)不加锁,不同对象多个线程一起执行,互不干扰
java">public class Synchronized03 {public static void main(String[] args) {// 不加锁,不同对象多个线程一起执行,互不干扰MyClass03 myClass0301 = new MyClass03();MyClass03 myClass0302 = new MyClass03();new Thread(myClass0301).start();new Thread(myClass0302).start();}
}class MyClass03 extends Thread {// 两个线程同时执行到 run 方法@Overridepublic void run() {method01();method02();}public static void method01() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");}public static void method02() {System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");}
}

运行结果:(结果不固定)

        (3)给静态方法加锁,锁住了同一个对象实例,也就是同一个类实例,线程阻塞
java">public class Synchronized03 {public static void main(String[] args) {// 给静态方法加锁,锁住了同一个对象实例,也就是同一个类实例,线程阻塞MyClass04 myClass04 = new MyClass04();new Thread(myClass04).start();new Thread(myClass04).start();}
}class MyClass04 implements Runnable {// 两个线程同时执行到 run 方法@Overridepublic void run() {method01();method02();}// 锁住了同一个类实例,线程阻塞public static synchronized void method01() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");}public static void method02() {System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");}
}

运行结果:

        (4)给静态方法加锁,锁住了不同的对象实例,但本质锁的是同一个类实例,线程阻塞
java">public class Synchronized03 {public static void main(String[] args) {// 给静态方法加锁,锁住了同一个对象实例,也就是同一个类实例,线程阻塞MyClass04 myClass04 = new MyClass04();new Thread(myClass04).start();new Thread(myClass04).start();}
}class MyClass04 implements Runnable {// 两个线程同时执行到 run 方法@Overridepublic void run() {method01();method02();}// 锁住了同一个类实例,线程阻塞public static synchronized void method01() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("method01 方法被 " + Thread.currentThread().getName() + " 执行...");}public static void method02() {System.out.println("method02 方法被 " + Thread.currentThread().getName() + " 执行...");}
}

运行结果:

3. 修饰代码块

        通过在 synchronized 关键字后面的对象(实例对象或类对象)来上锁,随便 new 个啥进去都行,只要保证不同线程中括号里的是同一个对象实例或类对象,那么就能成功上锁

        (1)不加锁,同一个对象多个线程一起执行,互不干扰
java">public class Synchronized04 {public static void main(String[] args) {// 不加锁,同一个对象多个线程一起执行,互不干扰MyClass05 myClass05 = new MyClass05();for (int i = 0; i < 5; i++) {new Thread(myClass05).start();}}
}class MyClass05 implements Runnable {@Overridepublic void run() {try {Thread.sleep(1000);System.out.println("代码块被 " + Thread.currentThread().getName() + " 执行...");} catch (InterruptedException e) {e.printStackTrace();}}
}

运行结果:(结果不固定)

        (2)加锁,但是每个线程传入的对象不一样,没锁住互不干扰
java">public class Synchronized04 {public static void main(String[] args) {// 加锁,但是每个线程传入的对象不一样,没锁住互不干扰for (int i = 0; i < 5; i++) {MyClass05 myClass05 = new MyClass05();new Thread(myClass05).start();}}
}class MyClass05 implements Runnable {@Overridepublic void run() {synchronized (this) {try {Thread.sleep(1000);System.out.println("代码块被 " + Thread.currentThread().getName() + " 执行...");} catch (InterruptedException e) {e.printStackTrace();}}}
}

运行结果:(结果不固定)

        (3)加锁,每个线程传入同样的对象,线程阻塞
java">public class Synchronized04 {public static void main(String[] args) {// 加锁,每个线程传入同样的对象,线程阻塞MyClass05 myClass05 = new MyClass05();for (int i = 0; i < 5; i++) {new Thread(myClass05).start();}}
}class MyClass05 implements Runnable {@Overridepublic void run() {synchronized (this) {try {Thread.sleep(1000);System.out.println("代码块被 " + Thread.currentThread().getName() + " 执行...");} catch (InterruptedException e) {e.printStackTrace();}}}
}

运行结果:

        (4)加锁,锁住类对象,线程阻塞
java">public class Synchronized04 {public static void main(String[] args) {// 加锁,锁住类对象,线程阻塞for (int i = 0; i < 5; i++) {MyClass05 myClass05 = new MyClass05();new Thread(myClass05).start();}}
}class MyClass05 implements Runnable {@Overridepublic void run() {synchronized (MyClass05.class) {try {Thread.sleep(1000);System.out.println("代码块被 " + Thread.currentThread().getName() + " 执行...");} catch (InterruptedException e) {e.printStackTrace();}}}
}

运行结果:

  • 实现上锁步骤

1. 找到需要上锁的部分

2. 用代码块或者方法封装起来

3. 让多个线程判断锁的对象为同一个即可

  • 线程死锁

        多个线程都占用了对方的锁资源,即 A 线程想要 B 线程持有的锁资源,而 B 线程想要 A 线程持有的锁资源,且双方互不释放,就会导致死锁的发生。

java">public class DeadLock01 {public static void main(String[] args) {DeadLockDemo deadLockDemo01 = new DeadLockDemo(true);deadLockDemo01.setName("线程 A ");DeadLockDemo deadLockDemo02 = new DeadLockDemo(false);deadLockDemo02.setName("线程 B ");deadLockDemo01.start();deadLockDemo02.start();}
}class DeadLockDemo extends Thread {static Object object1 = new Object(); // 保证不管有多少个实例对象, 都共享同一个 object1static Object object2 = new Object();boolean flag;public DeadLockDemo(boolean flag) {this.flag = flag;}@Overridepublic void run() {// 线程 A 传进来的 flag 为 true, 线程 B 同步传进来的 flag 为 false// 这样就会出现一个问题, 那就是线程 A 持有 object1 锁并在尝试获取 object2 锁// 但是线程 B 持有着 object2 锁同时尝试获取 object1 锁, 双方都持有对方想要的锁却不释放// 这样就造成了程序死锁的发生if (flag) {synchronized (object1) { // 加入对象互斥锁System.out.println(Thread.currentThread().getName() + "进入 object1 锁的代码块");synchronized (object2) {System.out.println(Thread.currentThread().getName() + "进入 object2 锁的代码块");}}} else {synchronized (object2) {System.out.println(Thread.currentThread().getName() + "进入 object2 锁的代码块");synchronized (object1) {System.out.println(Thread.currentThread().getName() + "进入 object1 锁的代码块");}}}}
}

运行结果:

  • 释放锁
1. 什么操作会释放锁资源
        (1)当这个同步代码块执行结束后,自动释放锁资源
        (2)遇到 break 或者 return 语句强制退出方法/代码块时,自动释放锁资源
        (3)同步代码块或同步方法中遇到未处理的 Error 或者 Exception 异常退出时,自动释放锁资源
        (4)通过 wait 方法来让当前线程暂停(join 方法底层调用 wait 方法),线程暂停后会释放锁资源
2. 什么操作不会释放锁资源
        (1)调用 sleep 、yield 方法时,不会释放锁资源
        (2)调用 suspend 方法将线程挂起,也不会释放锁资源

http://www.ppmy.cn/devtools/113274.html

相关文章

【Motion Forecasting】【摘要阅读】BANet: Motion Forecasting with Boundary Aware Network

BANet: Motion Forecasting with Boundary Aware Network 这项工作发布于2022年&#xff0c;作者团队来自于OPPO。这项工作一直被放在arxiv上&#xff0c;并没有被正式发表&#xff0c;所提出的方法BANet在2022年达到了Argoverse 2 test dataset上的SOTA水准。 Method BANet…

【Android 13源码分析】WindowContainer窗口层级-4-Layer树

在安卓源码的设计中&#xff0c;将将屏幕分为了37层&#xff0c;不同的窗口将在不同的层级中显示。 对这一块的概念以及相关源码做了详细分析&#xff0c;整理出以下几篇。 【Android 13源码分析】WindowContainer窗口层级-1-初识窗口层级树 【Android 13源码分析】WindowCon…

由于 Python 环境不一致导致的No module named ‘selenium‘

1. 检查 Python 和 pip 的路径是否一致 首先&#xff0c;确保 pip3 安装的 Selenium 是在当前使用的 Python 环境中。可以通过以下命令检查 python 和 pip3 的路径&#xff1a; which python which pip3在终端中运行这两个命令&#xff0c;并确保它们指向的是同一个 Python 环…

数据库常规操作

常用的 SQL 语法和操作&#xff1a; 数据定义语言&#xff08;DDL&#xff09; 1.创建数据库CREATE DATABASE database_name;2.删除数据库DROP DATABASE database_name;3.创建表CREATE TABLE table_name (column1 datatype constraints,column2 datatype constraints,...);4.删…

Linux环境基础开发工具使用(gcc/g++与makefile)

1.Linux编译器-gcc/g使用 1. 背景知识 接下来的操作&#xff0c;我以gcc为例&#xff0c;因为两者选项都是通用的&#xff0c;所以也就相当于间接学习了 1.预处理&#xff08;进行宏替换) 2.编译&#xff08;生成汇编) 3.汇编&#xff08;生成机器可识别代码&#xff09;…

宠物毛发对人体有什么危害?宠物空气净化器小米、希喂、352对比实测

作为一个呼吸科医生&#xff0c;我自己也养猫。软软糯糯的小猫咪谁不爱啊&#xff0c;在养猫的过程中除了欢乐外&#xff0c;也面临着一系列的麻烦&#xff0c;比如要忍耐猫猫拉粑粑臭、掉毛、容易带来细菌等等的问题。然而我发现&#xff0c;现在许多年轻人光顾着养猫快乐了&a…

新160个crackme - 057-bbbs-crackme04

运行分析 因软件版本老旧&#xff0c;需使用windows XP虚拟机运行有个SystemID&#xff0c;值为12345678需破解User ID和Password PE分析 yC壳&#xff0c;32位 OD手动脱壳 使用windows XP虚拟机&#xff0c;将程序拖入OD按一下F8&#xff0c;ESP变红&#xff0c;根据ESP定律设…

ArcGIS Pro SDK (十四)地图探索 2 地图图层

ArcGIS Pro SDK (十四)地图探索 2 地图图层 文章目录 ArcGIS Pro SDK (十四)地图探索 2 地图图层1 地图1.1 获取活动地图的名称1.2 清除活动地图中的所有选择1.3 以地图单位计算选择容差1.4 地图视图叠加控制2 图层2.1 选择目录中的所有要素图层2.2 闪烁所选功能2.3 检查图…