synchronized使用案例
并发问题的代码如下:
private static volatile int j=100;
public static void main(String[] args) throws InterruptedException {
updateJ updateJ = new updateJ(2, 100);
updateJ updateJ1 = new updateJ(-3, 1000);
updateJ.start();
updateJ1.start();
}
private static class updateJ extends Thread{
int i=0;
long sleepTime=0;
public updateJ(int i,long executeTime){
this.i=i;
this.sleepTime=executeTime;
}
@Override
public void run() {
int y = getJ();
System.out.println(Thread.currentThread().getName()+"开始更新数据库中的j,此时数据中 j为:"+y);
j =y-i;
System.out.println(Thread.currentThread().getName()+"第一次将数据库中的j更新写成了:"+getJ());
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"第二次从数据库中读取j成了:"+getJ());
}
private int getJ(){
return j;
}
}
结果分析
- 可以看到结果,线程0和线程1 可以理解为两个人先后去调用接口更新数据中的数据j. 线程0对j 减二, 线程1对j加3
- 从有序性上来说,假设对于一个线程在对数据库中的j变量进行修改前,先进行读取,然后进行修改,是要保证其他线程不能读取和修改的.否则会导致多个线程第一次读取的j都是一样的. 比如上图上显示的两个线程都同时读到了j为100.这就破坏了有序性.因为其他线程有可能还要对读到的数据进行修改.此时如果不按照顺序,那么线程各自就会对初始读到的数据进行修改了.
- 从原子性来说,线程0在进行写的同时,线程1按理是不允许进行读写的.但是上图,不仅读了,还对其进行了修改.导致线程0修改的失效了.
- 从可见性来说,虽然j变量加了volatile关键字,但是因为前面的有序性和原子性被破坏.导致即使保证了可见性也没有意义了.
- 这就是典型的并发情况下的读写有序性,原子性.和可见性问题.
优化后代码
对上面案例进行优化如下:
private static volatile int j=100;
public static void main(String[] args) throws InterruptedException {
updateJ updateJ = new updateJ(2, 1000);
updateJ updateJ1 = new updateJ(-3, 1000);
updateJ.start();
updateJ1.start();
}
private static class updateJ extends Thread{
int i=0;
long sleepTime=0;
public updateJ(int i,long executeTime){
this.i=i;
this.sleepTime=executeTime;
}
@Override
public void run() {
synchronized (updateJ.class){
System.out.println(Thread.currentThread().getName()+"开始更新数据库中的j,此时数据中 j为:"+getJ());
j =getJ()-i;
System.out.println(Thread.currentThread().getName()+"第一次将数据库中的j更新写成了:"+getJ());
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"第二次从数据库中读取j成了:"+getJ());
}
}
private synchronized int getJ(){
return j;
}
}
结果分析
- 从有序性上来说,线程0和线程1读取数据是有顺序的.如果线程0先获取到synchronized锁.就先执行读写
- 从原子性来说,多个线程抢占锁.只有一个线程抢的到.可以对变量进行操作.
- 从可见性来说,线程0更新完了数据之后,线程1再去获取到锁之后是最新的数据.
总结
synchronized可以保证多个线程执行的顺序,保证了有序性,在获取变量的时候会将本地内存删掉.然后从主内存重新获取,保证了可见性, 线程读写完全隔离,从而保证了原子性.