涨姿势了!原来这才是多线程正确实现方式

简介: 线程同步机制是一套适用于协调线程之间的数据访问机制,该机制可以保障线程安全java平台提供的线程同步机制包括:锁、volatile关键字、final关键字,static关键字、以及相关API如object.wait/object.notify

Java内存模型

网络异常,图片无法展示
|

网络异常,图片无法展示
|

线程同步

线程同步机制是一套适用于协调线程之间的数据访问机制,该机制可以保障线程安全

java平台提供的线程同步机制包括:锁、volatile关键字、final关键字,static关键字、以及相关API如object.wait/object.notify

锁概述

线程安全问题的产生前提是多个线程并发访问共享数据,将多个数据对共享数据的并发访问,转化为串行访问,即共享数据只能被一个线程访问,锁就是这种思路。

线程访问数据时必须先获得锁,获得锁的线程称为锁的持有线程,一个锁一次只能被一个线程持有,持有线程在获得锁之后和释放锁之前锁执行的代码称之为临界区。

锁具有排它性(Exclisive),即一个锁只能被一个线程持有,这种锁称为排它锁或者互斥锁。

网络异常,图片无法展示
|

JVM部分把锁分为内部锁和显示锁,内部锁通过Synchronized关键字实现,显示锁通过
java.concurrent.locks.Lock接口实现类实现的。

锁的作用

锁能够实现对共享数据的安全,保障线程的原子性,可见性与有序性。

锁是通过互斥保障原子性,一个锁只能被一个线程持有,这就保证了临界区的代码一次只能被一个线程执行,使得临界区代码所执行的的操作自然而然的具有不可分割的特性,既具备了原子性。

好比一条路段所有车辆都在跑,并发执行,在经过某一个路段的时候,多车道变为一车道,一次只能通过一辆车,由并发执行改为串行执行。

可见性是通过写线程冲刷处理器的缓存和读线程刷新处理器缓存这两个动作,锁的获得隐含着刷新处理器缓存的动作,锁的释放隐含着冲刷处理器缓存的动作。

锁能够保障有序性,写线程在临界区所执行的临界区看来像是完全按照源码顺序执行的。

锁的相关概念

可重入性:一个线程持有该锁的时候能够再次/多次申请该锁

如果一个线程持有一个锁的时候,还没有释放,但还能够继续成功申请该锁,称该锁可重入,反之。

锁的争用与调度

java中内部锁属于非公平锁,显示锁支持非公平锁和公平锁

锁的粒度

一个所可以保护的共享数据的数量大小称为锁的粒度。

锁保护共享数据量大,称为锁粒度粗,否则称为粒度细。

锁的粒度过粗会导致线程在申请锁时会进行不必要的等待,锁粒度过细会增加锁调度的开销。

比如银行有一个柜台一个员工可以办理开卡、销户、取现、贷款那么所有人都只能去这个柜台办理业务,会需要很长的等待时间。但是如果把业务细分,一个业务一个柜台,这时候增加了银行的开销,需要三个员工。

内部锁:Synchronized

Java中每一个对象都有一个与之关联的内部锁,这种锁也叫监视器,是一种排它锁,可以保障原子性、可见性、排它性。

Synchronized(对象锁)
{
  同步代码块,可以在同步代码块中访问共享数据
}

修饰实例方法称为同步实例方法,修饰静态方法称为同步静态方法。

Synchronized同步代码块

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        for (int i = 0; i <2 ; i++) {
            new Thread(new RunnableThread())
            {
                @Override
                public void run() {
                    synchronizedLock.mm();
                }
            }.start();
        }
    }
    public  void  mm()
    {
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}

两个线程的代码都在并发执行

网络异常,图片无法展示
|

