Java 中线程同步机制synchronized,互斥锁,死锁,释放锁的详解

简介: Java 中线程同步机制synchronized,互斥锁,死锁,释放锁的详解

一、线程同步机制synchronized的理解

二、synchronized的具体使用

下面可以通过同步机制,解决多线程卖票,出现的超卖问题,代码如下

public class SellTicket {
    public static void main(String[] args) {
//        SellTicket01 sellTicket01 = new SellTicket01();
//        SellTicket01 sellTicket02 = new SellTicket01();
//        SellTicket01 sellTicket03 = new SellTicket01();
//
//        //这里会出现超卖现象
//        sellTicket01.start();//启动售票线程
//        sellTicket02.start();//启动售票线程
//        sellTicket03.start();//启动售票线程
        /*
        输出结果:
        窗口 Thread-0 售出一张票剩余票数=2
        窗口 Thread-1 售出一张票剩余票数=-1
        售票结束
        窗口 Thread-0 售出一张票剩余票数=-2
        售票结束
        窗口 Thread-2 售出一张票剩余票数=0
        售票结束
         */
//        System.out.println("----使用实现接口的方式来售票----");
//        SellTicket02 sellTicket02 = new SellTicket02();
//        new Thread(sellTicket02).start();//第1个线程-窗口
//        new Thread(sellTicket02).start();//第2个线程-窗口
//        new Thread(sellTicket02).start();//第3个线程-窗口
        /*
        输出结果
        窗口 Thread-0 售出一张票剩余票数=2
        窗口 Thread-2 售出一张票剩余票数=1
        窗口 Thread-2 售出一张票剩余票数=0
        售票结束
        窗口 Thread-0 售出一张票剩余票数=-1
        售票结束
        窗口 Thread-1 售出一张票剩余票数=-2
        售票结束
        */
        System.out.println("----使用线程同步的方式来解决超卖票的情况----");
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();//第1个线程-窗口
        new Thread(sellTicket03).start();//第2个线程-窗口
        new Thread(sellTicket03).start();//第3个线程-窗口
        /*
        窗口 Thread-0 售出一张票剩余票数=6
        窗口 Thread-0 售出一张票剩余票数=5
        窗口 Thread-0 售出一张票剩余票数=4
        窗口 Thread-0 售出一张票剩余票数=3
        窗口 Thread-0 售出一张票剩余票数=2
        窗口 Thread-0 售出一张票剩余票数=1
        窗口 Thread-2 售出一张票剩余票数=0
        售票结束...
        售票结束...
        售票结束...
         */
    }
}
//使用Thread方式
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//让多个线程共享
    @Override
    public void run() {
        while (true) {
            //在判断这个条件的时候,三个线程会同时进来 当ticketNum等于1时,
            //三个线程都进来了,会出现超卖的情况
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                break;
            }
            //休眠50毫秒,模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + "剩余票数=" + (--ticketNum));
        }
    }
}
//实现接口方式
class SellTicket02 implements Runnable {
    private int ticketNum = 100;
    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                break;
            }
            //休眠50毫秒,模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + "剩余票数=" + (--ticketNum));
        }
    }
}
//实现接口方式,使用synchronized实现线程同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;
    private boolean loop = true;
    public synchronized void sell() {//同步方法,在同一时刻,只能有一个线程来执行sell方法
        if (ticketNum <= 0) {
            System.out.println("售票结束...");
            loop = false;
            return;
        }
        //休眠50毫秒,模拟
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                + "剩余票数=" + (--ticketNum));
    }
    @Override
    public void run() {
        while (loop) {
            sell();//sell方法是一个同步方法
        }
    }
}

分析同步原理

三、互斥锁的介绍

下面演示在代码块中加锁,和方法上加锁,还是以上面的多线程卖票为例

//实现接口方式,使用synchronized实现线程同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;
    private boolean loop = true;
    Object object = new Object();//也可以用同一个对象,比如object,因为是三个线程共享一个object对象,满足三个线程共享一个对象
    //同步方法(静态的)的锁为当前类本身
    //1.public synchronized static void m1(){}锁 是加在SellTicket03.class
    //2.如果在静态方法中,实现一个同步代码块
    /*
        synchronized (SellTicket03.class) {
            System.out.println("m2");
        }
     */
    public synchronized static void m1() {
    }
    public static void m2() {
        synchronized (SellTicket03.class) {
            System.out.println("m2");
        }
    }
    //1. public synchronized void sell(){} 就是一个同步方法
    //2.也可以在代码块上写synchronized ,同步代码块,互斥锁还是在this对象
    public /*synchronized*/ void sell() {//同步方法,在同一时刻,只能有一个线程来执行sell方法
        synchronized (/*this*/object) {
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                loop = false;
                return;
            }
            //休眠50毫秒,模拟
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
                    + "剩余票数=" + (--ticketNum));
        }
    }
    @Override
    public void run() {
        while (loop) {
            sell();//sell方法是一个同步方法
        }
    }
}

互斥锁的注意细节如下

四、线程的死锁

