我们为什么需要synchronized?
先谈谈并发:当同一个对象,被多个线程,同时访问时就是并发。
大家都是一起操作并修改数据,特别是在读和写之间存在延时,就会导致的数据的混乱不准确。
做了两年的财务开发,这次就拿取钱做案例吧:
public class Test {
public static void main(String[] args) {
//同一个对象
WithdrawMoney wm = new WithdrawMoney();
new Thread(wm, "小明").start();
new Thread(wm, "小红").start(); //多个线程
}
}
class WithdrawMoney implements Runnable {
private Integer money = 1000;
@Override
public void run() {
if (money > 900) {//想要取900
try {
if (Thread.currentThread().getName().equals("小明")) {//模拟小明存在网络延时,1秒
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
money = money - 900;//取900
System.out.println(Thread.currentThread().getName()+":现在还剩" + money);
}else {
System.out.println(Thread.currentThread().getName()+":余额不足");
}
}
}
--------------------------------------
输出结果为:
小红:现在还剩100
小明:现在还剩-800
明显的,虽然小明执行if (Test.money > 0)时通过了,但一秒后进行扣款时,被小红先扣走了,导致小明再进行扣款,总金额为负数了。
为了避免这种情况,保证数据的准确性与同步,我们可以使用synchronized来进行控制。
同步方法
用synchronized修饰的方法,称为同步方法。
public synchronized void method(int args) {}
当多个线程使用同一个同步方法时,会进行排队等待,一个一个线程执行。
注意,一定要是同一个同步方法,多个实例化对象的相同名字的同步方法,并不是同一个。
使用同步方法改写取钱案例:
public class Test {
public static void main(String[] args) {
//同一个对象
WithdrawMoney wm = new WithdrawMoney();
new Thread(wm, "小明").start();
new Thread(wm, "小红").start(); //多个线程
}
}
class WithdrawMoney implements Runnable {
private Integer money = 1000;
@Override
public void run() {
withdraw();
}
synchronized void withdraw() {
if (money > 900) {//想要取900
try {
if (Thread.currentThread().getName().equals("小明")) {//模拟小明存在网络延时,1秒
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
money = money - 900;//取900
System.out.println(Thread.currentThread().getName()+":现在还剩" + money);
}else {
System.out.println(Thread.currentThread().getName()+":余额不足");
}
}
}
-------------------------------
小明:现在还剩100
小红:余额不足
虽然小明存在延时,但小红得等小明全部执行完后,才能继续执行。
同步块
用synchronized修饰的代码块,称为同步块。
synchronized (obj){ } // obj称之为同步监视器
同步块中的obj称为同步监视器,obj可以是任何对象,但是推荐使用共享资源作为同步监视
器
使用同步块改写取钱案例:
public class Test {
public static void main(String[] args) {
// 同一个对象
WithdrawMoney wm = new WithdrawMoney();
new Thread(wm, "小明").start();
new Thread(wm, "小红").start(); // 多个线程
}
}
class WithdrawMoney implements Runnable {
private Integer money = 1000;
@Override
public void run() {
synchronized (this) {//this表示同一个的实例化对象wm,此处也可以改为锁money
if (money > 900) {// 想要取900
try {
if (Thread.currentThread().getName().equals("小明")) {// 模拟小明存在网络延时,1秒
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
money = money - 900;// 取900
System.out.println(Thread.currentThread().getName() + ":现在还剩" + money);
} else {
System.out.println(Thread.currentThread().getName() + ":余额不足");
}
}
}
}
-------------------------------
小明:现在还剩100
小红:余额不足
在同步块中是锁对象还是锁成员变量,取决于具体的场景,但务必要保证可以锁住。在能锁住的情况下,锁的范围越小,自然是对其他线程影响越小,效率越高越好的。