多线程Synchronized锁的使用与线程之间的通讯

这篇具有很好参考价值的文章主要介绍了多线程Synchronized锁的使用与线程之间的通讯。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、什么是线程安全问题

多线程同时对同一个全局变量做写操作,可能会受到其他线程的干扰,就会发生线程安全问题。

Java中的全局变量是存放在堆内存中的,而堆内容对于所有线程来说是共享的。

比如下面一个简单的代码案例:


public class ThreadCount implements Runnable{
    private int count = 10;

    @Override
    public void run() {
        while (true) {
            if (count > 1) {
                try {
                	// 模拟两个线程的阻塞状态
                    Thread.sleep(30);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                count--;
                System.out.println(Thread.currentThread().getName() + " == " + count);
            }
        }
    }

    public static void main(String[] args) {
        ThreadCount threadCount = new ThreadCount();
        // 启动两个线程执行任务 
        new Thread(threadCount).start();
        new Thread(threadCount).start();
    }
}

代码比较简单,我们看下面控制台的打印:

Thread-1 == 9
Thread-0 == 9
Thread-0 == 7
Thread-1 == 7
Thread-1 == 6
Thread-0 == 6
Thread-1 == 5
Thread-0 == 5
Thread-1 == 4
Thread-0 == 4
Thread-0 == 2
Thread-1 == 2
Thread-1 == 1
Thread-0 == 1

可以看到两个线程之间产生了冲突,产生了线程安全问题。

二、如何解决线程安全问题

如何解决线程安全问题呢?或者说如何实现线程的同步呢?
核心思想:加锁

在同一个JVM中,多个线程需要竞争锁的资源。

那么哪些代码需要加锁呢?
可能会发生线程安全性问题的代码需要加锁。

还是上面的例子,我们在哪里加锁合适呢?
(1)锁加在run()方法上

@Override
    public synchronized void run() {
        while (true) {
            if (count > 1) {
                try {
                	// 模拟两个线程的阻塞状态
                    Thread.sleep(30);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                count--;
                System.out.println(Thread.currentThread().getName() + " == " + count);
            }
        }
    }

这样可不可以呢?是可以的,但是我们来思考一个问题,如果synchronized加在了run()方法上,那么该执行过程是单线程还是多线程呢?
答案是单线程。我们可以看到以下控制台打印:

Thread-0 == 9
Thread-0 == 8
Thread-0 == 7
Thread-0 == 6
Thread-0 == 5
Thread-0 == 4
Thread-0 == 3
Thread-0 == 2
Thread-0 == 1

这是为什么呢?
原因是因为synchronized加在了run()方法上,获取到锁的线程不会释放锁,会一直持有锁,所以方法的执行就变成了单线程的;没有获取锁的线程,如果一直没有获取锁,中间需要经历一个锁的升级过程,最后会一直阻塞等待锁的释放。

(2)锁加在操作共享资源的代码上

@Override
    public void run() {
        while (true) {
            if (count > 1) {
                try {
                    Thread.sleep(30);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                synchronized (this) {
                    count--;
                    System.out.println(Thread.currentThread().getName() + " == " + count);
                }
            }
        }
    }

直接看控制台的输出结果:

Thread-1 == 9
Thread-0 == 8
Thread-0 == 7
Thread-1 == 6
Thread-1 == 5
Thread-0 == 4
Thread-0 == 3
Thread-1 == 2
Thread-1 == 1
Thread-0 == 0

这就解决了线程安全问题。
过程就是第一个线程和第二个线程同时去竞争this锁,假设第一个线程获取到锁,那么第二个线程就会阻塞等待,等第一个线程执行完操作资源后,释放锁之后才会获取到锁,执行操作。

三、synchronized锁的基本用法

1.修饰代码块,指定加锁对象,对指定对象加锁,进入同步代码块前要获取 给定对象 的锁。
2.修饰实例方法,作用于当前实例加锁,进入同步代码块前要获取 当前实例 的锁。
3.修饰静态方法,作用于当前类对象(当前类.class)加锁,进入同步代码块前要获得 当前类对象 的锁。

1、修饰代码块(this锁)

public class ThreadCount implements Runnable {
    private int count = 10;

    @Override
    public void run() {
        while (true) {
            sub();
        }
    }
    public void sub() {
        if (count > 1) {
            try {
                Thread.sleep(30);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            // 修饰代码块,即this锁,进入同步代码块之前需要获取对象锁
            synchronized (this) {
                count--;
                System.out.println(Thread.currentThread().getName() + " == " + count);
            }
        }
    }

    public static void main(String[] args) {
    	// 同一个实例,执行不同线程,是线程安全的,不会出现问题
        //ThreadCount threadCount = new ThreadCount();
        //new Thread(threadCount).start();
        //new Thread(threadCount).start();
        // 不同实例,执行不同线程,会出现线程安全问题,这就是对象锁
        // 代码比较简单,可自行执行测试
        ThreadCount threadCount1 = new ThreadCount();
        ThreadCount threadCount2 = new ThreadCount();
        new Thread(threadCount1).start();
        new Thread(threadCount2).start();
    }
}

2、修饰实例方法(this锁)

	@Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(30);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            sub();
        }
    }
    // 将synchronized加在实例方法上,则使用的还是this锁
    public synchronized void sub() {
        if (count > 1) {
            count--;
            System.out.println(Thread.currentThread().getName() + " == " + count);
        }
    }

3、修饰静态方法

	private static int count = 10;
	
	...
	// 使用当前的  类名.class 锁
	public static synchronized void sub() {
        if (count > 1) {
            count--;
            System.out.println(Thread.currentThread().getName() + " == " + count);
        }
    }

相当于

	public static  void sub() {
        synchronized(ThreadCount.class) {
            if (count > 1) {
                count--;
                System.out.println(Thread.currentThread().getName() + " == " + count);
            }
        }
    }

四、死锁问题

(1)死锁

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。如下图所示:

多线程Synchronized锁的使用与线程之间的通讯

(2)死锁产生的必要条件

产生死锁的必要条件:

  • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  • 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

(3)诊断synchronized死锁

以下这段代码会产生死锁问题,我们当作测试案例:

public class DeadlockThread implements Runnable {
    private int count = 1;
    private final String lock = "lock";

    @Override
    public void run() {
        while (true) {
            count++;
            if (count % 2 == 0) {
                synchronized (lock) {
                    a();
                }
            } else {
                synchronized (this) {
                    b();
                }
            }
        }
    }

    public synchronized void a() {
        System.out.println(Thread.currentThread().getName() + ",a方法...");
    }

    public void b() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + ",b方法...");
        }
    }

    public static void main(String[] args) {
        DeadlockThread deadlockThread = new DeadlockThread();
        Thread thread1 = new Thread(deadlockThread);
        Thread thread2 = new Thread(deadlockThread);
        thread1.start();
        thread2.start();
    }
}

