线程同步问题:多线程的同步问题指的是多个线程同时修改一个数据的时候,可能导致的问题 -多线程的问题,又叫Concurrency 问题 。
- 同步问题举例
假设有一个数x=1000,有两个线程,一个给x加一操作、另一个给x减一操作,
那么在执行n次之后,讲道理x应该还是等于1000的,但是因为线程同步的问题,最后会出现x!=1000的情况。
1 package UML; 2 3 public class Concurrency_test { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated method stub 7 final Hero gareen = new Hero(); 8 gareen.name = "盖伦"; 9 gareen.hp = 10000; 10 11 System.out.printf("盖伦的初始血量是%.0f%n", gareen.hp); 12 13 //多线程同步问题指的是多个线程同时修改一个数据的时候,导致的问题 14 15 //假设盖伦有10000滴血,并且在基地里,同时又被对方多个英雄攻击 16 17 //用JAVA代码来表示,就是有多个线程在减少盖伦的hp 18 //同时又有多个线程在恢复盖伦的hp 19 20 //n个线程增加盖伦的hp 21 22 int n = 10000; 23 24 Thread[] addThreads = new Thread[n]; 25 Thread[] reduceThreads = new Thread[n]; 26 27 for (int i = 0; i < n; i++) { 28 Thread t = new Thread(){ 29 public void run(){ 30 gareen.recover(); 31 try { 32 Thread.sleep(100); 33 } catch (InterruptedException e) { 34 // TODO Auto-generated catch block 35 e.printStackTrace(); 36 } 37 } 38 }; 39 t.start(); 40 addThreads[i] = t; 41 42 } 43 44 //n个线程减少盖伦的hp 45 for (int i = 0; i < n; i++) { 46 Thread t = new Thread(){ 47 public void run(){ 48 gareen.hurt(); 49 try { 50 Thread.sleep(100); 51 } catch (InterruptedException e) { 52 // TODO Auto-generated catch block 53 e.printStackTrace(); 54 } 55 } 56 }; 57 t.start(); 58 reduceThreads[i] = t; 59 } 60 61 //等待所有增加线程结束 62 for (Thread t : addThreads) { 63 try { 64 t.join(); 65 } catch (InterruptedException e) { 66 // TODO Auto-generated catch block 67 e.printStackTrace(); 68 } 69 } 70 //等待所有减少线程结束 71 for (Thread t : reduceThreads) { 72 try { 73 t.join(); 74 } catch (InterruptedException e) { 75 // TODO Auto-generated catch block 76 e.printStackTrace(); 77 } 78 } 79 80 //代码执行到这里,所有增加和减少线程都结束了 81 82 //增加和减少线程的数量是一样的,每次都增加,减少1. 83 //那么所有线程都结束后,盖伦的hp应该还是初始值 84 85 //但是事实上观察到的是: 86 87 System.out.printf("%d个增加线程和%d个减少线程结束后%n盖伦的血量变成了 %.0f%n", n,n,gareen.hp); 88 } 89 90 }Concurrency_test.java
1 package UML; 2 import java.util.*; 3 public class Hero implements Comparable<Hero>{ 4 public String name; 5 public float hp; 6 public int damage; 7 public Hero() { 8 } 9 // 增加一个初始化name的构造方法 10 public Hero(String name) { 11 12 this.name = name; 13 } 14 // 重写toString方法 15 public String toString() { 16 //return String.format("[name:%s hp:%.0f damage:%.0f]%n", name,hp,damage); 17 return "Hero [name=" + name + ", hp=" + hp + ", damage=" + damage + "]\r\n"; 18 } 19 public Hero(String name,int hp,int damage) { 20 this.name=name; 21 this.hp=hp; 22 this.damage=damage; 23 } 24 @Override 25 public int compareTo(Hero anotherHero) { 26 if(damage<anotherHero.damage) 27 return 1; 28 else 29 return -1; 30 } 31 public void attackHero(Hero h) { 32 //把暂停时间去掉,多条线程各自会尽力去占有CPU资源 33 //线程的优先级效果才可以看得出来 34 /*try { 35 Thread.sleep(1000); 36 }catch (InterruptedException e) { 37 // TODO Auto-generated catch block 38 e.printStackTrace(); 39 }*/ 40 h.hp-=damage; 41 System.out.format("%s is attacking %s,%s'blood now is %.0f%n",name,h.name,h.name,h.hp); 42 if(h.isDead()) 43 System.out.println(h.name +"死了!"); 44 } 45 public boolean isDead() { 46 return 0>=hp?true:false; 47 } 48 //回血 49 public void recover(){ 50 hp=hp+1; 51 } 52 53 //掉血 54 public void hurt(){ 55 hp=hp-1; 56 } 57 }Hero.java
- 同步问题产生的原因
1. 假设增加线程先进入,得到的hp是10000
2. 进行增加运算
3. 正在做增加运算的时候,还没有来得及修改hp的值,减少线程来了
4. 减少线程得到的hp的值也是10000
5. 减少线程进行减少运算
6. 增加线程运算结束,得到值10001,并把这个值赋予hp
7. 减少线程也运算结束,得到值9999,并把这个值赋予hp
hp,最后的值就是9999
虽然经历了两个线程各自增减了一次,本来期望还是原值10000,但是却得到了一个9999
这个时候的值9999是一个错误的值,在业务上又叫做脏数据
- 同步问题解决思路是: 在增加线程访问hp期间,其他线程不可以访问hp
1. 增加线程获取到hp的值,并进行运算
2. 在运算期间,减少线程试图来获取hp的值,但是不被允许
3. 增加线程运算结束,并成功修改hp的值为10001
4. 减少线程,在增加线程做完后,才能访问hp的值,即10001
5. 减少线程运算,并得到新的值10000