一:锁的概念
只有获取到这个锁后才会执行相应的代码,可以用任意对象当作锁
例如:
public class T {
private int count = 0;
Object o = new Object();
public void m() {
synchronized(o) {
count ++;
System.out.println(Thread.currentThread().getName()+"count="+count);
}
}
}
二:锁的几种方式
synchronized(this) // 锁定当前对象,获取到this锁才会执行相应代码
public synchronized void k() {} // 等同于synchronized(this)
- 静态方法加锁
public class T {
private static int count = 0;
// 等同于synchronized(T.class)
public synchronized static void n () {
count ++;
System.out.println(Thread.currentThread().getName()+"count="+count);
}
}
三:锁的使用场景
比如多个线程对同一个数据进行读写,若不加锁将可能会出现脏读
例子:
/**
* 模拟银行账户
* 对业务写方法加锁
* 对业务读方法不加锁
*
* 这样将会容易出现脏读:即读到的内容是写过程前的数据,此时正在写但还未写完成
* @author 86152
*/
public class Account {
String name;
Double balance = 0.0;
public synchronized void set(String name,Double balance) {
this.name = name;
try {
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
this.balance = balance;
}
public Double get() {
return balance;
}
public static void main(String[] args) {
Account account = new Account();
new Thread(()->{
account.set("zhangsan",100.0);
}).start();
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(account.get());
try {
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(account.get());
}
}
结果:
0.0
100.0
读写均加锁后:
public class Account {
String name;
Double balance = 0.0;
public synchronized void set(String name,Double balance) {
this.name = name;
try {
Thread.sleep(2000);
}catch (Exception e){
e.printStackTrace();
}
this.balance = balance;
}
public synchronized Double get() {
return balance;
}
public static void main(String[] args) {
Account account = new Account();
new Thread(()->{
account.set("zhangsan",100.0);
}).start();
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(account.get());
try {
TimeUnit.SECONDS.sleep(2);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(account.get());
}
}
加锁后结果:
100.0
100.0
A获取到锁后,在A未执行完并释放锁时B不能执行同样需要锁的代码。只有等A释放锁后B获取到锁B才能继续执行。
四:synchronized是可重入锁
public synchronized void m() {
m1();
}
public synchronized void m1() {
}
一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁。也就是说synchronized获得的锁是可重入的
五:异常和锁
程序在执行过程中,如果出现异常,默认情况下锁会被释放。所以在并发处理过程中,要注意同步代码中出现异常可能会出现数据不一致情况。
例如:多线程访问同一资源,如果异常处理不合适,在第一个线程中抛出异常,其它线程将会进入同步代码区,有可能会访问到异常产生时的数据。
因此:处理同步业务逻辑时要谨慎处理异常
六:总结
1.在非必要加锁的情况下,能不加锁就不加,因为加锁后会严重影响效率
2.同步代码块中的代码越少越好,能提高效率。但若有较多同步代码块,也需适当合并成较少代码块
3.用作锁对象的对象最好加上finalfinal Object o = new Object();
4.基本类型不要用来做锁对象