先说下Java内存模型与多线程,很重要的。希望大家看完再运行代码,文字部分很重要,最好读熟读透再运行我给的例子,便于理解。读书不能求快,快的话吸收就有问题。
上面的这两个问题读者需要完全理解
编写线程安全的代码,本质上就是对状态的访问,而且通常都是共享的,可变的状态。
通俗地说,一个对象的状态就是它的数据,存储在状态变量中,比如实例域或静态域。对象的状态还包括了其他附属对象的域。例如,HashMap的状态一部分存储到对象本身中,但同时也存储1到很多Map.Entry对象中。一个对象的状态包含了任何会对它外部可见行为产生影响的数据。
所谓共享,是指一个变量可以被多个线程访问;所谓可变,是指变量的值在其申明周期内可以改变,我们讨论的线程安全性好像是关于代码的,但是我们真正要做的,是在不可控制的并发访问中保护数据。
下面给出一个例子说明一下
有这么一个场景,假设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);
}
}
运行结果
结果应该是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);
}
}
运行结果
也可以在方法里面加锁
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);
}
}
}
运行结果如下
也可以用ThreadLocal来实现,具体请看我的另外一篇博文。这里就不讲了。
https://blog.csdn.net/ab111996/article/details/106609653
下面说一下线程安全的级别,当然,并没有涵盖所有的可能。大家最好熟记。后面要用到
.1.不可变--这个类的实例是不变的。所以,不需要外部的同步。这样的例子包括String,Long和BigInteger
.2.无条件的线程安全--这个类的实例是可变的,但是这个类有着足够的内部同步,所以,它的实例可以被并发使用。其中的例子包括Random和ConcurrentHashMap
.3.有条件的线程安全--除了有些方法为进行安全的并发使用而需要外部同步之外,这种线程安全级别与无条件的线程安全相同。这样的例子包括Collections.synchronized包装返回的集合,它们的迭代器要求外部同步。
.4.非线程安全--这个类的实例是可变的。为了并发地使用它们,客户必须利用自己选择的外部同步包围每个方法调用(或者调用序列)。这样的例子包括通用的集合实现,例如ArrayList和HashMap.
.5.线程对立的--这个类不能安全地被多个线程并发使用,即时所有的方法调用都被外部同步包围。线程对立的根源通常在于,没有同步地修改静态数据。没有人会有意编写一个线程编写一个线程对立的类;这种类是因为没有考虑到并发性而产生的后果。幸运的是,在Java平台类库中,线程对立的类或者方法非常少。System.runFinalizersOnExit方法是线程对立的,但已经被废除了。