多线程问题多线程问题:为什么同步方法(锁对象为this)和静态同步方法(锁对象为this.class)的锁对象不同?

多线程问题:为什么同步方法(锁对象为this)和静态同步方法(锁对象为this.class)的锁对象不同?

首先要了解以下几点:

  • 1.为什么要加锁:解决多线程操作共享数据时的数据安全问题;
  • 2.锁的分类:类锁和对象锁
    • 对象锁:在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。
    • 类锁:在 Java 中,针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。
  • 锁的特性:
    • 互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。
    • 可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。

以卖票小程序做代码演示:

任务对象继承Thread类:
		多线程执行的任务对象不是同一个
任务对象继承Runnable接口:
		多线程执行的任务对象是同一个

如果多线程访问同一个对象的多个实例,但不操作共享数据,是不会有线程安全问题的;

如下代码,线程d1和d2访问的是Demo1任务对象不同实例,不会出现线程安全问题,但是没有意义;

//任务对象
class Demo1 extends Thread{
    //非共享数据
    private int tickets = 100;
    @Override
    public void run() {
        while (true) {
            try {
                sellTickets();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void sellTickets() throws InterruptedException {
        if (tickets == 0) {
            return;
        }
        //注意:如果不加延时,其他线程会抢不到CPU执行权,无法演示线程安全问题
        Thread.sleep(10);
        tickets--;
        System.out.println(Thread.currentThread().getName() + ":tickets的库存为:" + tickets);
    }
}
//测试类
public class Test1 {
    public static void main(String[] args) {
        Demo1 d1 = new Demo1();
        Demo1 d2 = new Demo1();
        d1.start();
        d2.start();
    }
}

将上面代码稍作修改,将tickets修改为静态成员变量,让多线程操作共享数据;

如果多个线程访问Demo1的不同实例,而且对Demo1中的静态变量进行操作,此时会出现线程安全的问题,运行之后出现重复票,即证明有线程安全问题;

//任务对象
class Demo1 extends Thread{
    //共享数据
    private static int tickets = 100;
    @Override
    public void run() {
        while (true) {
            try {
                sellTickets();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void sellTickets() throws InterruptedException {
        if (tickets == 0) {
            return;
        }
        //注意:如果不加延时,其他线程会抢不到CPU执行权,无法演示线程安全问题
        Thread.sleep(10);
        tickets--;
        System.out.println(Thread.currentThread().getName() + ":tickets的库存为:" + tickets);
    }
}
//测试类
public class Test1 {
    public static void main(String[] args) {
        Demo1 d1 = new Demo1();
        Demo1 d2 = new Demo1();
        d1.start();
        d2.start();
    }
}
//控制台结果,出现重复票和负票
Thread-0:tickets的库存为:98
Thread-1:tickets的库存为:98
Thread-0:tickets的库存为:97
Thread-1:tickets的库存为:97
Thread-1:tickets的库存为:95
Thread-0:tickets的库存为:95
......
Thread-1:tickets的库存为:0
Thread-0:tickets的库存为:-1

为了解决线程安全问题,所以就需要在Demo1中加锁同步,由于同一对象的多个实例的对象锁是不同的,所以加对象锁是锁不住共享数据的,代码演示如下

//继承了Thread,开启多个线程后,执行的并不是同一个任务
class Demo1 extends Thread {
    private static int tickets = 100;

    @Override
    public void run() {
        while (true) {
            try {
                sellTickets();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    /*
	加对象锁有两种方式:
		1.同代码块synchronized(this|Object) {}
		2.同步方法(默认的锁对象是this)
	*/
    public synchronized void sellTickets() throws InterruptedException {
        if (tickets == 0) {
            return;
        }
        //注意:如果不加延时,其他线程会抢不到CPU执行权,无法演示线程安全问题
        Thread.sleep(10);
        tickets--;
        System.out.println(Thread.currentThread().getName() + ":tickets的库存为:" + tickets);
    }
}
//测试类
public class Test1 {
    public static void main(String[] args) {
        //因为Demo1继承了Thread,此时开启多线程需要创建两个对象
        Demo1 d1 = new Demo1();
        Demo1 d2 = new Demo1();
        //因为Demo1中的票是静态成员变量,所以d1和d2操作的是共享数据
        //此处d1和d2使用的是对象锁,锁不同所以锁不住票
        d1.start();
        d2.start();
    }
}
//控制台结果,出现重复票和负票
Thread-1:tickets的库存为:98
Thread-0:tickets的库存为:98
Thread-1:tickets的库存为:97
Thread-0:tickets的库存为:96
......
Thread-0:tickets的库存为:-1
Thread-1:tickets的库存为:-1

修改上述代码,将对象锁换为类锁

class Demo1 extends Thread {
    private static int tickets = 100;

    @Override
    public void run() {
        while (true) {
            try {
                sellTickets();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /*
	加类锁有两种方式:
		1.同代码块synchronized(Demo1.class) {}
		2.静态同步方法(类锁:默认锁对象是this.class)
	*/
    public static synchronized void sellTickets() throws InterruptedException {
        if (tickets == 0) {
            return;
        }
        //注意:如果不加延时,其他线程会抢不到CPU执行权,无法演示线程安全问题
        Thread.sleep(10);
        tickets--;
        System.out.println(Thread.currentThread().getName() + ":tickets的库存为:" + tickets);
    }
}
//测试类
public class Test1 {
    public static void main(String[] args) {
        Demo1 d1 = new Demo1();
        Demo1 d2 = new Demo1();
        //此处虽然d1和d2是两个
        d1.start();
        d2.start();
    }
}
//控制台数据正常,没有数据安全问题
Thread-0:tickets的库存为:99
Thread-0:tickets的库存为:98
Thread-0:tickets的库存为:97
Thread-0:tickets的库存为:96
Thread-0:tickets的库存为:95
......
Thread-0:tickets的库存为:1
Thread-0:tickets的库存为:0

修改上述代码,使多线程访问同一个对象的同一实例,则可以用对象锁锁住共享数据

//将继承Therad改为实现Runable,可以保证多线程执行的是同一个任务对象
class Demo1 implements Runnable {
    private static int tickets = 100;

    @Override
    public void run() {
        while (true) {
            try {
                sellTickets();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    /*
	加对象锁有两种方式:
		1.同代码块synchronized(this|Object) {}
		2.同步方法
	*/
    public synchronized void sellTickets() throws InterruptedException {
        if (tickets == 0) {
            return;
        }
        //注意:如果不加延时,其他线程会抢不到CPU执行权,无法演示线程安全问题
        Thread.sleep(10);
        tickets--;
        System.out.println(Thread.currentThread().getName() + ":tickets的库存为:" + tickets);
    }
}
//测试类
public class Test1 {
    public static void main(String[] args) {
        Demo1 d1 = new Demo1();
        Thread t1 = new Thread(d1);
        Thread t2 = new Thread(d1);
        t1.start();
        t2.start();
    }
}

综上所述:

为了保证锁的唯一,在多线程操作同一任务对象实例的共享数据时可以用类锁和对象锁;

在多线程操作不同任务对象实例的共享数据时只能用类锁;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值