一、如何解决原子性问题?
原子性问题的产生原因是“线程切换”,那么只要禁止线程切换就可以避免原子性问题,不管是单核还是多核cpu,都必须禁止同一时刻只有一个线程在执行。
二、锁
锁的含义:保护需要保护的资源
三、synchronized(同步锁/互斥锁)
java编译器会在synchronized修饰的代码前后加上加锁和解锁的功能。
3.1 修饰普通方法 = 锁住当前实例
示例:多个线程同时访问同一同步方法,则串行执行,即一个线程执行完,才会轮到下一个线程。
public class SynchronizedTest {
// 修饰普通方法
// 锁的是当前实例对象this
synchronized void foo1() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " foo1 begin");
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() + " foo1 end");
}
public static void main(String[] args) throws InterruptedException {
SynchronizedTest synchronizedTest1 = new SynchronizedTest();
Thread a = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " synchronizedTest1");
synchronizedTest1.foo1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread b = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " synchronizedTest1");
synchronizedTest1.foo1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
a.start();
b.start();
}
总结:修饰普通方法产生的对象锁只能锁住当前对象的同步方法。
3.2 修饰普通变量 = 锁住当前实例
例:多个线程同时访问同一个对象的同一个方法,该方法里含有锁(普通变量),则串行执行。
package exercise_01_0705_synchronized;
public class SynchronizedTest2 {
// 2 修饰普通变量
private Object object = new Object();
private void foo2() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " foo2 begin");
synchronized (object) {
System.out.println(Thread.currentThread().getName() + " object lock");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " object unlock");
}
System.out.println(Thread.currentThread().getName() + " foo2 end");
}
public static void main(String[] args) throws InterruptedException {
SynchronizedTest2 synchronizedTest2 = new SynchronizedTest2();
Thread a = new Thread(()->{
try {
synchronizedTest2.foo2();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread b = new Thread(()->{
try {
synchronizedTest2.foo2();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
a.start();
b.start();
}
}
总结:修饰普通变量产生的对象锁只能锁住当前对象的所有跟给该变量加锁的代码块,不会影响其他对象,也不会影响当前对象的其他加锁代码块
3.3 修饰this对象 = 锁住当前实例 (同3.1)
示例:多个线程用一个对象同时访问一个方法,该方法里加了this的同步代码块,则会串行执行
package exercise_01_0705_synchronized;
public class SynchronizedTest3 {
public void foo3() throws InterruptedException {
// 修饰 this对象
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " foo3 lock");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " foo3 unlock");
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedTest3 synchronizedTest1 = new SynchronizedTest3();
Thread a = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " synchronizedTest1");
synchronizedTest1.foo3();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread b = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " synchronizedTest1");
synchronizedTest1.foo3();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
a.start();
b.start();
}
}
结果:
结论同3.1
3.4 修饰静态方法 = 类锁
示例:多个线程同时调用synchronized修饰的静态方法,则会串行执行
package exercise_01_0705_synchronized;
public class SynchronizedTest4 {
// 修饰静态方法
// 锁的是当前这个类
synchronized static void foo1() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " foo1 lock");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " foo1 unlock");
}
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " synchronizedTest1");
foo1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread b = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " synchronizedTest1");
foo1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
a.start();
b.start();
}
}
总结:类锁只能锁住当前类的所有加锁方法,和对象锁同时出现时,是互相不影响的。
3.5 修饰静态变量 = 类锁
示例:多个线程同时访问一个方法(加了静态变量锁),则会串行执行
package exercise_01_0705_synchronized;
public class SynchronizedTest5 {
private static Object objectStatic = new Object();
public void foo() throws InterruptedException {
// 4 修饰静态对象
synchronized (objectStatic) {
System.out.println(Thread.currentThread().getName() + " foo lock");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " foo unlock");
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedTest5 synchronizedTest1 = new SynchronizedTest5();
Thread a = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " synchronizedTest1");
synchronizedTest1.foo();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread b = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " synchronizedTest1");
synchronizedTest1.foo();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
a.start();
b.start();
}
}
总结:修饰静态变量产生的类锁,只能锁住关于该静态变量的相关代码块,锁不住其他同步代码块或者其他静态变量的代码块的哈
3.6 修饰类名 = 类锁
示例:多个线程同时访问一个方法(加了类锁),则会串行执行
package exercise_01_0705_synchronized;
public class SynchronizedTest7 {
public void foo() throws InterruptedException {
// 修饰类
synchronized (SynchronizedTest7.class) {
System.out.println(Thread.currentThread().getName() + " foo lock");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " foo unlock");
}
}
public static void main(String[] args) throws InterruptedException {
SynchronizedTest7 synchronizedTest1 = new SynchronizedTest7();
Thread a = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " synchronizedTest1");
synchronizedTest1.foo();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread b = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + " synchronizedTest1");
synchronizedTest1.foo();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
a.start();
b.start();
}
}
结论同3.4
3.7 总结
修饰普通方法,this对象的作用一样,都产生的是对象锁。
对象锁只对当前对象起作用,换句话说,当前对象需要加锁的地方(哪怕是不同的方法),都用的是同一把锁。
修饰普通变量,产生的也是对象锁,但控制范围会小一些,只对该对象里的该普通变量的同步块起作用。
修饰静态方法,该类名的作用一样,都产生的是类锁。
而类锁对整个类起作用,该类需要加锁的地方(哪怕是不同的静态方法)都用的是用一把锁。
修饰静态变量,产生的也是类锁,但控制范围会小一些,只会对该类的该静态变量的同步块起作用。
当类锁和对象锁同时出现时,互不影响哈。
四、synchronized解决问题
4.1 银行取款问题
银行转账肯定是要账户余额加锁的,那么加锁的方案有几种呢?
1、不可用方案:
package exercise_01_0705_synchronized;
public class Account {
private Integer balance = 10000; // 账户余额
// 取款操作
private void withDraw(Integer amt) throws InterruptedException {
synchronized (this.balance) {
System.out.println(Thread.currentThread().getName() + " withDraw begin " + balance);
balance -= amt;
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " withDraw end " + balance);
}
}
private Integer getBalance() {
synchronized (this.balance) {
return balance;
}
}
public static void main(String[] args) throws InterruptedException {
Account account = new Account();
Thread a = new Thread(()->{
try {
account.withDraw(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread b = new Thread(()->{
try {
account.withDraw(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread c = new Thread(() -> {
System.out.println(account.getBalance());
});
a.start();
Thread.sleep(10);
b.start();
c.start();
}
}
线程1没走完,线程2就进来了,原因是线程1修改了balance的值,导致锁失效,所以 锁不能用可变对象控制。
可用方案:
4.2 银行转账操作
银行转账操作是一个连续的操作,a转账给b,那么a的钱减少,b的钱增加
不可用方案:
package exercise_01_0705_synchronized;
public class Account2 {
private Integer balance = 10000; // 账户余额
// 转账操作
synchronized void transfer(Account2 account2,int amt) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " transfer begin");
account2.balance += amt;
Thread.sleep(1000);
this.balance -= amt;
System.out.println(Thread.currentThread().getName() + " transfer end");
}
public static void main(String[] args) {
Account2 account = new Account2();
Account2 account2 = new Account2();
Account2 account3 = new Account2();
Thread a = new Thread(()->{
try {
account.transfer(account2,100);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread b = new Thread(()->{
try {
account2.transfer(account3,90);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
a.start();
b.start();
}
}
在第一个转账还没完全结束,第二个转账就已经来了。
理由:该同步代码块里有两个对象,但是给方法加synchronized只能锁住当前对象的,锁不住传入对象的。
解决方法:
创建一个类锁来解决。
总结:没有关系的资源用不同的锁,如果要保护多个资源,就要用一个范围比较大的锁来控制。