现在要打印的时候进行同步,同步的原理线程在执行的时候要先要获得锁

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        for (int i = 0; i <2 ; i++) {
            new Thread(new RunnableThread())
            {
                @Override
                public void run() {
                    synchronizedLock.mm();//使用锁的对象是synchronizedLock对象
                }
            }.start();
        }
    }
    public  void  mm()
    {
        synchronized (this)//this作为当前对象
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

网络异常,图片无法展示
|

因为Synchronized内部锁是排它锁,一次只能被一个线程持有,现在是Thread-0先取得锁对象,Thread-1在等待区等待Thread-0执行完毕释放锁,Thread-1获得锁再执行。

锁对象不同不能实现同步

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        SynchronizedLock synchronizedLock2=new SynchronizedLock();
            new Thread(new RunnableThread())
            {
                @Override
                public void run() {
                    synchronizedLock.mm();//使用锁的对象是synchronizedLock对象
                }
            }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                synchronizedLock2.mm();//使用锁的对象是synchronizedLock对象
            }
        }.start();
    }
    public  void  mm()
    {
        synchronized (this)//this作为当前对象
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

网络异常,图片无法展示
|

因此想要同步必须使用同一个锁对象

使用常量作为锁对象

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        SynchronizedLock synchronizedLock2=new SynchronizedLock();
            new Thread(new RunnableThread())
            {
                @Override
                public void run() {
                    synchronizedLock.mm();//使用锁的对象是synchronizedLock对象
                }
            }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                synchronizedLock.mm();//使用锁的对象是synchronizedLock对象
            }
        }.start();
    }
    public  static  final  Object obj=new Object();
    public  void  mm()
    {
        synchronized (obj)//常量作为当前对象
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

同步实例方法

使用synchronized修饰实例方法,同步实例方法,默认使用this作为锁对象

public class SynchronizedLock {
   public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
            new Thread(new RunnableThread())
            {
                @Override
                public void run() {
                    synchronizedLock.mm();
                }
            }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() { 
                synchronizedLock.mm2();
            }
        }.start();
    }
    //同步实例方法
    public  synchronized void  mm()
    {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
    }
    public  void  mm2()
    {
        synchronized (this)//常量作为当前对象
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

同步静态方法

使用synchronized修饰静态方法,同步静态方法,默认运行时使用SynchronizedLock class作为锁对象

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                synchronizedLock.mm2();
            }
        }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                SynchronizedLock.mm();//使用锁的对象是SynchronizedLock.class
            }
        }.start();
    }
    //同步静态方法
    public  synchronized static void  mm()
    {
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
    public  void  mm2()
    {
        synchronized (SynchronizedLock.class)//常量作为当前对象
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

同步代码块和同步方法如何选择

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                try {
                    synchronizedLock.mm2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                try {
                    synchronizedLock.mm2();//使用锁的对象是SynchronizedLock.class
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
    //同步实例方法 锁的粒度粗 执行效率低
    public  synchronized  void  mm() throws InterruptedException {
        long starttime= System.currentTimeMillis();
        System.out.println("start");
        Thread.sleep(3000);
        for (int i = 0; i <100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
        System.out.println("end");
        long Endtime= System.currentTimeMillis();
        System.out.println(Endtime-starttime);
    }
    //同步代码块 锁的粒度细 并发效率高
    public  void  mm2() throws InterruptedException {
        System.out.println("start");
        Thread.sleep(3000);
        synchronized (this)//常量作为当前对象
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
        System.out.println("end");
    }
}

在执行同步方法的时候,两次线程调用每次都需要休眠三秒,而同步代码块同时启动线程都先准备三秒,效率比较高

脏读

public class Test06 {
    public static void main(String[] args) throws InterruptedException {
        User user=new User();
        SubThread subThread=new SubThread(user);
        subThread.start();
        user.GetName();
    }
    static  class SubThread extends Thread
    {
        public User user;
        public SubThread(User user)
        {
            this.user=user;
        }
        @Override
        public void run() {
            user.SetValue("ww","456");
        }
    }
    static  class  User
    {
        private  String name="ylc";
        private  String pwd="123";
        public  void  GetName()
        {
            System.out.println(Thread.currentThread().getName()+"==>"+name+"密码"+pwd);
        }
        public  void  SetValue(String name,String pwd)
        {
            System.out.println("原来为为name="+this.name+",pwd="+this.pwd);
            this.name=name;
            this.pwd=pwd;
            System.out.println("更新为name="+name+",pwd="+pwd);
        }
    }
}

网络异常,图片无法展示
|

在修改数据还没有完成的时候,就读取到了原来的数据,而不是修改之后的

出现脏读的原因是对共享数据的修改和读取不同步引起的

解决办法是对修改和读取的方法进行同步方法上加上synchronized关键字

网络异常,图片无法展示
|

线程出现异常释放锁

假如在同步方法中,一个线程出现了异常,会不会没有释放锁,其他在等待的线程就在一直等待,论证:

public class SynchronizedLock {
    public static void main(String[] args) {
        SynchronizedLock synchronizedLock=new SynchronizedLock();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                synchronizedLock.mm();
            }
        }.start();
        new Thread(new RunnableThread())
        {
            @Override
            public void run() {
                synchronizedLock.mm2();
            }
        }.start();
    }
    //同步实例方法
    public  synchronized void  mm()
    {
        for (int i = 0; i <100 ; i++) {
            if(i==50)
            {
                Integer.parseInt("abc");//异常设置
            }
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
    public  void  mm2()
    {
        synchronized (this)
        {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
}

网络异常,图片无法展示
|

同步过程中线程出现异常,会自动释放锁对象,以供下一个线程继续执行

死锁

多线程中可能需要使用多个锁,如果获取锁的顺序不一致,可能导致死锁。

public class Text06_5 {
    public static void main(String[] args) {
        SubThread subThread=new SubThread();
        SubThread subThread2=new SubThread();
        subThread.setName("a"); subThread2.setName("b");
        subThread.start();subThread2.start();
    }
    static  class SubThread  extends  Thread
    {
        private  static  final Object lock1=new Object();
        private  static  final Object lock2=new Object();
        @Override
        public void run() {
            if("a".equals(Thread.currentThread().getName()))
            {
                synchronized (lock1)
                {
                    System.out.println("a 线程 lock1获得了锁,再需要获得lock2");
                    synchronized (lock2)
                    {
                        System.out.println("a 线程 lock2获得了锁");
                    }
                }
            }
            if("b".equals(Thread.currentThread().getName()))
            {
                synchronized (lock2)
                {
                    System.out.println("b 线程 lock2获得了锁,再需要获得lock1");
                    synchronized (lock1)
                    {
                        System.out.println(" b 线程 lock1获得了锁");
                    }
                }
            }
        }
    }
}

网络异常,图片无法展示
|

程序还在运行,却进入了卡死状态,a线程得到了lock1,要想把该线程释放的执行下面的代码获取lock2,而lock2被b线程获取无法释放,出现了鹬蚌相争的情况。

避免死锁:当需要获得锁时,所有线程获得锁的顺序一致,a线程先锁lock1,再锁lock2,b线程同理,就不会出现死锁了。

本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。

相关文章
|
3月前
|
Rust 并行计算 安全
揭秘Rust并发奇技!线程与消息传递背后的秘密,让程序性能飙升的终极奥义!
【8月更文挑战第31天】Rust 以其安全性和高性能著称,其并发模型在现代软件开发中至关重要。通过 `std::thread` 模块,Rust 支持高效的线程管理和数据共享,同时确保内存和线程安全。本文探讨 Rust 的线程与消息传递机制,并通过示例代码展示其应用。例如,使用 `Mutex` 实现线程同步,通过通道(channel)实现线程间安全通信。Rust 的并发模型结合了线程和消息传递的优势,确保了高效且安全的并行执行,适用于高性能和高并发场景。
47 0
|
6月前
|
安全 Java 程序员
牛皮了!八年美团大佬耗时3月竟在写《java虚拟机并发编程》
除了咖啡因,我想没有什么能比写出一段执行速度飞快的代码更能令程序员们兴奋了。然而我们如何才能满足这种对计算速度的渴求呢?诚然,摩尔定律可以帮我们解决部分问题,但多核处理器才代表了未来真正的发展方向。
|
Java 程序员
终于不慌内卷了,多亏阿里内部的并发图册+JDK源码速成笔记
并发编程 Java并发在近几年的面试里面可以说是面试热点,每个面试官面试的时候都会跟你扯一下并发,甚至是高并发。面试前你不仅得需要弄清楚的是什么是并发,还得搞清什么是高并发! 在这里很多小白朋友就会很疑惑:我工作又不用,为啥面试总是问?真就内卷卷我呗!(手动狗头)互联网内卷已经是现在的行业趋势,而且是不可逆的,这个大家也知道;但LZ要说的是,虽然简单地增删改查并不需要并发的知识,但是业务稍微复杂一点,你的技术水平稍微提升一点的话你就会知道,并发是我们Java程序员绕不开的一道坎。
50 0
涨姿势了!原来这才是多线程正确实现方式
线程同步机制是一套适用于协调线程之间的数据访问机制,该机制可以保障线程安全 java平台提供的线程同步机制包括:锁、volatile关键字、final关键字,static关键字、以及相关API如object.wait/object.notify
|
SQL 存储 监控
程序员新人频繁使用count(*),被组长批评后怒怼:性能并不拉垮!
程序员新人频繁使用count(*),被组长批评后怒怼:性能并不拉垮!
|
消息中间件 JavaScript 小程序
新来个阿里 P7,仅花 2 小时,撸出一个多线程永动任务,看完直接跪了,真牛逼!
新来个阿里 P7,仅花 2 小时,撸出一个多线程永动任务,看完直接跪了,真牛逼!
|
算法 Java Linux
工作这么久了,还不懂多线程吗?
浩哥Java多线程整理学习系列之01基础知识整理
102 0
工作这么久了,还不懂多线程吗?
|
存储 安全 算法
重生之我在人间敲代码_Java并发基础_安全性、活跃性以及性能问题
并发编程中我们需要注意的问题有很多,很庆幸前人已经帮我们总结过了,主要有三个方面,分别是:安全性问题、活跃性问题和性能问题。
|
监控
【多线程:犹豫模式】
【多线程:犹豫模式】
132 0