2021-08-16

java学习

线程安全问题

多个线程能修改同一个共享数据,就会发生线程安全问题

就有我们模拟多个用户同时从银行账户里面取钱,如果用户取钱数小于等于当前账户余额,则提示取款成功,并将余额减去取款钱数,如果余额不足,则提示余额不足,取款失败。

Account 类:银行账户类,里面有一些账户的基本信息,以及操作账户信息的方法

DrawThread类:继承了Thread,是一个多线程类,用于模拟多个用户操作同一个账户的信息

DrawTest:测试类

这时我们运行程序可能会看到如下运行结果:

甲取钱成功 800.0
乙取钱成功 800.0
    余额为 200.0
    余额为 -600.0 

余额竟然为-600,余额不足也能取出钱来,这就是线程安全问题。因为线程调度的不确定性,出现了偶然的错误。

解决线程安全问题

1.同步代码块

解决线程问题,可以引入同步监视器解决。

synchronized(obj){

  //此处的代码就是同步代码块

}
 @Override
    public void run() {
        
        synchronized(account) {
            if(account.getBalance()>drawAmount) {
                System.out.println(getName()+"取钱成功"+" "+drawAmount);
                try {
                    Thread.sleep(1);
                }catch(Exception e) {
                    e.printStackTrace();
                }
                account.setBalance(account.getBalance()-drawAmount);
                System.out.println("\t余额为"+" "+account.getBalance());
            }else {
                System.out.println("余额不足,取钱失败");
            }
        }
  • 使用account作为同步监视器,任何线程在进入下面同步代码块之前,必须先获得account账户的锁定,其他线程无法获得锁,也就无法修改它
  • 这种做法符合:"加锁-修改-释放锁"的逻辑

2.同步方法

同步方法是使用synchronized关键字来修饰某个方法。对于 synchronized修饰的实例方法**(非 static方法)**,不需要显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。

public synchronized void 方法名(){

  //具体代码

}

3.同步锁

提出了功能强大的线程同步机制——通过显示定义同步锁对对象实现同步,由LOCK对象充当

常用锁:ReentrantLock(可重入锁),可以对对象进行显示加锁,释放锁。。

class X{
    //定义锁对象
    private final ReentrantLock lock=new ReentrantLock();
    //...
    //定义需要保护线程安全的方法
    public void m() {
        //加锁
        lock.lock();
        try {
            //需要保证线程安全的代码
            //...method body
        }finally {
            //释放锁
            lock.unlock();
        }
    }
     
}

死锁

当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁岀现。一旦岀现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

死锁是很容易发生的,尤其在系统中出现多个同步监视器的情况下

线程池

使用线程池可以提高性能。在需要创建大量生存期很短暂的线程的程序中,可以考虑使用线程池。

线程池在系统启动时即创建大量空闲的线程,程序将一个 Runnable对象或 Callable对象传给线程池,线程池就会启动一个空闲的线程来执行它们的run()或call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法。

创建线程池的常用方法

  • newSingleThreadExecutor

    单线程的线程池。这个线程池中只有一个线程在工作,就如单线程执行所有任务。如果因为异常结束的话,就会有新线程来替代他。同时所有任务的执行顺序都是按照任务的提交顺序来执行。

  • newFixedThreadPool

    固定大小的线程池。每提交一个任务创建一个线程,直到线程达到线程池的最大值,并且一旦达到最大值就会保持不变,如果因为异常结束的话,线程池会补充新线程。

  • newCachedThreadPool

    可以缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

  • newScheduledThreadPool

    建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

参考:https://www.cnblogs.com/wugongzi/p/11491965.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值