诊断死锁我们可以使用jdk8自带的诊断工具jconsole.exe

多线程Synchronized锁的使用与线程之间的通讯
如图,双击打开,选择对应的进程。
多线程Synchronized锁的使用与线程之间的通讯

这里我们本地,不需要登录,直接选择不安全方式连接。
多线程Synchronized锁的使用与线程之间的通讯
连接成功之后,点击线程,点击检测死锁,该工具就可以帮我们自动检测到产生死锁的线程,如图:
多线程Synchronized锁的使用与线程之间的通讯
还能够显示出死锁线程的具体信息,锁的拥有者,以及对应的代码行数:
多线程Synchronized锁的使用与线程之间的通讯

五、线程如何实现同步

线程如何实现同步?
或者说线程如何保证线程安全性问题?

  • 使用synchronized锁,JDK1.6开始,锁的升级过程
偏向锁 --> 轻量级锁 --> 重量级锁
  • 使用Lock锁(JUC),需要自己实现锁的升级过程,底层是基于AQS+CAS实现
  • 使用ThreadLocal,但是需要注意内存泄漏的问题
  • 原子类CAS非阻塞式

六、多线程之间的通信

1、等待/通知机制

等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object 上,方法如下:
1.notify():通知一个在对象上等待的线程,使其从main()方法返回,而返回的前提是该线程获取到了对象的锁;
2.notifyAll():通知所有等待在该对象的线程;
3.wait():调用该方法的线程进入WAITING状态,只有等待其他线程的通知或者被中断,才会返回。需要注意调用wait()方法后,会主动释放对象的锁。

2、生产者和消费者模型

下面我们通过一个案例来展示生产者消费者模型,模拟过程为两个线程,一个输入线程一个输出线程,输入线程输入内容,输出线程立马打印:

public class ThreadTest {
	// 共享变量
    class Res {
        public String userName;
        public char sex;
    }

    /**
     *  输入线程
     */
    class InputThread extends Thread {
        private Res res;
        public InputThread(Res res) {
            this.res = res;
        }

        @Override
        public void run() {
            int count = 0;
            while (true) {
                if (count == 0) {
                    res.userName = "zal";
                    res.sex = '男';
                } else {
                    res.userName = "zzal";
                    res.sex = '女';
                }
                count = (count + 1) % 2;
            }
        }
    }

