Java入门:线程2

线程同步

如果涉及多个线程访问同一个数据的情况,就容易出现问题。比如一个int型数组int[] a={2,1,4,3},如果线程a对它升序操作,另一个线程b对它降序操作。两个线程同时运行,a线程刚刚把它的2和1排好,正好发生了时间片轮换,就是这么突然狗屎运,b线程得到CPU时间片,马上把4和3放到前面。这样a线程和b线程访问共享的数据a,就会出现结果不正确的情况,这很尴尬。

一个卖书的例子:
卖书

输出结果:

第10本卖出者: tom 
第10本卖出者:Third
第10本卖出者:线程1
第7本卖出者: tom 
第6本卖出者:Third
第5本卖出者:线程1
第5本卖出者: tom 
第4本卖出者:Third
第3本卖出者: tom 
第3本卖出者:线程1
第1本卖出者: tom 
第2本卖出者:Third

这个运行结果表明数据出现异常,很是尴尬。我们如何解决这个问题呢?
当然伟大的Java已经为我们考虑好了,Java语言设计了同步机制来保护数据。在任何一个Java对象上,都有一个锁标志。synchronized关键字和对象的锁标志配合完成数据的保护。
就像这个样子去保护:
synchronized

sysnchronized包围的代码叫关键代码,当线程运行到关键代码处,首先要到o对象上去取得o对象的锁标志,然后才能执行代码。但是锁标志只有一个,线程取得锁标志后,运行关键代码,只有从关键代码离开时,才会归还锁标志。如果线程没有运行完成关键代码,锁标志不会归还。其他线程再次运行到synchronized处,无法从o上取得锁标志,就无法执行关键代码,只能静静的等待锁标志的归还。
咱来改造一下上面的卖书的案例,看是不是如我们所愿解决好问题。
卖书的案例

输出如下:

第10本卖出者:线程1
第9本卖出者:线程1
第8本卖出者:线程1
第7本卖出者:线程1
第6本卖出者:线程1
第5本卖出者:线程1
第4本卖出者:线程1
第3本卖出者:线程1
第2本卖出者:线程1
第1本卖出者:线程1

上面只看到线程1是因为案例计算复杂度小,不信你可以把i修改成100试试,三个线程就都能看到辣。
上面的代码还可以再优化一下写法,因为:
public synchronized void sell(){}它就相当于如下代码:

public void sell()
{
    synchronized(this)
    {
    
    }
}

这样写代码可以更清晰,便于理解,上面的SellBook就可以这样写了:
SellBook案例升级

线程通信

当然,在synchronized关键代码中,也可以主动放弃对象的锁标志。就是通过wait()方法做到这一点。线程放弃锁标志后,线程进入了阻塞状态。
所有的Java对象都有一个wait池,每个池都可以容纳线程。wait()notify()方法都有是Object中的方法。当线程执行了wait()方法后,线程就释放对象的锁标志并进入该对象的wait池中等待。直到notify()通知后才能运行。

wait()方法

wait()方法关键点:

  • wait()方法是Object对象的方法,不是Thread类的方法
  • wait()方法只可能在synchronized块中被调用
  • wait()方法被调用时,原来的锁对象释放锁,线程进入block状态
  • wait()notify()唤醒的线程从wait()后面的代码开始继续执行

notidy()方法

notidy()用来唤醒正在等待的线程,使线程可以重新运行。被唤醒的线程从当时wait候的代码开始执行,但是因为其wait时已经释放了锁标志,所以必须重新获得锁标志。
notidy()方法关键点:

  • 只能在sysnchronized中被调用,即先获得对象锁标志。
  • notify()方法唤起锁标志对象的等待池中的一个线程。但是,如果有几个线程在等待列表中,它无法决定哪个线程被唤醒。调用notifyAll()方法可以让所有的等待线程被唤醒。

使用notify()唤醒wait()的例子:
notify()唤醒wait()

notidyAll()方法

notidy()方法注意事项:

  • 只能在synchronized中被调用,即先获得对象锁标志
  • notidy()方法唤起锁标志所属对象的等待池中的所有的等待线程

Timer和TimerTask

Timer是一种定时器工具。它可以用来启动TimerTask来执行任务一次或者反复多次。
TimerTask是一个抽象类,表示一个可以被Timer执行的定时器任务。它实际上是一种特殊的线程,是Timer来定时启动执行一次任务或者重复执行某个任务。
TimerTask类本身没有实现run()方法,其run()方法由子类实现。

Timer类常用方法如下:

  • void schedule(TimerTask task,Date time):安排在指定的时间执行执行的任务
  • void schedule(TimerTask task,Date firstTime,long period):安排指定的任务在指定的时间开始进行重复的执行
  • void schedule(TimerTask task,long delay):安排在指定延迟后执行指定的任务
  • void wchedule(TimerTask task,long delay,long priod):安排指定的任务从指定的延迟后开始进行重复的固定延迟执行

很普通的例子:
Timer案例

死锁

死锁就是所有的线程都无法运行,整个程序处于阻塞状态,并且不可以恢复到运行状态。一旦发生死锁,线程就没有运行的意义了。
两个线程互相拿到了对方需要的资源,此时两个线程都不会放弃自己已经拿到的资源,这时程序就无法继续运行下去,死锁就出现了。我们写程序要注意防止死锁情况的出现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值