第五阶段(二) 线程安全 线程池

线程安全问题

首先考虑一个场景,当两个人同一时间去同一账户取出账户等额的金钱,如果不去考虑资源共享时候的冲突情况,极易可能出现在两个人取钱之后,出现负的等额的情况,如果这种事情发生在当前的诸多场景中很容易出现,软件将完全丧失可用性。

1、场景引入

//Account实体类,并且提供了取钱方法
public class Account {

    private Integer money;

    public Integer getMoney() {
        return money;
    }

    public void setMoney(Integer money) {
        this.money = money;
    }

    public Account(Integer money) {
        this.money = money;
    }

    public void getMoneyFromAccount(Integer money) {
        if (money <= this.money) {
            this.money -= money;
            System.out.println(Thread.currentThread().getName() + "已从账户中取出:" + money + "当前账户剩余:" + this.money);
        } else {
            System.out.println("当前账户里面没有那么多钱");
        }
    }
}
//取钱线程
public class AccountThread extends Thread {

    private Integer money;
    private Account account;


    public AccountThread(String name, int money, Account account) {
        //直接继承的无参构造是默认对名称进行修改的
        super(name);
        this.money = money;
        this.account = account;
    }

    @Override
    public void run() {
        account.getMoneyFromAccount(money);
    }

}

注:super直接继承的是setName()相当于给当前线程设置对应的名称

  public static void main(String[] args) {
        //初始账户一万元
        Account account = new Account(10000);
        //相当于两个人从同一个账户中去进行取钱
        Thread ming = new AccountThread("ming", 10000, account);
        Thread sang = new AccountThread("sang", 10000, account);
        ming.start();
        sang.start();
    }

这个场景中明和桑共同操作account这个账户,在线程中操作取钱方法,对账户进行操作,在这个场景中资源就是Account,抢夺的就是Account账户中的资源

2、那么如何保障线程安全呢?

毫无疑问就是线程同步,在一个线程对该资源进行操作的时候,其余线程首先去知晓这个状态,且不可对该线程进行可操作

同步的三种方式

(1)同步代码块-synchronized对同步该资源的代码块进行上锁

public  void  getMoneyFromAccount(Integer money) {
        
//        lock.lock();
        try {
            synchronized (this) {
                if (money > this.money) {
                    System.out.println("当前账户里面没有那么多钱");
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "已从账户中取出:" + money + "当前账户剩余:" + (this.money -= money));
            }
        }finally {
//            lock.unlock();
        }
    }

在操作该对象的money时,在这块进行加锁

(2)在操作该资源的方法上进行加锁

public synchronized void  getMoneyFromAccount(Integer money) {
//        public  void  getMoneyFromAccount(Integer money) {

//        lock.lock();
        try {
//            synchronized (this) {
                if (money > this.money) {
                    System.out.println("当前账户里面没有那么多钱");
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "已从账户中取出:" + money + "当前账户剩余:" + (this.money -= money));
//            }
        }finally {
//            lock.unlock();
        }
    }

与方法一相比,方法二是在整个执行方法上进行加锁,但锁越精细越好,越能最大的发挥CPU的性能,因此推荐方法一

另外,锁的对象只要保证唯一性即可,但在动态方法中,锁的对象常常是this,即当前对象,在静态方法中,锁的对象常常是当前的字节类

(3)第三种方式为显式锁,通过lock创建对应的锁对象,通过锁对象去进行加锁解锁

  Lock lock = new ReentrantLock();

 public  void  getMoneyFromAccount(Integer money) {

        lock.lock();
        try {
//            synchronized (this) {
                if (money > this.money) {
                    System.out.println("当前账户里面没有那么多钱");
                    return;
                }
                System.out.println(Thread.currentThread().getName() + "已从账户中取出:" + money + "当前账户剩余:" + (this.money -= money));
//            }
        }finally {
            lock.unlock();
        }
    }

 显示锁与隐式锁有什么区别和优劣差异,后期再去看吧,算了,就现在吧,他娘的

显示锁又称乐观锁,当中间发生资源抢占或者其它,直接进行回退,有种原子操作的感觉

隐式锁又称悲观锁,假设其它线程会使用该资源,因此是独占操作

显示锁是jdk1.5版本开始出现,隐式锁是JVM本身维护且较耗资源,随着版本迭代,sync的使用在逐渐优化且是官方推荐~

线程池

使用容器化技术,对线程的创建进行约束,不可能随意的去进行线程的创建,相当于去使用的一种容器化的技术,实现了线程反复利用,省去了频繁创建和销毁线程对象的操作——这就又印出了一个问题,线程池种的核心线程不会销毁且一直存在

1、简述一下,为什么要使用线程池?

(1)降低资源的消耗

(2)提高相应速度

(3)提高线程的可管理性

线程的核心思想即是:线程一次创建,反复利用执行各种任务

2、创建线程池的方式

ExecutorService executorService = Executors.newFixedThreadPool(2);

        executorService.submit(new MyRun());
        executorService.submit(new MyRun());
        executorService.submit(new MyRun());
        executorService.submit(new MyRun());
        //停止线程池 — 在所有线程全部运行完之后
        executorService.shutdown();
        //立即对线程池进行关闭,不去关注线程中是否还存在线程,支取进行资源关闭
        executorService.shutdownNow();

注意shutdown和shutdownNow这两种使用方式

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 2L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());

目前创建的方式大多使用其子方法,对内部参数进行填充

注:线程池是有自己的运行机制的,这套机制很有意思,后期再补充

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值