    /**
     * 输出线程
     */
    class OutPutThread extends Thread {
        private Res res;
        public OutPutThread(Res res) {
            this.res = res;
        }
        @Override
        public void run() {
            while (true) {
                System.out.println(res.userName + ", " + res.sex);
            }
        }
    }

    public static void main(String[] args) {
        new ThreadTest().print();
    }

    public void print() {
        // 全局对象
        Res res = new Res();
        // 输入线程
        InputThread inputThread = new InputThread(res);
        OutPutThread outPutThread = new OutPutThread(res);
        inputThread.start();
        outPutThread.start();
    }
}

然后我们看控制台输出打印,发现了问题。
多线程Synchronized锁的使用与线程之间的通讯
这就意味着该代码出现了线程安全性问题,那么为了解决线程安全性问题,我们就需要对线程进行加锁,那么锁哪些代码块呢?

肯定是锁Res对象。

代码改进如下:

public class ThreadTest {
    class Res {
        public String userName;
        public char sex;
    }

    /**
     *  输入线程
     */
    class InputThread extends Thread {
        private Res res;

        public InputThread(Res res) {
            this.res = res;
        }

        @Override
        public void run() {
            int count = 0;
            while (true) {
                synchronized (res) {
                    if (count == 0) {
                        res.userName = "zal";
                        res.sex = '男';
                    } else {
                        res.userName = "zzal";
                        res.sex = '女';
                    }
                }
                count = (count + 1) % 2;
            }
        }
    }

    /**
     * 输出线程
     */
    class OutPutThread extends Thread {
        private Res res;

        public OutPutThread(Res res) {
            this.res = res;
        }

        @Override
        public void run() {
            while (true) {
                synchronized (res) {
                    System.out.println(res.userName + ", " + res.sex);
                }
            }
        }
    }

    public static void main(String[] args) {
        new ThreadTest().print();
    }

    public void print() {
        // 全局对象
        Res res = new Res();
        // 输入线程
        InputThread inputThread = new InputThread(res);
        OutPutThread outPutThread = new OutPutThread(res);
        inputThread.start();
        outPutThread.start();
    }
}

我们对输入线程和输出线程的res对象都加了锁,并且锁住的是同一个对象,这下不会再出现线程安全问题了,运行截图如下:

多线程Synchronized锁的使用与线程之间的通讯
可是又出现了新的问题,那就是输入和输出一片一片的打印,并不能实现我们输入线程输入,输出线程立马输出的功能。

出现问题的原因就是当输入线程获取锁的时候,那么输出线程就不能获取锁,就会进入阻塞状态,而当输出线程进行输出的时候,输入线程就不能输入了,所以就会出现这种现象。

最后,我们使用生产者和消费者模型进行改进代码,代码如下:文章来源地址https://www.toymoban.com/news/detail-443488.html

public class ThreadTest {
    class Res {
        public String userName;
        public char sex;

        /**
         * flag 标志
         *  当flag = false时,输入线程输入,输出线程等待
         *  当flag = true时,输出线程输出,输入线程等待
         */
        public boolean flag = false;
    }

    /**
     *  输入线程
     */
    class InputThread extends Thread {
        private Res res;

        public InputThread(Res res) {
            this.res = res;
        }

