JAVA实例锁和类锁

Java中的实例锁(Synchronized)和类锁(Static Synchronized)

锁的作用

​ 对于多线程同时访问共享变量时,就会产生线程安全问题。锁的作用就是对共享的变量进行加锁,当有线程在访问变量时,其他线程必须要等锁释放才可以访问,解决线程安全问题。

线程安全问题

​ 直接上代码,下面是模拟10个线程对同时卖票的场景。

public class ThreadSecurtiy implements Runnable{
    int ticket = 100;
    @Override
    public void run() {
        sellTicket();//设置线程任务是调用addSalary接口
    }

    public void sellTicket(){
        while (ticket > 0){
            try {
                Thread.sleep(100);//睡眠是为了增加线程问题出现的概率,方便演示
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(ticket > 0) ticket--;//当票数大于0的时候,就可以卖票
            System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
        }
    }

    public static void main(String[] args) {
        ThreadSecurtiy ts = new ThreadSecurtiy();//创建一个ts对象
            for(int i = 0;i < 10;i++){//通过ts创建了10个线程,这10个线程对同一个对象ts的salary进行访问
                new Thread(ts).start();
            }
    }
}

输出结果是(当然结果太多了。后面就不贴了。)

Thread-5正在卖96张票
Thread-0正在卖95张票
Thread-3正在卖96张票
Thread-1正在卖96张票
Thread-2正在卖96张票
Thread-9正在卖94张票
Thread-8正在卖93张票
Thread-7正在卖92张票
Thread-4正在卖90张票
Thread-6正在卖91张票
Thread-5正在卖87张票

从结果中可以看出,如果十个线程不加以控制,会出现各种问题,比如96号票卖了多次,100号票根本就没有卖。

实例锁

实例锁很简单,就是在刚才的addSalary前加上sychroinzed关键字,但是这个实例锁,只对一个实例起作用,比如上面我们只创建了一个实例ts。

其他地方都一样哦,只贴sellTicket()方法了。

public synchronized void sellTicket(){
    while (ticket > 0){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(ticket > 0) ticket--;//当票数大于0的时候,就可以卖票
        System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
    }
}

结果是

Thread-0正在卖99张票
Thread-0正在卖98张票
Thread-0正在卖97张票
Thread-0正在卖96张票
Thread-0正在卖95张票
Thread-0正在卖94张票
Thread-0正在卖93张票
Thread-0正在卖92张票
Thread-0正在卖91张票

结果分析:发现只有thread0在卖票,这是因为线程0占有了锁不会释放,一只到run方法结束才释放,所以线程安全问题是解决了。但是并行度也降低了。

那么如果加了实例锁,现在创建了两个对象呢?按照道理来说,肯定是还有线程安全问题的!代码

public class ThreadSecurtiy implements Runnable{
    int ticket = 100;
    @Override
    public void run() {
        sellTicket();//设置线程任务是调用addSalary接口
    }

    public synchronized void sellTicket(){
        while (ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(ticket > 0) ticket--;//当票数大于0的时候,就可以卖票
            System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
        }
    }

    public static void main(String[] args) {
        ThreadSecurtiy ts = new ThreadSecurtiy();//创建一个ts对象
        ThreadSecurtiy ts1 = new ThreadSecurtiy();//创建另一个对象
        new Thread(ts).start();
        new Thread(ts1).start();
    }
}

注意这里主函数中创建了两个对象,运行的结果是:

Thread-1正在卖99张票
Thread-0正在卖99张票
Thread-0正在卖98张票
Thread-1正在卖98张票
Thread-1正在卖97张票
Thread-0正在卖97张票
Thread-1正在卖96张票

结果分析:两个对象在堆内存中创建了不同的实例,也就是两份salary所以肯定会出现如上的情况。这也是为什么实例锁只能锁一个对象的原因!因为两个对象,锁也是两个,堆内存是线程共享的!

类锁

看这个名字就知道,它的锁的范围是针对类的,就是这个类的所有对象,都能保证线程同步。类锁就是在sellTicket方法前用static synchroinzed关键字修饰。直接上代码

public class ThreadSecurtiy implements Runnable{
    static int ticket = 100;
    @Override
    public void run() {
        sellTicket();//设置线程任务是调用addSalary接口
    }

