JAVA线程安全学习笔记之线程安全

Posted 诗萧尘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA线程安全学习笔记之线程安全相关的知识,希望对你有一定的参考价值。

问题: 线程安全,线程同步。为什么发生线程安全,线程同步问题。如何解决?

核心思想: 上锁。 代码从哪里上锁?----可能会发生线程安全的地方进行上锁。

通俗的讲就是我们更改数据的地方。那 是锁方法,锁类,锁代码块 ?

锁:分布式锁、公平锁,非公平锁、重入锁、悲观锁、乐观锁。

锁的机制:

在同一个JVM中,多个线程需要竞争锁资源。多个线程同时抢同一把锁,谁拿到锁资源,谁执行相关代码。

如果没有获取成功,中间需要经历锁升级过程,如果一直没有获取到锁就一直阻塞等待。

加锁的缺点:可能影响到程序的执行效率。

加锁的代码:

2、使用 synchronized 关键字,完成上述功能。

注意:synchronized 获取锁与释放锁都由底层虚拟机实现好了。

  在多线程的情况下 需要是同一个对象锁:且对象锁不能重复 Synchronized( 对象锁 )  需要保证线程安全的代码

3、synchronized 3种用法。

1.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码快前要获得 给定对象 的锁。 

  对象可以是。this, 也可以是任意对象。

2.修饰实例方法,作用于当前实例加锁,进入同步代码前要获得 当前实例 的锁, 

   加在实例方法上使用的是 this 锁。

   加在静态方法上使用的是 当前类名.class

3.修饰静态方法,作用于当前类对象(当前类.class)加锁,进入同步代码前要获得 当前类对象 的锁

/**  同一把锁 **/

如果是同一把锁 在多线程的情况下 最终只能够给一个线程使用。

如果有线程持有了该锁 意味着其他的线程 不能够在继续获取锁。

核心思想:当多个线程共享同一个全局变量时,将可能会发生线程安全的代码

上锁,保证只有拿到锁的线程才可以执行,没有拿到锁的线程不可以执行,需要阻塞等待。

死锁:

线程1

线程2

先获取到自定义对象的lock锁

先获取this锁

需要线程2已经持有的this锁

线程1已经持有自定义对象的lock锁 

 代码

synchronized 的死锁问题。



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

    @Override
    public void run() 
        while (true) 
            count++;
            if (count % 2 == 0) 
                // 线程1需要获取 lock 在获取 a方法this锁
                // 线程2需要获取this 锁在 获取B方法lock锁
                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();
    

检测死锁的工具:

synchronized 死锁诊断工具

jconsole.exe  默认在 jdk 安装目录的 bin 文件夹下

线程1 先获取到自定义对象的lock锁,进入到a方法需要获取this锁

线程2 先获取this锁,          进入到b方法需要自定义对象的lock锁

线程1 线程2 是在同时执行

线程同步:

  1. 使用synchronized锁,JDK1.6开始 锁的升级过程
  2. 使用Lock锁 ,需要自己实现锁的升级过程。底层是基于aqs实现

  3. 使用Threadlocal,需要注意内存泄漏的问题。

  4.  手写 原子类 cas 非阻塞式锁。 

有个升级过程记录下:

synchronized  偏向锁--> 轻量级锁(cas)-->重量级锁。

多线程通信

多线程之间通信:

等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,方法如下:

1.notify() :通知一个在对象上等待的线程,使其从main()方法返回,而返回的前提是该线程获取到了对象的锁

2.notifyAll():通知所有等待在该对象的线程

3.wait():调用该方法的线程进入WAITING状态,只有等待其他线程的通知或者被中断,才会返回。需要注意调用wait()方法后,会释放对象的锁 。

代码例子:

public class Thread03 extends Thread 
    @Override
    public void run() 
        try 
            synchronized (this) 
                System.out.println(Thread.currentThread().getName() + ">>当前线程阻塞,同时释放锁!<<");
                this.wait();
            
            System.out.println(">>run()<<");
         catch (InterruptedException e) 

        
    

    public static void main(String[] args) 
        Thread03 thread = new Thread03();
        thread.start();
        try 
            Thread.sleep(3000);
         catch (Exception e) 

        
        synchronized (thread) 
            // 唤醒正在阻塞的线程
            thread.notify();
        

    

多线程通讯实现生产者与消费者

public class Thread04 
    class Res 
        /**
         * 姓名
         */
        private String userName;
        /**
         * 性别
         */
        private char sex;
        /**
         * 标记
         */
        private 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) 
                    //flag = false  写入输入 flag = true 则不能写入数据 只能读取数据
                    try 
                        // 如果flag = true 则不能写入数据 只能读取数据 同时释放锁!
                        if (res.flag) 
                            res.wait();
                        
                     catch (Exception e) 

                    
                    if (count == 0) 
                        this.res.userName = "余胜军";
                        this.res.sex = '男';
                     else 
                        this.res.userName = "小薇";
                        this.res.sex = '女';
                    
                    res.flag = true;
                    res.notify();
                

                count = (count + 1) % 2;
            
        
    

    class OutThread extends Thread 
        private Res res;

        public OutThread(Res res) 
            this.res = res;
        


        @Override
        public void run() 
            while (true) 
                synchronized (res) 
                    try 
                        if (!res.flag) 
                            res.wait();
                        
                     catch (Exception e) 

                    
                    System.out.println(res.userName + "," + res.sex);
                    res.flag = false;
                    res.notify();
                
            
        
    


    public static void main(String[] args) 
        new Thread04().print();
    

    public void print() 
        Res res = new Res();
        InputThread inputThread = new InputThread(res);
        OutThread outThread = new OutThread(res);
        inputThread.start();
        outThread.start();
    

个人理解:

wait()和 notify()  : wait 阻塞当前线程,并且释放锁。notify() 唤醒对应释放锁的线程。  应用场景可以用于并行计算,A,B 同时计算。 A计算完毕,需要B 的结果时,阻塞当前线程,并释放锁,返回B 线程中计算完毕后唤醒A线程。

在 spring mvc controller 中使用 sychronized 关键字。

需要注意:

Spring MVC Controller默认是单例的 需要注意线程安全问题

单例的原因有二:

1、为了性能。

2、不需要多例。

3、省内存。

设置多例

@Scope(value = "prototype") 设置为多例子。

@RestController

@Slf4j

//@Scope(value = "prototype")

public class CountService 

private int count = 0;

@RequestMapping("/count")
public synchronized String count() 
try 
log.info(">count<" + count++);
try 
Thread.sleep(3000);
 catch (Exception e) 


 catch (Exception e) 


return "count";


以上是关于JAVA线程安全学习笔记之线程安全的主要内容,如果未能解决你的问题,请参考以下文章

Java 学习笔记之 实例变量与线程安全

Java并发编程实践读书笔记线程安全性和对象的共享

《java并发编程实战》读书笔记2--对象的共享,可见性,安全发布,线程封闭,不变性

java的多线程:线程安全问题

Java学习笔记45(多线程二:安全问题以及解决原理)

并发编程之多线程线程安全