锁什么时候需要使用?
多线程并发时,多个线程共享同一个对象的时候,会存在线程安全问题,因此要用synchronized语句块来保护数据的安全。synchronized修饰就是上锁。
一、对象锁
概述: 每个类创建的对象都对应一把锁,如果是多个线程访问不同对象的时候,其它线程不用等待锁的释放,因为它们是不同的锁。
请看代码。
public class Test {
public static void main(String[] agrs){
Account account1=new Account("Jack",1000);
Account account1=new Account("Tom",5000);
//线程一和线程二不是同一个对象
Thread t1=new Thread(new MyThread(account1));
Thread t2=new Thread(new MyThread(account2));
t1.setName("线程一");
t1.setName("线程二");
//启动线程
t1.start();
t2.start();
}
}
class MyThread implements Runnable{
Account account;
public MyThread(Account account){
this.account=account;
}
//重写run方法
public void run(){
account.drawMoney(500);
}
}
class Account{
private String name;
private double balance;
public Account(String name,double balance){
this.name=name;
this.balance=balance;
}
public double getBalance(){
return this.balance;
}
public void drawMoney(double money){
//this表示当前对象
synchronized(this){
double after=getBalance()-money;
this.balance=after;
//让线程一睡眠5秒,如果线程二需要等待线程一释放锁之后才能执行。
//则线程二的执行结果会延迟至少5秒后才会出现,否则则证明两个线程之间互不干扰
if("线程一".equals(Thread.currentThread().getName())){
try{
//模拟网络延迟5秒
Thread.sleep(1000*5);
}catch(Exception e){
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"取款成功"+" 剩余:"+getBalance());
}
}
}
- 执行该代码,我们会发现,线程二并没有任何等待就执行了,这说明synchronized锁住的对象是不同的,因为是两个对象,占有的锁不同,因此互不干扰。
总结:每个对象对应一把锁,只有有共同对象的线程才会等待锁的释放(这就是对象锁)。
二、类锁
概述: 类锁和对象锁不同,每个对象都有一把对应的锁,而类锁是一个类只有一把锁,类锁和对象锁的作用相同,也是使得线程同步执行,而非异步执行。
类锁会出现在哪里?
在“我的上一篇博客:多线程总结(下)”中提到过,synchronized可以修饰部分代码,也可以修饰整个方法,当synchronized修饰的方法是静态方法(static)修饰的方法的时候(因为static修饰的方法属于类方法),锁住的就是类锁。
请看代码。(这个例子是一个多线程的面试题)
public class Exam01 {
public static void main(String[] args) throws InterruptedException {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new MyThread(mc1);
Thread t2 = new MyThread(mc2);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000); //这个睡眠的作用是:为了保证t1线程先执行。
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc){
this.mc = mc;
}
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass {
// synchronized出现在静态方法上是找类锁。
public synchronized static void doSome(){
System.out.println("doSome begin");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized static void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
执行结果: 需要等待线程t1的结束线程t2才可以执行。
- 线程t1和线程t2访问的是两个不同的方法,但在执行的时候还是需要等待线程t1的执行结束线程t2才可以执行,因为synchronized出现在了静态方法上,占用的是类锁,类锁被占,其它需要占锁执行的线程都必须等待。
- 如果将doOther()方法上的synchronized去掉的话就不需要等待线程t1执行完成,这是因为,当线程t2不需要占锁的时候,类锁是不是被占用对它没有任何影响,类锁被占用只会让同样需要占锁执行(这个占锁可以是类锁,也可以是对象锁)的线程等待。
总结:一个类只有一把类锁,当类锁被占用的时候,只要是需要占锁执行的线程就必须等待类锁的释放才可以执行。
有用就好评,喜欢就点赞,持续关注,精彩不断!