Java并发编程3 —— 对象锁和类锁

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/qq_25246305/article/details/82633694

synchronized关键字作用在同步代码块上,给共享变量“上锁”可以解决线程安全问题。这把“锁”可以作用在某个对象上,也可以作用在某个类上。

举个栗子,有个自助银行,里面有两台ATM机,工作人员可以看到每次存取款之后机器里钞票的总金额数。现在有两个人来存钱,各存50元。

没有锁

在下面的代码中,两个线程t1、t2相当于两个人,每个Service对象相当于一台ATM机。这里先只创建了一个Service对象,也就是两个人向同一台ATM机里存钱。显然,第一个人存完后机器里应当有150元,第二个人存完后有200元。

public class Bank {

    public static void main(String[] args) {
        Service s1 = new Service();
        //Service s2 = new Service();
        Thread t1 = new Thread(s1, "t1");
        Thread t2 = new Thread(s1, "t2");
        t1.start();
        t2.start();
    }
}

class Service implements Runnable{
    private int total = 100;

    @Override
    public void run() {
        total += 50;
        System.out.println(Thread.currentThread().getName() + " --- total = " + total);
    }
}

输出:

t1 --- total = 200
t2 --- total = 200

显然是两个人同时操作“余额”这个共享变量时出现了问题,第一个人存完50元后即读到了第二个人存完50元后的余额200元。

对象锁

我们给run方法加上synchronized关键字,相当于给存钱和修改余额的操作原子化,加上一把锁。

public class Bank {

    public static void main(String[] args) {
        Service s1 = new Service();
        //Service s2 = new Service();
        Thread t1 = new Thread(s1, "t1");
        Thread t2 = new Thread(s1, "t2");
        t1.start();
        t2.start();
    }
}

class Service implements Runnable{
    private int total = 100;

    @Override
    public synchronized void run() {
        total += 50;
        System.out.println(Thread.currentThread().getName() + " --- total = " + total);
    }
}

输出:

t1 --- total = 150
t2 --- total = 200

这里的结果就是正确的了。第一个人存完后看到余额150元,第二个人存完后看到余额200元。

如果线程t2使用另一个对象s2会怎么样?

public class Bank {

    public static void main(String[] args) {
        Service s1 = new Service();
        Service s2 = new Service();
        Thread t1 = new Thread(s1, "t1");
        Thread t2 = new Thread(s2, "t2");
        t1.start();
        t2.start();
    }
}

class Service implements Runnable{
    private int total = 100;

    @Override
    public synchronized void run() {
        total += 50;
        System.out.println(Thread.currentThread().getName() + " --- total = " + total);
    }
}

输出

t1 --- total = 150
t2 --- total = 150

这里的逻辑相当于两个人分别在两台ATM机上存钱,而这两台机器里钞票的余额是互相独立、互不干扰的。因为s1和s2每个对象都有它自己的“total”变量,分别给了t1和t2去操作。所以在这种逻辑下也就不会产生线程安全问题,这两个变量并不是共享的。代码也证实了这一点,如果把此时run方法的synchronized关键字去掉,得到的是同样的结果。

这也就是对象锁的概念。对象锁作用在某个(非静态)方法上,这个方法为每个对象所独享。只有当不同的线程处理同一个对象时,相当于走了同一个方法处理同一个共享变量,才会产生线程安全问题。当一个线程处理这个对象或对象的方法时,其他线程必须等待,直到这个线程处理完毕,释放对象锁,其他线程才重新竞争并获得对象锁。

类锁

继续上面存钱的例子。每个ATM机内钞票的金额是每台机器独享的,但是整个银行拥有的资金总额是各个机器所共享的,在每台机器上存钱取钱后都会修改这同一个银行资金总额。

静态变量、静态方法不再属于某个具体的对象实例,而是属于某个类的。所以我们把total变量定义为static。

public class Bank {

    public static void main(String[] args) {
        Service s1 = new Service();
        Service s2 = new Service();
        Thread t1 = new Thread(s1, "t1");
        Thread t2 = new Thread(s2, "t2");
        t1.start();
        t2.start();
    }
}

class Service implements Runnable{
    private static int total = 100;

    @Override
    public synchronized void run() {
        total += 50;
        System.out.println(Thread.currentThread().getName() + " --- total = " + total);
    }
}

输出

t1 --- total = 150
t2 --- total = 200

两个线程去操作了这个类的不同对象实例,但static变量total属于Service这个类,所以两个线程操作了相同的共享变量。如果不加synchronized关键字,就有点类似本文第一段代码的情形,产生了线程安全问题。

这就是类锁的概念。静态方法、静态变量只会存在一份,给它加上锁,不同线程使用不同对象操作这个变量时,也就使用的相同的一把锁。

总结

对象锁即“一个对象一把锁”,当多个线程使用同一个对象时会有线程安全问题,多个对象之间的锁互不影响。

类锁即“多个对象一把锁”,当多个线程使用多个对象操作同一个静态共享变量时存在线程安全问题。

猜你喜欢

转载自blog.csdn.net/qq_25246305/article/details/82633694