public class DeadLock_ {
    public static void main(String[] args) {
        //模拟死锁现象
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A线程");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B线程");
        A.start();
        B.start();
    }
}
//线程
class DeadLockDemo extends Thread {
    static Object o1 = new Object();//保证多线程,共享一个对象,这里使用static
    static Object o2 = new Object();
    boolean flag;
    public DeadLockDemo(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
        //下面业务逻辑分析
        //1.如果flag为true,线程A就会先得到/持有 o1 对象锁,然后尝试去获取o2对象锁
        //2.如果线程A 得不到o2对象锁,就会Blocked
        //3.如果flag为false,线程B就会先得到/持有 o2 对象锁,然后尝试去获取o1对象锁
        //2.如果线程B 得不到o1对象锁,就会Blocked
        if (flag) {
            synchronized (o1) {//对象互斥锁,下面是同步代码
                System.out.println(Thread.currentThread().getName() + " 进入1");
                synchronized (o2) {//这里获得li对象的监视权
                    System.out.println(Thread.currentThread().getName() + "进入2");
                }
            }
        } else {
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + " 进入3");
                synchronized (o1) {//这里获得li对象的监视权
                    System.out.println(Thread.currentThread().getName() + "进入4");
                }
            }
        }
    }
}

输出结果

B线程 进入3
A线程 进入1
之后就卡在这里了,写代码时一定要避免

下面操作会释放锁

下面操作不会释放锁

线程相关的练习题如下

代码如下

public class HomeWork01 {
    public static void main(String[] args) {
        A a = new A();
        a.start();
        B b = new B(a);
        b.start();
    }
}
class A extends Thread {
    private boolean loop = true;
    public void setLoop(boolean loop) {
        this.loop = loop;
    }
    @Override
    public void run() {
        while (loop) {
            System.out.println((int) (Math.random() * 100 + 1));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("a线程退出...");
    }
}
class B extends Thread {
    private A a;
    public B(A a) {//构造器中,传入A类对象
        this.a = a;
    }
    @Override
    public void run() {
        while (true) {
            //接收到用户的输入
            System.out.println("请输入命令");
            Scanner scanner = new Scanner(System.in);
            char c = scanner.next().toUpperCase().charAt(0);
            if (c == 'Q') {
                //以通知的方式结束A线程
                a.setLoop(false);
                break;
            }
        }
        System.out.println("b线程退出...");
    }
}

输出结果如下

85
请输入命令
8
41
79
81
75
41
29
Q
b线程退出...
a线程退出...

练习题二

代码如下

public class HomeWork02 {
    public static void main(String[] args) {
        Card card = new Card();
        new Thread(card).start();
        new Thread(card).start();
    }
}
//编程取款的线程
//1.因为这里涉及到多个线程共享线程资源,所以我们使用实现Runnable方式
class Card implements Runnable {
    private boolean loop = true;
    private int balance = 10000;
    @Override
    public void run() {
        while (loop) {
            //解读:
            //1.这里使用synchronized实现了线程同步
            //2.当多个线程执行到这里时,就会去争夺this对象锁
            //3.哪个对象争夺到(获取)this对象锁,就执行synchronized代码块,执行完成后,会释放this对象锁
            //4.争夺不到this对象锁,就blocked,准备继续争夺
            //5.this对象锁 是非公平锁
            synchronized (this) {
                if (balance < 1000) {
                    System.out.println("余额不足..");
                    loop = false;
                    return;
                }
                System.out.println(Thread.currentThread().getName() + " 取出1000 剩余余额为:" + (balance -= 1000));
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果如下

Thread-0 取出1000 剩余余额为:9000
Thread-1 取出1000 剩余余额为:8000
Thread-1 取出1000 剩余余额为:7000
Thread-0 取出1000 剩余余额为:6000
Thread-1 取出1000 剩余余额为:5000
Thread-0 取出1000 剩余余额为:4000
Thread-1 取出1000 剩余余额为:3000
Thread-0 取出1000 剩余余额为:2000
Thread-1 取出1000 剩余余额为:1000
Thread-0 取出1000 剩余余额为:0
余额不足..
余额不足..


目录
相关文章
|
2天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
15 5
Java反射机制:解锁代码的无限可能
|
1天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
9 3
|
1天前
|
安全 Java UED
深入理解Java中的异常处理机制
【10月更文挑战第25天】在编程世界中,错误和意外是不可避免的。Java作为一种广泛使用的编程语言,其异常处理机制是确保程序健壮性和可靠性的关键。本文通过浅显易懂的语言和实际示例,引导读者了解Java异常处理的基本概念、分类以及如何有效地使用try-catch-finally语句来处理异常情况。我们将从一个简单的例子开始,逐步深入到异常处理的最佳实践,旨在帮助初学者和有经验的开发者更好地掌握这一重要技能。
7 2
|
3天前
|
Java 数据库连接 开发者
Java中的异常处理机制####
本文深入探讨了Java语言中异常处理的核心概念,通过实例解析了try-catch语句的工作原理,并讨论了finally块和throws关键字的使用场景。我们将了解如何在Java程序中有效地管理错误,提高代码的健壮性和可维护性。 ####
|
5天前
|
安全 Java 程序员
深入浅出Java中的异常处理机制
【10月更文挑战第20天】本文将带你一探Java的异常处理世界,通过浅显易懂的语言和生动的比喻,让你在轻松阅读中掌握Java异常处理的核心概念。我们将一起学习如何优雅地处理代码中不可预见的错误,确保程序的健壮性和稳定性。准备好了吗?让我们一起踏上这段旅程吧!
20 6
|
3天前
|
存储 运维 Java
💻Java零基础:深入了解Java内存机制
【10月更文挑战第18天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
15 1
|
5天前
|
Java 开发者 UED
Java中的异常处理机制及其重要性
【10月更文挑战第20天】 在Java编程中,异常处理是确保程序健壮性的关键。本文将探讨Java中的异常处理机制,包括其定义、类型、抛出和捕获异常的方法,以及如何自定义异常。通过实例说明,我们将展示异常处理在实际编程中的应用,帮助读者理解其在提高代码质量和稳定性方面的重要性。
10 0
|
21天前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
36 1
C++ 多线程之初识多线程
|
6天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
11 3
|
6天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
9 2