• 线程安全


    什么是线程安全?

    为什么有线程安全问题?

    当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

    案例: 现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。

    package com.toov5.threadSecurity;
    
    public class TrainThread implements Runnable{
         private int trainCount = 100;
        
           @Override
        public void run() {
            while(trainCount>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                sale();
            }
            
        }
           
        public void sale(){
            if(trainCount>0){
                System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票");
                trainCount--;
            }
        
        }   
           
           
        public static void main(String[] args) {
            TrainThread trainThread = new TrainThread();
            Thread t1 = new Thread(trainThread,"1号窗口");
            Thread t2 =    new Thread(trainThread,"2号窗口");
            t1.start();
            t2.start();
        }   
           
        
    }

     代码运行结果是有问题的

    一号窗口和二号窗口同时出售火车第九九张,部分火车票会重复出售。

    结论发现,多个线程共享同一个全局成员变量时,做写的操作可能会发生数据冲突问题。

    线程安全解决办法:

    1、使用多线程之间同步synchronized或使用锁(lock)解决多线程之间的安全问题

         将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。

    2、当多个线程共享同一个资源,不会受到其他线程的干扰,这就是多线程之间的同步问题

    内置的锁

    Java提供了一种内置的锁机制来支持原子性

    每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁

    内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A释放锁,线程B才能获取到同一个锁

    内置锁使用synchronized关键字实现,synchronized关键字有两种用法:

    1.修饰需要进行同步的方法(所有访问状态变量的方法都必须进行同步),此时充当锁的对象为调用同步方法的对象

    2.同步代码块和直接使用synchronized修饰需要同步的方法是一样的,但是锁的粒度可以更细,并且充当锁的对象不一定是this,也可以是其它对象,所以使用起来更加灵活

    同步代码块synchronized

    就是将可能会发生线程安全问题的代码,给包括起来。

    synchronized(同一个数据){

     可能会发生线程冲突问题

    }

    就是同步代码块 

    synchronized(对象)//这个对象可以为任意对象 

        需要被同步的代码 

    对象如同锁,持有锁的线程可以在同步中执行 

    没持有锁的线程即使获取CPU的执行权,也进不去 

    同步的前提: 

    1,必须要有两个或者两个以上的线程 

    2,必须是多个线程使用同一个锁 

    必须保证同步中只能有一个线程在运行 

    好处:解决了多线程的安全问题 

    弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。 

    比如本demo:

    public void sale(){
            synchronized (this) {
                if(trainCount>0){
                    System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票");
                    trainCount--;
                }
            }
        }   
    public void sale(){
            synchronized (obj) {
                if(trainCount>0){
                    System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票");
                    trainCount--;
                }
            }
        }   

    同步方法

    什么是同步方法?

    在方法上修饰synchronized 称为同步方法

      public synchronized void sale(){
                    if(trainCount>0){
                        System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票");
                        trainCount--;
                }
            }  

    需要值得一提的是:

    同步方法使用的是什么锁

    证明方式: 一个线程使用同步代码块(this明锁),另一个线程使用同步函数。如果两个线程抢票不能实现同步,那么会出现数据错误。

    哈哈哈,代码证明题,堪比数学题:

     思路: 另个线程共享操作同一个全局变量,每个线程锁不同。

    一个使用非静态同步方法,另一个使用同步代码块

    对于synchronize的写法我感觉真心像个 排列组合

    代码块随便加 

    方法(){

    代码块(this)

     {

           }

    }

    等等

    使用的时候尽量使用同步方法代码块哈 这样粒度更小一些 效率更高

    证明代码

    package com.toov5.threadSecurity;
    class Thread02 implements Runnable{
    
        private int countTicket = 100;
        private static Object obj = new Object();
    
        public static  boolean flag = true;
        @Override
        public void run() {
    
            if(flag){//使用同步锁--this
                while(countTicket>0){
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sale1();
                }
            }else{//使用同步函数
                while(countTicket>0){
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sale();
                }
            }
    
        }
    
        public void sale1(){
    
            synchronized(obj){
                if(countTicket>0){ //------当把这个if判断去掉 最后还是会有两个线程来争夺最后一张票 会挨个执行,数据不会出错,但是逻辑会出错。
                System.out.println("当前线程名字:"+Thread.currentThread().getName()+";出售第"+(100 - countTicket + 1)+"张票。");
                countTicket--;
             }
            }
        }
    
        public synchronized void sale(){
    
            if(countTicket>0){
                //卖票
                System.out.println("当前线程名字:"+Thread.currentThread().getName()+";出售第"+(100 - countTicket + 1)+"张票。");
                countTicket--;
            }
    
        }
    }
    
    public class ThreadDemo1 {
    
        public static void main(String[] args) throws InterruptedException {
            Thread02 t = new Thread02();
    
            Thread t1 = new Thread(t,"窗口1");
            Thread t2 = new Thread(t,"窗口2");
            t1.start();
            Thread.sleep(50);
            Thread02.flag=false;
            t2.start();
        }
    
    }

     两个线程,共享一个全局变量。每个锁不同

    一个使用非静态同步代码块,一个使用同步代码块(this可以同步,Object不可以)

     

     

  • 相关阅读:
    HTMLParser使用
    SpringMVC学习系列(6) 之 数据验证
    SpringMVC学习系列 之 表单标签
    开源OSS.Social微信项目解析
    源码分析——核心机制
    Struts2 源码分析——过滤器(Filter)
    调结者(Dispatcher)之执行action
    配置管理之PackageProvider接口
    源码分析——Action代理类的工作
    DefaultActionInvocation类的执行action
  • 原文地址:https://www.cnblogs.com/toov5/p/9827108.html
Copyright © 2020-2023  润新知