原子性和synchronized对象锁
原子性
原子指化学反应不可再分的基本微粒,在互联网指的是一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行,其实也可以看作"一个"操作,这个操作只能成功或者失败。
比如我们在银行转钱,B要转钱1000块钱到A的账户,第一步获取B账户的钱是否大于1000块钱 ,如果大于1000块钱那么就扣减1000,然后A账户加钱1000元,按照常理来说是不会有问题的,但是如果不是原子性,那么就会被其他干扰,或者在你做操作的时候其他人同时也来做操作,比如现在B的老婆同时也要转钱给C,B来查询钱是1000元,B老婆来查询也是1000元,这个时候同时来进行扣减就会B转的1000元,B老婆转的1000元,转给A和C,虽然B的账户只有1000但是转出了2000块钱 ,这是不合理的
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Account b = new Account(); // b账户
Account a = new Account(); // a账户
Account c = new Account(); // c账户
Thread thread = new Thread(() -> { // b转钱给a
if (b.getMoney() >= 1000) { // 如果账户余额大于 1000
// 扣减 b账户1000元
log.debug("B操作");
b.deduct(1000);
// a账户添加1000元
a.add(1000);
}
}, "B操作");
Thread thread2 = new Thread(() -> { // b老婆转钱给c
if (b.getMoney() >= 1000) { // 如果账户余额大于 1000
// 扣减 b账户1000元
log.debug("B老婆操作");
b.deduct(1000);
// c账户添加1000元
c.add(1000);
}
}, "B老婆操作");
// 启动线程
thread.start();
thread2.start();
// 等待两个线程结束
thread.join();
thread2.join();
log.debug("b账户-->{}", b.getMoney() + "");
log.debug("a账户-->{}", a.getMoney() + "");
log.debug("c账户-->{}", c.getMoney() + "");
}
}
@Data
class Account { // 账户对象 有一余额,加钱,扣钱
private int money = 1000;
void deduct(int num) {
this.money -= num;
}
void add(int num) {
this.money += num;
}
}
最终我们发现b的账户已经是负数了,a和c账户也加上钱
Synchronized
synchronized对象锁能够使多个操作变成原子性操作,解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
通俗的说就是给把代码放到一个有锁的房间里运行,每次只能有一个人拿到钥匙进去,他把他要干的事情做完出来 把钥匙放出来然后下一个人拿到钥匙,有进去操作出来。 但是会有很多这样的房间,你需要拿对应的钥匙进对应的房间。
Synchronized用法
Synchronized代码块
class TestSynchronized {
public void test1() {
synchronized (this) {
int i = 5;
while (i-- > 0) {
log.debug(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public void test2() {
synchronized (this) {
int i = 5;
while (i-- > 0) {
log.debug(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public static void main(String[] args) {
final TestSynchronized myt2 = new TestSynchronized();
Thread t1 = new Thread(myt2::test1, "t1");
Thread t2 = new Thread(myt2::test2, "test2");
t1.start();
t2.start();
}
}
上面代码我们发现我们只new了一个TestSynchronized对象,也就是说我们用的一个对象调用的test1方法和test2方法,而里面的 synchronized (this) {}
锁对象this都是一个对象TestSynchronized对象,两个方法谁去先拿到对象锁谁就先执行,synchronized 代码块里面包裹的代码是原子性的可以看做一个操作,所以说没有发生交替执行的情况
public static void main(String[] args) {
// 创建对象
final TestSynchronized myt2 = new TestSynchronized();
final TestSynchronized myt1 = new TestSynchronized();
// 创建线程
Thread t2 = new Thread(myt2::test2, "t1");
Thread t1 = new Thread(myt1::test1, "test2");
t1.start();
t2.start();
}
这里我们改变mian方法 new 了2个TestSynchronized 对象 myt1和调用test1方法,myt2调用test2方法, 而test1方法的synchronized (this) {}的this拿到的就是myt1对象,而test2方法的this拿到的就是myt2对象,就相当于大家都拿到了锁进入了各自的房间执行
如何解决这个问题啊,那么就去找他们两都能拿到的对象当锁 比如TestSynchronized .class对象他们都能拿到,或者Object对象
当我们换成.class对象后两个方法都去拿同一个锁 拿到锁的就执行 没拿到的就等待
Synchronized修饰方法
这里有两种一个是修饰静态方法,一个是修饰类方法
如果是修饰修饰静态方法其实锁是该对象的class对象
public static synchronized void test3() {
}
// 两种写法意思差不多
public static void test4() {
synchronized (TestSynchronized.class) {
}
}
如果是修饰修饰类方法其实锁是该类自己也就是this
public synchronized void test3() {
}
// 两种写法意思差不多
public void test4() {
synchronized (this) {
}
}
改造原子性案例
我们知道了Synchronized的用法那么来改造一下刚才原子性用的案例
我们只要保证读取-扣减-添加是原子性的就能保证b账户钱的正确性
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Account b = new Account(); // b账户
Account a = new Account(); // a账户
Account c = new Account(); // c账户
Thread thread = new Thread(() -> { // b转钱给a
synchronized (b) {
if (b.getMoney() >= 1000) { // 如果账户余额大于 1000
// 扣减 b账户1000元
log.debug("B操作");
b.deduct(1000);
// a账户添加1000元
a.add(1000);
}
}
}, "B操作");
Thread thread2 = new Thread(() -> { // b老婆转钱给c
synchronized (b) {
if (b.getMoney() >= 1000) { // 如果账户余额大于 1000
// 扣减 b账户1000元
log.debug("B老婆操作");
b.deduct(1000);
// c账户添加1000元
c.add(1000);
}
}
}, "B老婆操作");
thread.start();
thread2.start();
thread.join();
thread2.join();
log.debug("b账户-->{}", b.getMoney() + "");
log.debug("a账户-->{}", a.getMoney() + "");
log.debug("c账户-->{}", c.getMoney() + "");
}
}
@Data
class Account { // 账户对象 有一余额,加钱,扣钱
private int money = 1000;
void deduct(int num) {
this.money -= num;
}
void add(int num) {
this.money += num;
}
}
我们把b账户对象当锁就解决了这个问题,当然也可以用Account.class,a对象,c对象也是一样的