——- android培训、java培训、期待与您交流! ——–
7.5、线程安全问题
线程安全 :一数据错乱、二死锁
问题演示 一:数据错乱
package XianChen;
public class LiZi {
public static void main(String[] args) {
// 开启新线程 将要运行的 对象传入
XianCh x = new XianCh();
Thread t1 = new Thread(x);
Thread t2 = new Thread(x);
Thread t3 = new Thread(x);
// 启动新线程
t1.start();
t2.start();
t3.start();
}
}
class XianCh implements Runnable{
private int num=100;
// 覆写 抽象方法 run()
public void run() {
for(int x=0;x<50;x++){
if(num>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"……"+ num--);
}
}
}
}
class XianCh2 extends Thread{
// 覆写 抽象方法 run()
public void run() {
for(int x=10;x>0;x--)
System.out.println(Thread.currentThread().getName()+"……"+x);
}
}
/*
* 输出结果截取:
Thread-0……13
Thread-2……12
Thread-1……11
Thread-0……10
Thread-2……9
Thread-1……8
Thread-0……7
Thread-2……6
Thread-1……5
Thread-0……4
Thread-2……3
Thread-1……2
Thread-0……1
Thread-2……0
Thread-1……-1 //错误数据
*/
问题分析:
出现上图安全问题的原因在于Thread-0通过了if判断后,在执行到“num–”语句之前,num此时仍等于1。
CPU切换到Thread-1、Thread-2、Thread-3之后,这些线程依然可以通过if判断,从而执行“num–”的操作,因而出现了0、-1的情况。
也就是说线程安全问题产生的原因:
1. 多个线程在操作共享的数据。
2. 操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
解决方案
思路:
保证单一线程操作数据
具体方法
synchronized
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象){
需要被同步的代码;
}
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。
例:
package XianChen;
public class LiZi {
public static void main(String[] args) {
// 开启新线程 将要运行的 对象传入
XianCh x = new XianCh();
Thread t1 = new Thread(x);
Thread t2 = new Thread(x);
Thread t3 = new Thread(x);
// 启动新线程
t1.start();
t2.start();
t3.start();
}
}
class XianCh implements Runnable{
private int num=100;
// 覆写 抽象方法 run()
// 使用 synchronized 同步函数 方式 一
public synchronized void run() {
for(int x=0;x<50;x++){
// 方式二 加入同步代码块
synchronized(obj ){
if(num>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.println(Thread.currentThread().getName()+"……"+ num--);
}
}
}
}
}
class XianCh2 extends Thread{
// 覆写 抽象方法 run()
public void run() {
for(int x=10;x>0;x--) System.out.println(Thread.currentThread().getName()+"……"+x);
}
}
注:
同步函数和同步代码块的区别:
1. 同步函数的锁是固定的this。
2. 同步代码块的锁是任意的对象。
建议使用同步代码块。
由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。
问题演示 二:死锁
概念、原因、条件
什么叫死锁?
所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
那么为什么会产生死锁呢?
1.因为系统资源不足。
2.进程运行推进的顺序不合适。
3.资源分配不当。
产生死锁的条件有四个:
1.互斥条件:所谓互斥就是进程在某一时间内独占资源。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
死锁 一:请求与保持条件
package XianChen;
/**
*
*
*/
public class LiZi {
public static void main(String[] args) {
Rec r = new Rec();
// 开启新线程 将要运行的 对象传入
Sell s= new Sell(r);
Get g = new Get(r);
Thread t1 = new Thread(s);
Thread t2 = new Thread(g);
// 启动新线程
t1.start();
t2.start();
}
}
class Rec {
private Object objA=new Object();
private Object objB=new Object();
private boolean flag=true;
private int A=100;
private int B=100;
public void sell() {
for(int x=0;x<50;x++){
synchronized(objA){
System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);
synchronized(objB){
if(!flag)
System.out.println(Thread.currentThread().getName()+":B上锁我拿到 B"+ B--);
}
}
}
}
public void get(){
synchronized(objB){
System.out.println(Thread.currentThread().getName()+":B上锁我拿到 B"+ B--);
synchronized(objA){
if(flag){
System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);
flag=false;
}
}
}
}
}
class Sell implements Runnable{
private Rec r=null;
Sell(Rec r){
this.r = r;
}
public void run() {
while(true){
r.sell();
}
}
}
class Get implements Runnable{
private Rec r=null;
Get(Rec r){
this.r=r;
}
public void run() {
while(true){
r.get();
}
}
}
/*
输出结果:
Thread-0:A上锁我拿到 A100
Thread-1:B上锁我拿到 B100
*/
问题分析
死锁大多是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
解决方案
避免锁的嵌套,用完之后立即解锁
具体案例
package XianChen;
/**
*
*
*/
public class LiZi {
public static void main(String[] args) {
Rec r = new Rec();
// 开启新线程 将要运行的 对象传入
Sell s= new Sell(r);
Get g = new Get(r);
Thread t1 = new Thread(s);
Thread t2 = new Thread(g);
// 启动新线程
t1.start();
t2.start();
}
}
class Rec {
private Object objA=new Object();
private Object objB=new Object();
private boolean flagA=true,flagB=true;
private int A=100;
private int B=100;
public void sell() {
// 当两个资源都为零 时 停止程序
while(flagA||flagB){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(objA){
if(A>0)
System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);
else
flagA =false;
}
synchronized(objB){
if(B>0)
System.out.println(Thread.currentThread().getName()+":A上锁我拿到 B"+ B--);
else
flagB =false;
}
}
}
public void get(){
// 当两个资源都为零 时 停止程序
while(flagA||flagB){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(objB){
if(B>0)
System.out.println(Thread.currentThread().getName()+":B上锁我拿到 B"+ B--);
else
flagB =false;
}
synchronized(objA){
if(A>0)
System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);
else
flagA =false;
}
}
}
}
class Sell implements Runnable{
private Rec r=null;
Sell(Rec r){
this.r = r;
}
public void run() {
r.sell();
}
}
class Get implements Runnable{
private Rec r=null;
Get(Rec r){
this.r=r;
}
public void run() {
r.get();
}
}
/*
输出结果:
Thread-0:A上锁我拿到 A7
Thread-0:A上锁我拿到 B7
Thread-1:B上锁我拿到 B6
Thread-1:A上锁我拿到 A6
Thread-0:A上锁我拿到 A5
Thread-0:A上锁我拿到 B5
Thread-1:B上锁我拿到 B4
Thread-1:A上锁我拿到 A4
Thread-0:A上锁我拿到 A3
Thread-0:A上锁我拿到 B3
Thread-1:B上锁我拿到 B2
Thread-1:A上锁我拿到 A2
Thread-0:A上锁我拿到 A1
Thread-0:A上锁我拿到 B1
*/
死锁 二 循环等待条件
问题演示:
package XianChen;
/**
*
*
*/
public class LiZi {
public static void main(String[] args) {
Rec r = new Rec();
// 开启新线程 将要运行的 对象传入
Sell s= new Sell(r);
Get g = new Get(r);
Thread t1 = new Thread(s);
Thread t2 = new Thread(g);
// 启动新线程
t1.start();
t2.start();
}
}
class Rec {
private Object objA=new Object();
private Object objB=new Object();
// private boolean flagA=true,flagB=true;*/
private int A=100;
private int B=100;
public void sell() {
synchronized(objA){
if(A>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);
}
get();
}
}
public synchronized void get(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(objA){
if(B>0){
System.out.println(Thread.currentThread().getName()+":A上锁我拿到 B"+ B--);
}
}
}
}
class Sell implements Runnable{
private Rec r=null;
Sell(Rec r){
this.r = r;
}
public void run() {
while(true){
r.sell();
}
}
}
class Get implements Runnable{
private Rec r=null;
Get(Rec r){
this.r=r;
}
public void run() {
while(true){
r.get();
}
}
}
/*
*/
问题分析
这其实还是两个锁的嵌套
执行get 方法则必须获取this对象锁,然后才能执行其中的同步代码块。
当线程t1获取到objA对象锁执行同步代码块,线程t2获取到this对象锁执行get方法。同步代码块中的get方法因无法获取到this对象锁无法执行,sell方法中的同步代码块因无法获取到objA对象锁无法执行,就会产生死锁。
解决方案
同问题一,去掉锁的嵌套
具体方案
package XianChen;
/**
*
*
*/
public class LiZi {
public static void main(String[] args) {
Rec r = new Rec();
// 开启新线程 将要运行的 对象传入
Sell s= new Sell(r);
Get g = new Get(r);
Thread t1 = new Thread(s);
Thread t2 = new Thread(g);
// 启动新线程
t1.start();
t2.start();
}
}
class Rec {
private Object objA=new Object();
private Object objB=new Object();
// private boolean flagA=true,flagB=true;*/
private int A=100;
private int B=100;
public void sell() {
synchronized(objA){
if(A>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":A上锁我拿到 A"+ A--);
}
get();
}
}
public void get(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(objA){
if(B>0){
System.out.println(Thread.currentThread().getName()+":A上锁我拿到 B"+ B--);
}
}
}
}
class Sell implements Runnable{
private Rec r=null;
Sell(Rec r){
this.r = r;
}
public void run() {
while(true){
r.sell();
}
}
}
class Get implements Runnable{
private Rec r=null;
Get(Rec r){
this.r=r;
}
public void run() {
while(true){
r.get();
}
}
}
/*
输出结果:
Thread-0:A上锁我拿到 B89
Thread-1:A上锁我拿到 B88
Thread-0:A上锁我拿到 A89
Thread-0:A上锁我拿到 B87
Thread-0:A上锁我拿到 A88
Thread-0:A上锁我拿到 B86
Thread-0:A上锁我拿到 A87
Thread-0:A上锁我拿到 B85
Thread-0:A上锁我拿到 A86
Thread-0:A上锁我拿到 B84
Thread-0:A上锁我拿到 A85
Thread-0:A上锁我拿到 B83
Thread-0:A上锁我拿到 A84
Thread-0:A上锁我拿到 B82
Thread-1:A上锁我拿到 B81
*/
死锁处理总结
不难看出,产生死锁主要是四个条件,因此只要不让四个发生即可
互斥:
要打破这个条件,就是要让多个线程能共享资源,就相当于A和B能同时举起ball1一样,当然在这个例子里我们可以这样修改规则,但是在其它程序中就不一定能了,比如说一个“读”线程,一个“写”线程,它们都能操作同一文件。在这种情况下,我们就不能“又读又写”文件,否则有可能会读到脏数据!因此我们很少从这方面考虑。
占有等待
打破占有等待,只要当检测到自己所需的资源仍被别的线程占用,即释放自己已占有的资源(毫不利己,专门利人,呵呵~),或者在经过一段时间的等待后,还未得到所需资源,才释放,这都能打破占有等待。
不剥夺,循环等待
这两个线程也是如此,经过一段时间的等待后,还未得到所需资源,
就释放资源,即可打破占有等待;
另外其实打破非剥夺,只要给线程制定一个优先级即可