java多线程系列五之线程安全概念

先说下Java内存模型与多线程,很重要的。希望大家看完再运行代码,文字部分很重要,最好读熟读透再运行我给的例子,便于理解。读书不能求快,快的话吸收就有问题。

java多线程系列五之线程安全概念_第1张图片

 

java多线程系列五之线程安全概念_第2张图片 

java多线程系列五之线程安全概念_第3张图片 

 java多线程系列五之线程安全概念_第4张图片

上面的这两个问题读者需要完全理解 

 

编写线程安全的代码,本质上就是对状态的访问,而且通常都是共享的,可变的状态。

  通俗地说,一个对象的状态就是它的数据,存储在状态变量中,比如实例域或静态域。对象的状态还包括了其他附属对象的域。例如,HashMap的状态一部分存储到对象本身中,但同时也存储1到很多Map.Entry对象中。一个对象的状态包含了任何会对它外部可见行为产生影响的数据。

  所谓共享,是指一个变量可以被多个线程访问;所谓可变,是指变量的值在其申明周期内可以改变,我们讨论的线程安全性好像是关于代码的,但是我们真正要做的,是在不可控制的并发访问中保护数据。

java多线程系列五之线程安全概念_第5张图片

 

 

java多线程系列五之线程安全概念_第6张图片 

 

下面给出一个例子说明一下

 

有这么一个场景,假设5个用户,都来给一个数字加1的工作

  工作内容:单个用户需要做的事

public class Count{

public int num=0;//跨线程访问了该字段,

public void add(){

try{

Thread.sleep(51);

}catch(InterruptedException e){

 

}

num+=1;

System.out.println(Thread.currentThread().getName()+"-"+num);

}

}

 

用户类,包含它需要做的工作

public class ThreadTestA extends Thread{
    private Count count;
    public ThreadTestA(Count count){
        this.count=count;
    }
    @Override
    public void run(){
        count.add();
    }
}

 

5个人干完活,最后的值

public class ThreadMainTest{
    public static void main(String[] args){
        Count count=new Count();
        for (int i=0;i<5;i++) {
            ThreadTestA task=new ThreadTestA(count);
            task.start();
        }
        try{
            Thread.sleep(1001);
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("5个人干完活之后:最后的值是"+count.num);
    
}
}

运行结果

java多线程系列五之线程安全概念_第7张图片

结果应该是5,现在是4 .

加锁让它运行正常。现在不考虑性能问题,只是演示

synchronized关键字:保证在同一时刻,只有一个线程可以执行某一个方法,或者某一个代码块

  我们把 工作内容:单个用户需要做的事 这个类改动一下,给add方法加上synchronized关键字

public class Count{
    public volatile int num=0;
    public synchronized void add(){
        try{
            Thread.sleep(51);
        }catch(InterruptedException e){

        }


        num+=1;
        System.out.println(Thread.currentThread().getName()+"-"+num);
    }
}

运行结果

java多线程系列五之线程安全概念_第8张图片

也可以在方法里面加锁

public class Count{
    public  int num=0;
    
    public  void add(){
        try{
            Thread.sleep(51);
        }catch(InterruptedException e){

        }
        synchronized(this){
        num+=1;
        System.out.println(Thread.currentThread().getName()+"-"+num);
    }
}
}

运行结果如下

java多线程系列五之线程安全概念_第9张图片

 也可以用ThreadLocal来实现,具体请看我的另外一篇博文。这里就不讲了。

https://blog.csdn.net/ab111996/article/details/106609653

 

下面说一下线程安全的级别,当然,并没有涵盖所有的可能。大家最好熟记。后面要用到

.1.不可变--这个类的实例是不变的。所以,不需要外部的同步。这样的例子包括String,Long和BigInteger

.2.无条件的线程安全--这个类的实例是可变的,但是这个类有着足够的内部同步,所以,它的实例可以被并发使用。其中的例子包括Random和ConcurrentHashMap

.3.有条件的线程安全--除了有些方法为进行安全的并发使用而需要外部同步之外,这种线程安全级别与无条件的线程安全相同。这样的例子包括Collections.synchronized包装返回的集合,它们的迭代器要求外部同步。

java多线程系列五之线程安全概念_第10张图片

.4.非线程安全--这个类的实例是可变的。为了并发地使用它们,客户必须利用自己选择的外部同步包围每个方法调用(或者调用序列)。这样的例子包括通用的集合实现,例如ArrayList和HashMap.

.5.线程对立的--这个类不能安全地被多个线程并发使用,即时所有的方法调用都被外部同步包围。线程对立的根源通常在于,没有同步地修改静态数据。没有人会有意编写一个线程编写一个线程对立的类;这种类是因为没有考虑到并发性而产生的后果。幸运的是,在Java平台类库中,线程对立的类或者方法非常少。System.runFinalizersOnExit方法是线程对立的,但已经被废除了。

 

 

 

你可能感兴趣的:(java,多线程,线程安全概念)