多线程的补充 关于类锁和对象锁的理解

锁什么时候需要使用?

多线程并发时,多个线程共享同一个对象的时候,会存在线程安全问题,因此要用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不需要占锁的时候,类锁是不是被占用对它没有任何影响,类锁被占用只会让同样需要占锁执行(这个占锁可以是类锁,也可以是对象锁)的线程等待。

总结:一个类只有一把类锁,当类锁被占用的时候,只要是需要占锁执行的线程就必须等待类锁的释放才可以执行。

发布了6 篇原创文章 · 获赞 20 · 访问量 1182

猜你喜欢

转载自blog.csdn.net/weixin_46521681/article/details/105743778
今日推荐