• java线程安全初窥探


    • 知识点: 线程同步 线程并发
    • 问题描述:在当处理全局变量的时候,当两个或者以上的线程处理同一个** 全局 **变量的时候,可能会出现冲突问题。

    java 同步函数

    首先看一下问题场景

    package com.Thread.Test;
    
    /**
     * 抢票问题的一个案例分析
     */
    
    class ThreadTrain implements Runnable{
    
        private int TrainCount = 100;
    
    
        @Override
        public void run() {
                while (TrainCount>0){
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sale();
                }
        }
    
        private void sale(){
            System.out.println(Thread.currentThread().getName()+":正在出售第"+(100-TrainCount+1)+"张票");
            TrainCount--;
        }
    }
    
    
    public class Main {
    
        public static void main(String[] args) {
        //开辟两个线程
            ThreadTrain threadTrain = new ThreadTrain();
            Thread t1 = new Thread(threadTrain, "窗口一");
            Thread t2 = new Thread(threadTrain, "窗口二");
    
            t1.start();
    
            t2.start();
    
        }
    }
    
    

    Console :
    Console :
    可以看到 上图 会出现两个线程同时贩卖一张票的情况,而且最后会出现贩卖101张票的时候

    • 那么为什么会产生这样的问题呢?
    • 原因分析: 是因为两个线程当时同时处于运行状态,那么他们接收的全局变量的value是相等的,那么就会出现贩卖同一张票的情况,这样就会产生线程不安全的情况!
    • 解决方案分析:就像是购票的原理一样,会对数据库进行锁表,来实现数据同步,java也有锁这种东西
      • synchronize ---- >自动锁
      • lock --> jdk1.5 手动锁

    synchronize 解决代码:

    package com.Thread.Test;
    
    /**
     * 抢票问题的一个案例分析
     */
    
    class ThreadTrain implements Runnable{
    
        private int TrainCount = 100;
    
    
        @Override
        public void run() {
            while (TrainCount>0){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                sale();
            }
        }
    
        //synchronized 分成 函数 和标识两个  
        private synchronized void sale(){ //this锁
    //        synchronized (this){
            //加一个判断 判断最后一张票的两个线程情况
            if(TrainCount>0){
                System.out.println(Thread.currentThread().getName()+":正在出售第"+(100-TrainCount+1)+"张票");
                TrainCount--;
            }
    
    //        }
        }
    }
    
    
    public class Main {
    
        public static void main(String[] args) {
            ThreadTrain threadTrain = new ThreadTrain();
            Thread t1 = new Thread(threadTrain, "窗口一");
            Thread t2 = new Thread(threadTrain, "窗口二");
    
            t1.start();
    
            t2.start();
    
        }
    }
    

    synchronize 方法 比较方便,但是拓展性不高,资源占用大

    lock锁的解决办法

    package com.Thread.Test;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class ThreadTrain implements Runnable{
    
        private int TrainCount = 100;
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
    
            while (TrainCount>0){
                try {
                    Thread.sleep(50);
                    sale();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        //synchronized 分成 函数 和标识两个
        private  void sale(){ //this锁
    
            try {
                lock.lock();
                if(TrainCount>0){
                    System.out.println(Thread.currentThread().getName()+":正在出售第"+(100-TrainCount+1)+"张票");
                    TrainCount--;
                }
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
    
        }
    }
    
    
    public class LockTest {
        public static void main(String args[]) throws InterruptedException {
            ThreadTrain threadTrain = new ThreadTrain();
            Thread t1 = new Thread(threadTrain, "窗口一");
            Thread t2 = new Thread(threadTrain, "窗口二");
    
            t1.start();
            Thread.sleep(40);
            t2.start();
        }
    }
    
    

    result

    • 值得注意的是 如果将函数标识成synchronized锁的话,这个函数只是一个this锁,但是如果使用synchronized函数的话,函数的变量可以定义任何Object类型
    • 如果是用lock锁的话,如果代码在加锁的过程中,程序崩溃报错,那么这个锁就一直会在锁定状态,所以应该用try catch的时候,在finally加上unlock保证锁的正常运行
    • 通过锁来实现数据同步,来解决一个像是抢票的并发问题。

    java静态同步函数

    • 如果将synchroized锁函数名前面加上static 标识限制的时候,那么这个函数不再是一个this锁,而是锁本类的java对象
      例如
    private static synchronized void sale(){ //this锁
    //        synchronized (this){
            //加一个判断 判断最后一张票的两个线程情况
            if(TrainCount>0){
                System.out.println(Thread.currentThread().getName()+":正在出售第"+(100-TrainCount+1)+"张票");
                TrainCount--;
            }
    
    //        }
        }
    

    是和下面的功能是一样的

     private  void sale(){ //this锁
            synchronized (ThreadTrain.class){
            //加一个判断 判断最后一张票的两个线程情况
            if(TrainCount>0){
                System.out.println(Thread.currentThread().getName()+":正在出售第"+(100-TrainCount+1)+"张票");
                TrainCount--;
            }
    
            }
        }
    

    线程死锁

    • 千万不要在数据同步的时候在嵌套一个数据锁,这样可能产生一个线程死锁
    • 具体代码如下
    package com.Thread.Test;
    
    /**
     * 抢票问题的一个案例分析
     */
    
    class ThreadTrain implements Runnable{
    
        private int TrainCount = 100;
    
        private Object oj = new Object();
    
        public boolean flag = true;
    
        @Override
        public void run() {
            if(flag){
                while (TrainCount>0){
    
                    synchronized (oj){
                        //加一个判断 判断最后一张票的两个线程情况
                        sale();
    
                    }
                }
            }else{
                while(TrainCount>0){
                    sale();
                }
    
            }
    
        }
    
        private synchronized void sale(){
            synchronized (oj){
            //加一个判断 判断最后一张票的两个线程情况
            if(TrainCount>0){
                try {
                    Thread.sleep(40);
                } catch (Exception e) {
    
                }
    
                System.out.println(Thread.currentThread().getName()+":正在出售第"+(100-TrainCount+1)+"张票");
                TrainCount--;
            }
    
            }
        }
    }
    
    public class Main {
    
        public static void main(String[] args) throws InterruptedException {
            ThreadTrain threadTrain = new ThreadTrain();
            Thread t1 = new Thread(threadTrain, "窗口一");
            Thread t2 = new Thread(threadTrain, "窗口二");
    
            t1.start();
            Thread.sleep(40);
            threadTrain.flag = false;
            t2.start();
    
        }
    }
    
    

    运行结果

    产生原因: 一个线程已经占用了Object锁之后,打算进入this锁。但是第二个线程从flag = false那里的代码块直接占用this锁,从而第一个线程进不去sale()方法,而第二个方法执行sale()方法需要解开Object锁,导致死锁的产生。
    这就好比是两个好友分别有对方的密码盒,并且都有自己钥匙,但是都不会把钥匙给对方,从而会产生一个谁也打不就开密码盒的尴尬情况。

  • 相关阅读:
    FtpClient中文乱码问题解决
    JS点击按钮弹出窗口
    win7配置简单的FTP服务器
    docker 使用案例:部署nginx
    Asp.Net Boilerplate Project (ABP) 视频教程
    dva.js 用法总结
    webpack4: compilation.mainTemplate.applyPluginsWaterfall is not a function 解决方法
    c# MongoDB Driver 官方教程翻译
    c# redis 操作类库推荐:StackExchange.Redis.Extensions
    react-native导航器 react navigation 介绍
  • 原文地址:https://www.cnblogs.com/adroitwolf/p/14309953.html
Copyright © 2020-2023  润新知