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
余额不足..
余额不足..


目录
相关文章
|
8月前
|
Arthas 监控 Java
Java死锁 如何定位?如何避免Java死锁?(图解+秒懂+史上最全)
Java死锁 如何定位?如何避免Java死锁?(图解+秒懂+史上最全)
Java死锁 如何定位?如何避免Java死锁?(图解+秒懂+史上最全)
|
存储 架构师 安全
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
锁状态bits1bit是否是偏向锁2bit锁标志位无锁状态对象的hashCode001偏向锁线程ID101轻量级锁指向栈中锁记录的指针000重量级锁指向互斥量的指针010尼恩提示,讲完 如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等优化手段 , 可以得到 120分了。如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等‌。JVM锁的膨胀、锁的内存结构变化相关的面试题,是非常常见的面试题。也是核心面试题。
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
|
安全 Java 开发者
Java并发迷宫:同步的魔法与死锁的诅咒
在Java并发编程中,合理使用同步机制可以确保线程安全,避免数据不一致的问题。然而,必须警惕死锁的出现,采取适当的预防措施。通过理解同步的原理和死锁的成因,并应用有效的设计和编码实践,可以构建出高效、健壮的多线程应用程序。
243 21
Java 中锁的主要类型
【10月更文挑战第10天】
372 59
|
缓存 Java
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
本文介绍了几种常见的锁机制,包括公平锁与非公平锁、可重入锁与不可重入锁、自旋锁以及读写锁和互斥锁。公平锁按申请顺序分配锁,而非公平锁允许插队。可重入锁允许线程多次获取同一锁,避免死锁。自旋锁通过循环尝试获取锁,减少上下文切换开销。读写锁区分读锁和写锁,提高并发性能。文章还提供了相关代码示例,帮助理解这些锁的实现和使用场景。
434 4
java中的公平锁、非公平锁、可重入锁、递归锁、自旋锁、独占锁和共享锁
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
345 7
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
699 3
|
Java 开发者
Java 中的锁是什么意思,有哪些分类?
在Java多线程编程中,锁用于控制多个线程对共享资源的访问,确保数据一致性和正确性。本文探讨锁的概念、作用及分类,包括乐观锁与悲观锁、自旋锁与适应性自旋锁、公平锁与非公平锁、可重入锁和读写锁,同时提供使用锁时的注意事项,帮助开发者提高程序性能和稳定性。
650 3
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
205 4
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
【10月更文挑战第6天】在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
124 2

热门文章

最新文章