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

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 方法将线程挂起,也不会释放锁资源
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值