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