当前位置: 首页 > article >正文

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

  • 多线程同步机制

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

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

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

        (1)不加锁,同一个对象多个线程一起执行,互不干扰
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 方法
    @Override
    public 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)不加锁,不同对象多个线程一起执行,互不干扰
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 方法
    @Override
    public 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,线程阻塞
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 方法
    @Override
    public 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)给普通方法加锁,锁住了不同的对象实例,互不干扰
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 方法
    @Override
    public 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)不加锁,同一个对象多个线程一起执行,互不干扰
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 方法
    @Override
    public 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)不加锁,不同对象多个线程一起执行,互不干扰
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 方法
    @Override
    public 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)给静态方法加锁,锁住了同一个对象实例,也就是同一个类实例,线程阻塞
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 方法
    @Override
    public 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)给静态方法加锁,锁住了不同的对象实例,但本质锁的是同一个类实例,线程阻塞
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 方法
    @Override
    public 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)不加锁,同一个对象多个线程一起执行,互不干扰
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 {

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
            System.out.println("代码块被 " + Thread.currentThread().getName() + " 执行...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

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

        (2)加锁,但是每个线程传入的对象不一样,没锁住互不干扰
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 {

    @Override
    public void run() {
        synchronized (this) {
            try {
                Thread.sleep(1000);
                System.out.println("代码块被 " + Thread.currentThread().getName() + " 执行...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

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

        (3)加锁,每个线程传入同样的对象,线程阻塞
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 {

    @Override
    public void run() {
        synchronized (this) {
            try {
                Thread.sleep(1000);
                System.out.println("代码块被 " + Thread.currentThread().getName() + " 执行...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

        (4)加锁,锁住类对象,线程阻塞
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 {

    @Override
    public 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 线程持有的锁资源,且双方互不释放,就会导致死锁的发生。

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(); // 保证不管有多少个实例对象, 都共享同一个 object1
    static Object object2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag) {
        this.flag = flag;
    }

    @Override
    public 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.kler.cn/news/307971.html

相关文章:

  • 『功能项目』怪物的有限状态机【42】
  • 神经网络卷积层和最大池化
  • 2024 年最佳 Chrome 验证码扩展,解决 reCAPTCHA 问题
  • 小程序——生命周期
  • STM32 移植FATFS时遇到ff_oem2uni函数未定义问题
  • SQLyou基础知识总结(带案例)
  • 3286、穿越网格图的安全路径
  • Elasticsearch之bool查询
  • Vue页面中实现自动播放报警音
  • Python实现Socket.IO的完整指南
  • JavaSE - 易错题集 - 006
  • 学懂C++(六十):C++ 11、C++ 14、C++ 17、C++ 20新特性大总结(万字详解大全)
  • Idea 中的一些配置
  • PointNet++改进策略 :模块改进 | Residual MLP | PointMLP,简化原本复杂的局部几何特征提取器,减少计算同时提高效率
  • 记录近期iOS开发几个报错及解决方案
  • 在线查看 Android 系统源代码 AOSPXRef and AndroidXRef
  • Spring 的循环依赖
  • Git提交类型
  • 3D云渲染农场为何怎么贵?主要消耗成本介绍
  • Vue路由二(嵌套多级路由、路由query传参、路由命名、路由params传参、props配置、<router-link>的replace属性)
  • 【GIS开发小课堂】写一个高德地图巡航功能的小DEMO
  • 遗传算法及其MATLAB实现
  • Leetcode 字母异位词分组
  • Error: ENOENT: no such file or directory, uv_cwd
  • JVM-内存区域
  • 新装mysql8 并开启外网连接
  • 理解DataLoader
  • Redis——常用数据类型hash
  • 华为地图服务功能概览 -- HarmonyOS自学7
  • 【LeetCode Hot 100】169. 多数元素