        @Override
        public void run() {
            int count = 0;
            while (true) {
                synchronized (res) {
                    if (res.flag) {
                        try {
                            res.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    if (count == 0) {
                        res.userName = "zal";
                        res.sex = '男';
                    } else {
                        res.userName = "zzal";
                        res.sex = '女';
                    }
                    // 输出线程可以输出值
                    res.flag = true;
                    // 唤醒输出线程
                    res.notify();
                }
                count = (count + 1) % 2;
            }
        }
    }

    /**
     * 输出线程
     */
    class OutPutThread extends Thread {
        private Res res;

        public OutPutThread(Res res) {
            this.res = res;
        }

        @Override
        public void run() {
            while (true) {
                synchronized (res) {
                    // 如果res.flag = false,则输出线程主动释放锁
                    // 同时会阻塞线程
                    if (!res.flag) {
                        try {
                            res.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    System.out.println(res.userName + ", " + res.sex);
                    res.flag = false;
                    // 唤醒输出线程
                    res.notify();
                }
            }
        }
    }

    public static void main(String[] args) {
        new ThreadTest().print();
    }

    public void print() {
        // 全局对象
        Res res = new Res();
        // 输入线程
        InputThread inputThread = new InputThread(res);
        OutPutThread outPutThread = new OutPutThread(res);
        inputThread.start();
        outPutThread.start();
    }
}

到了这里,关于多线程Synchronized锁的使用与线程之间的通讯的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C# 中多线程锁的使用经验

    C# 中多线程锁的使用经验:全局锁,实例锁         private static object _exeLock = new object();        static 静态的是全应用程序的资源。如果在一个类里定义了这样一个锁,你在调用使用这个类的时候,是NEW了一个对象,并把这个对象给了一个静态全局变量中保存。这时这个锁

    2024年03月14日
    浏览(55)
  • 【Java|多线程与高并发】线程安全问题以及synchronized使用实例

    Java多线程环境下,多个线程同时访问共享资源时可能出现的数据竞争和不一致的情况。 线程安全一直都是一个令人头疼的问题.为了解决这个问题,Java为我们提供了很多方式. synchronized、ReentrantLock类等。 使用线程安全的数据结构,例如ConcurrentHashMap、ConcurrentLinkedQueue等

    2024年02月09日
    浏览(44)
  • 06 为什么需要多线程;多线程的优缺点;程序 进程 线程之间的关系;进程和线程之间的区别

    CPU、内存、IO之间的性能差异巨大 多核心CPU的发展 线程的本质是增加一个可以执行代码工人 多线程的优点 多个执行流,并行执行。(多个工人,干不一样的活) 多线程的缺点 上下文切换慢,切换上下文典型值1us vs 0.3ns/cycle CPU在执行A任务(A没有执行完)时,切换到任务B,需

    2024年02月14日
    浏览(48)
  • 【Linux从入门到精通】线程互斥与互斥锁的使用与原理详解

      上篇文章我们对线程 | 线程介绍线程控制介绍后,本篇文章将会对多线程中的线程互斥与互斥锁的概念进行详解。同时结合实际例子解释了可重入与不被重入函数、临界资源与临界区和原子性的概念。希望本篇文章会对你有所帮助。 文章目录 引入 一、重入与临界 1、1 可重

    2024年02月07日
    浏览(43)
  • springboot~jgroups实现节点间的通讯

    在 JGroups 中,集群(cluster)是一个由多个节点组成的逻辑实体,节点可以通过一个共享的集群名称来进行连接和通信。这个集群名称可以在配置中指定或在运行时动态创建。 JGroups 提供了多种方式来创建集群,并使节点能够加入到相同的集群中。下面是一些常见的方式: 静

    2024年02月08日
    浏览(30)
  • 即时通讯:短轮询、长轮询、SSE 和 WebSocket 间的区别

    在现代 Web 开发中,即时通讯已经成为许多应用程序的重要组成部分。为了实现即时通讯,开发人员通常使用不同的技术和协议。本文将介绍四种常见的即时通讯实现方法:短轮询、长轮询、SSE(服务器发送事件)和 WebSocket,并探讨它们之间的区别。 短轮询是最简单的即时通

    2024年02月12日
    浏览(42)
  • cisco三层交换机怎么设置实现vlan间的通讯?

    二层交换机是不能实现vlan间通讯的,这就需要三层路由功能,而3560默认情况下是二层交换机,这就需要多交换机进行一些配置,实现vlan间的通讯,下面通过具体实验对其进行讲解,如需具体学习,谢谢大家的支持。 1、打开思科模拟软件----按如图配置拓扑图---将pc0、pc1、

    2024年02月08日
    浏览(41)
  • java线程-synchronized详解

    解决线程原子性问题,最常见的手段就是加锁,Java提供了两种加锁的方式,一个synchronized隐式锁,另外一个是通过J.U.C框架提供的Lock显式加锁。本文主要介绍一个Synchronized的实现方式。 synchronized解决的是多个线程之间访问资源的同步性,synchronized 翻译为中文的意思是

    2024年02月10日
    浏览(44)
  • 【多线程进阶】synchronized 原理

    在前面章节中, 提到了多线程中的锁策略, 那么我们 Java 中的锁 synchronized 背后都采取了哪些锁策略呢? 又是如何进行工作的呢? 本节我们就来谈一谈. 关注收藏, 开始学习吧🧐 在 Java 中, synchronized 具有以下特性(这里以 JDK 1.8 为例): 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲

    2024年02月07日
    浏览(34)
  • 【JavaEE】多线程之线程安全(synchronized篇),死锁问题

    线程安全问题 观察线程不安全 线程安全问题的原因  从原子性入手解决线程安全问题 ——synchronized synchronized的使用方法  synchronized的互斥性和可重入性 死锁 死锁的三个典型情况  死锁的四个必要条件  破除死锁 在前面的章节中,我们也了解到多线程为我们的程序带来了

    2024年02月01日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包