线程安全
由于系统没有为线程分配资源,它们与进程中的其他线程共享进程的共享资源,这时候如果多个线程共享一个数据,如果处理不好的话就会出现线程的安全隐患,比如丢失修改,不可重复读,读脏数据等。
1.丢失修改
package com.sw.xc;
public class Thread1 implements Runnable{
int num = 5;//5个苹果
@Override
public void run() {
while (num>0) {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + num-- + "个苹果还剩下" + num + "个苹果");
} else {
System.out.println("苹果卖完了");
}
}
}
}
package com.sw.xc;
public class Test {
public static void main(String[] args) {
new Thread(new Thread1(),"t1").start();
new Thread(new Thread1(),"t2").start();
}
}
/*测试结果
t1正在卖第5个苹果还剩下4个苹果
t2正在卖第5个苹果还剩下4个苹果
t1正在卖第4个苹果还剩下3个苹果
t2正在卖第4个苹果还剩下3个苹果
t1正在卖第3个苹果还剩下2个苹果
t2正在卖第3个苹果还剩下2个苹果
t2正在卖第2个苹果还剩下1个苹果
t2正在卖第1个苹果还剩下0个苹果
1正在卖第2个苹果还剩下1个苹果
t1正在卖第1个苹果还剩下0个苹果*/
上面的卖苹果t1和t2都有同时在卖同一个苹果,如当t1在卖第5个苹果的时候还没来得及去修改苹果的数量,t2就以及开始卖苹果了而且也是卖的第五个,这样的线程是不安全的,这就造成了修改丢失的情况。
2.脏读
package com.sw.xc;
public class DirtyRead {
private String userName = "abc";
private String passWord = "321";
public synchronized void setValue(String userName,String passWord){
this.userName = userName;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.passWord = passWord;
System.out.println("setValue最终结果是:userName = "+userName+",passWord = "+passWord);
}
public void getvalue(){
System.out.println("getvalue方法得到的是:userName = "+this.userName+",passWord = "+this.passWord);
}
public static void main(String[] args) throws InterruptedException {
final DirtyRead dr = new DirtyRead();
Thread t1 = new Thread(()->{
dr.setValue("cba","123");
});
t1.start();
Thread.sleep(1000);
dr.getvalue();
}
}
//测试结果
//getvalue方法得到的是:userName = cba,passWord = 321
//setValue最终结果是:userName = cba,passWord = 123
上面get/set方法的共享资源是userName 和passWord,t1线程设置它们需要2秒,main线程获取它们只需要1秒,它们同去访问userName 和passWord,t1还没有set完main就get了它们导致最后的passWord不一样。
3.不可重复读
package com.sw.xc;
public class Person {
int age =40;
public synchronized int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package com.sw.xc;
public class Test {
public static void main(String[] args) {
Person p = new Person();
new Thread(()-> {for(int i=0;i<5;i++) {
System.out.println("age=" + p.getAge());
}
},"t1").start();
new Thread(()->{p.setAge(30);},"t2").start();
}
}
//测试结果
//age=40
//age=30
//age=30
//age=30
//age=30
上面t1线程多次读取age,本来每次得到结果应该是一样的,但是结果不一,是由于被t2线程给中途修改了,这就是不可重复读即前后读取数据不一致。
4.解决方法
解决线程不安全的问题,我们可以使用同步关键字synchronized
修饰访问共享资源的方法,被它修饰的方法同一时刻只能由一个线程进行访问,其他线程只能等已启动的线程执行完,这就很大程度的保证了线程的安全性,它也可以用来修饰类,表明该类中的所有方法都是synchronized
的。它还可以修饰一段代码synchronized(this) {//代码}