防止多个线程访问数据对象时,对数据造成破坏或者是说多个线程访问同一个对象进行数据处理时,发生了错误,因此就需要线程同步。
我们来创建一个实例进行说明:
我们来构建一个信用卡账户,起初额度为100w,然后模拟透支,存款等操作。
1、创建一个非同步的实例
第一步:创建一个User对象
创建一个User对象,添加两个变量账号(code)和余额(cash);添加一个处理业务的方法oper()。
代码如下:
package thread_synchronization;
public class User {
private String code;//账号
private int cash;//金额
public User(String code,int cash){
this.code=code;
this.cash=cash;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public int getCash() {
return cash;
}
public void setCash(int cash) {
this.cash = cash;
}
/**
* 计算剩余额度
* @param x
*/
public void oper(int x){
try {
this.cash+=x;
System.out.println(Thread.currentThread().getName()+"运行结束,增加"+x+","+"当前余额剩余为:"+cash);
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public String toString() {
return "User{" + "code='" + code + '\'' + ",cash=" + cash + '}';
}
}
第二步:创建线程
public class ThreadExtend_Synchronization extends Thread{
private User user;
private int y;
ThreadExtend_Synchronization(String name, User user, int y){
super(name);
this.user=user;
this.y=y;
}
@Override
public void run() {
user.oper(y);
}
public static void main(String args[]){
User user=new User("张三",100);
Thread thread1=new ThreadExtend_Synchronization("线程A",user,1);
Thread thread2=new ThreadExtend_Synchronization("线程B",user,2);
Thread thread3=new ThreadExtend_Synchronization("线程C",user,3);
Thread thread4=new ThreadExtend_Synchronization("线程D",user,4);
Thread thread5=new ThreadExtend_Synchronization("线程E",user,-10);
Thread thread6=new ThreadExtend_Synchronization("线程F",user,-20);
Thread thread7=new ThreadExtend_Synchronization("线程G",user,-30);
Thread thread8=new ThreadExtend_Synchronization("线程H",user,-40);
Thread thread9=new ThreadExtend_Synchronization("线程I",user,-50);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
thread6.start();
thread7.start();
thread8.start();
thread9.start();
}
}
第三步:运行结果
运行结果如下:
线程B运行结束,增加2,当前余额剩余为:103
线程A运行结束,增加1,当前余额剩余为:103
线程D运行结束,增加4,当前余额剩余为:107
线程E运行结束,增加-10,当前余额剩余为:97
线程C运行结束,增加3,当前余额剩余为:100
线程F运行结束,增加-20,当前余额剩余为:40
线程H运行结束,增加-40,当前余额剩余为:60
线程I运行结束,增加-50,当前余额剩余为:-10
线程G运行结束,增加-30,当前余额剩余为:-40
通过结果,我们发现,数据计算并不正确,并不是我们想要的结果,原因是因为多个线程对同一个对象user不加访问的修改其数据锁导致的。
要修改成正确的,我们只要保证每一次对User对象的oper()方法只有一个线程在访问就可以了。
这个时候我么就需要synchronized线程同步了。
2、修改成线程同步的实例
Java当中每个对象都有一个内置的锁,当程序运行到非静态的synchronized同步方法上时,自动获取当前实例的锁,获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
关于锁和同步,有以下几个要点:
(1)、只能同步方法,不能同步变量和类。
(2)、每个对象只能有一个锁,当提到同步时,应该清楚再什么条件下进行同步,再什么位置进行同步。
(3)、如果两个线程要执行一个类的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能拥有一个线程执行方法,另一个线程需要等待,直到锁被释放。
(4)、如果线程拥有同步方法和非同步方法,那么非同步方法可以被多个线程自由访问而不受锁的限制。
(5)、线程睡眠时,它所持有的任何锁都不会被释放。
(6)、线程可以获得多个锁。比如说在一个对象的同步方法里可以调用另外一个对象的同步方法。
(7)、同步损害并发性。应该尽可能的缩小同步范围,同步不但可以同步整个方法,还可以同步方法中的一部分代码。
我们在原先的oper()方法上加上synchronized,并在方法里加上个睡眠sleep,修改后的代码如下:
/**
* (1)、synchronized定义在方法上
* (2)、计算剩余额度
* (3)、使用synchronized锁定线程对象,实现线程同步,从而保证计算结果的正确性,如果去掉synchronized,数据会发生错误,是错误的
* @param x
*/
public synchronized void oper(int x){
try {
Thread.sleep(10L);
this.cash+=x;
System.out.println(Thread.currentThread().getName()+"运行结束,增加"+x+","+"当前余额剩余为:"+cash);
Thread.sleep(10L);
}catch (Exception e){
e.printStackTrace();
}
}
我们重新再运行,结果如下:
线程A运行结束,增加1,当前余额剩余为:101
线程I运行结束,增加-50,当前余额剩余为:51
线程H运行结束,增加-40,当前余额剩余为:11
线程G运行结束,增加-30,当前余额剩余为:-19
线程F运行结束,增加-20,当前余额剩余为:-39
线程E运行结束,增加-10,当前余额剩余为:-49
线程D运行结束,增加4,当前余额剩余为:-45
线程C运行结束,增加3,当前余额剩余为:-42
线程B运行结束,增加2,当前余额剩余为:-40
通过结果我们可以发现,每一个线程输出的结果都是正确的。