线程同步
如果涉及多个线程访问同一个数据的情况,就容易出现问题。比如一个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关键字和对象的锁标志配合完成数据的保护。
就像这个样子去保护:

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就可以这样写了:

线程通信
当然,在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()的例子:

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):安排指定的任务从指定的延迟后开始进行重复的固定延迟执行
很普通的例子:

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

被折叠的 条评论
为什么被折叠?