    public static synchronized void sellTicket(){
        while (ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(ticket > 0) ticket--;//当票数大于0的时候,就可以卖票
            System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
        }
    }

    public static void main(String[] args) {
        ThreadSecurtiy ts = new ThreadSecurtiy();//创建一个ts对象
        ThreadSecurtiy ts1 = new ThreadSecurtiy();//创建另一个对象
        new Thread(ts).start();
        new Thread(ts1).start();
    }
}

结果是:

Thread-0正在卖99张票
Thread-0正在卖98张票
Thread-0正在卖97张票
Thread-0正在卖96张票
Thread-0正在卖95张票
Thread-0正在卖94张票
Thread-0正在卖93张票
Thread-0正在卖92张票

结果分析:加上类锁之后,即使创建了两个线程对象,仍然可以保证线程的同步,说明类锁起了作用。原因是什么呢?以下为我个人理解哈:类锁加上static关键字,会导致这个锁和类最先加载,并且都存放在方法区,如果不加static,锁会和对象绑定,创建在堆区。所以不管创建多少个对象,它们对应的锁都是一个!

不知道对不对的理解。。。

这就是用静态和不用静态的区别,也不知道对不对。

其他解决线程安全问题的方法

同步代码块

还是上面的代码,在会被共享的变量周围加上同步代码块:

public class ThreadSecurtiy implements Runnable{
    int ticket = 100;
    @Override
    public void run() {
        sellTicket();//设置线程任务是调用addSalary接口
    }

    public void sellTicket(){
        while (ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this){
                if(ticket > 0) ticket--;//当票数大于0的时候,就可以卖票
                System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
            }
        }
    }

    public static void main(String[] args) {
        ThreadSecurtiy ts = new ThreadSecurtiy();//创建一个ts对象
        new Thread(ts).start();
        new Thread(ts).start();
        new Thread(ts).start();
    }
}

部分结果:

Thread-0正在卖99张票
Thread-1正在卖98张票
Thread-2正在卖97张票
Thread-1正在卖96张票
Thread-0正在卖95张票
Thread-2正在卖94张票
Thread-0正在卖93张票
Thread-1正在卖92张票
Thread-2正在卖91张票

结果分析:可以看出,没有线程安全问题,因为在会对共享的变量的周围加上了同步代码块,同步代码块的参数就是一个对象,随便什么对象都行,所以我写了this。原理就是,线程修改ticket的时候,会把这个锁给拿走,其他线程访问的时候没有这个锁,就只能阻塞,当线程0用完了之后会归还锁,线程1拿到锁,对ticket操作。

ReentrantLock锁

它实现了lock接口,常用的方法是里面的.lock()和unlock(),也很好理解,和同步代码块差不多!注意使用的时候要导包!

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadSecurtiy implements Runnable{
    int ticket = 100;
    @Override
    public void run() {
        sellTicket();//设置线程任务是调用addSalary接口
    }

    public void sellTicket(){
        Lock lk = new ReentrantLock();
        while (ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lk.lock();
            if(ticket > 0) ticket--;//当票数大于0的时候,就可以卖票
            System.out.println(Thread.currentThread().getName() + "正在卖" + ticket + "张票");
            lk.unlock();
        }
    }

    public static void main(String[] args) {
        ThreadSecurtiy ts = new ThreadSecurtiy();//创建一个ts对象
        new Thread(ts).start();
        new Thread(ts).start();
        new Thread(ts).start();
    }
}

运行结果:

Thread-0正在卖99张票
Thread-1正在卖98张票
Thread-2正在卖97张票
Thread-0正在卖96张票
Thread-2正在卖95张票
Thread-1正在卖94张票
Thread-0正在卖93张票
Thread-1正在卖92张票

结果分析:由于使用了ReentrantLock锁,所以共享变量ticket不会出现线程安全问题。至于这个锁的原理,我还没学到。学了再补充吧!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值