1.什么是线程同步?
要弄清什么是线程同步必须先弄清楚以下概念?
并行、并发、中断?
并行:2个线程在同一时刻一起执行。要注意:计算机中不存在同时并行发生的事情,因为计算机的时间单位是毫秒,任何2件事情的发生时间只要精 确到毫秒便会看到先后顺序,这个世界上也不存在同时发生的事情,例如奥运会上,我们肉眼看到2个运动员似乎在同一时间通过终点,但是经过摄像机的放慢,将时间精确到毫秒,我们便会看到先后顺序。
并发:以2个人赛跑为例,A和B在跑道上一起赛跑,但是他们的起跑时间是有差异的(若以毫秒为单位)。我们称A和B在并发赛跑。
中断:暂停的意思,例如用暴风影音看视频,点击暂停,再点击播放,会从暂停出继续往下播放,而不是从头播放。
多线程是如何执行的,有哪几种执行方式?
CPU真正在执行多线程时,是一会儿并发执行,一会儿单独执行。
①并发执行:程序中的2个线程的执行是一定有先后顺序的。不论是谁先执行,谁在谁前面;只要2个都在执行线程体run方法,我们便称之为2个线程在并发执行,2线程的先后时间差,我们称之为并发时间差。如下图,3个红条表示线程,后面的紫色背景表示线程体。s
②单个执行:即在某段时间内,只有一个线程在执行,其他线程都中断了,或者是早已执行完毕,在等待CPU分配时间重新执行。
注意:我们有时需要人为的控制线程的执行方式,如:A线程先执行一次,然后B线程再执行一次,然后A线程再执行一次,即单次交替式执行。这需要借助开关变量、门锁和notify(),wait()方法实现。我把这命名为线程的第三种执行方式。
什么是同步?请看如下实例。
例子一:假设在古代清朝,一个黄梅戏班要在北京唱戏,戏楼里共有200个座位,于是戏班依据座位印刷了200张标有序列号的入场券,从1-200。北京很大,为了方便人们买票,以及加快售票速度将票卖完,戏班分成3组人员共同售票(3个线程),各组人员都带上200张门票分别在海淀区,朝阳区,宣武区,售票。越靠前的座位,票价越高。此时各组进入start启动状态,等待观众前来购票,当海淀区有人来购票时,此售票组开始执行售票(类似cpu执行此线程)。此观众买的是1号座位票。于是该组立马派2个人到朝阳区和宣武区通知他们1号票卖了,从2号票开始卖,但是那时通信不发达,送个信息必须要人骑马送信,当他们到达的时候,已经过去了半个时辰,而朝阳区和宣武区的售票组,没有及时收到消息,也都将1号票售出了。这便出现了同一个座位买了3张票的重复处理情况。出现这种状况的原因是送信人的送信速度太慢导致的。
时间到了现代90年代。此时中国最先进的通信方式是电话,刘德华要在北京开演唱会,同样卖200张座票。售票方式和上面一样,分成3组。分布在海淀区(A),朝阳区(B),宣武区(C)。当海淀区售出1号票时,立马打电话通知另外2个售票组,1号票已售。这比用人力通信要及时的多。但是这样依然不能完全阻止重复售票的情况发生。假设3个售票组的买票人员都很多,排了很长的队。此时3个小组都有人要购买2号票,最先交钱的是A组,于是A组立马打电话通知B组和C组,但事实是A组刚售出2号票,正在拨号准备通知时,B组和C组也把2号票卖了。出现这种状况的原因依然是信息传达的不够及时。
与此同时,美国人也发现了这个问题,而此时计算机、程序、网络已经在美国迅速发展,很多事情开始用程序处理,唯独这种售票的现实问题,难以解决,于是,计算机语言设计者加入了线程技术,这给程序员带来了极大的便利,程序员用线程来模拟多个售票点,写出了联网电子售票系统,当某个线程要售票时,立马用Synchronized()锁住,其他线程发现有Synchronized(),也就不会执行,而是等待A号线程的Synchronized语句块执行完了,另外的线程再执行。
计算机在执行多个线程时,有时是在并发执行多个线程,有时是单独一个线程在执行,有时一个线程没有执行完所有代码,便中断了,其他线程又来执行。而我们要控制的便是在处理共享数据时,防止并发执行导致的数据未及时更新,以及阻止线程中断(暂停)。
例如下面的程序,
class Cus implements Runnable
{
int i=0;
public void run()
{
while(true)
{
String st = Thread.currentThread().getName()+--- ;
System.out.println(st+(i++)) ;
}
}
}
class Demo
{
public static void main(String[] args)
{
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
本来是想打出有序不重复的数字。结果由于2个线程的并发执行,并且并发的时间差太小,可能会同时打印出数字0。例如t1线程比t2线程早执行1毫秒,2个线程都开始执行while(true)
语句,而t1执行到h字母时,t2紧随其后执行到w字母,这样,t1线程总比t2线程早执行一个字符,当执行到打印语句时,t1先输出0,t2后输出0;但是如果2个线程总是这样一起执行的话,打印结果是
Thread-0---0
Thread-1---0
Thread-0---1
Thread-1---1
Thread-0---2
Thread-1---2
Thread-0---3
Thread-1---3
Thread-0---4
Thread-1---4
Thread-0---5
Thread-1---5
但是计算机的CPU很奇怪,它并不是按这种平静的方式执行,2个线程执行完第一遍while循环后,CPU可能只让t2单独执行会,然后再让t1单独执行会,然后再让2个线程一起执行。所以我们看到的结果一般是
Thread-0---0
Thread-1---0
Thread-0---1
Thread-0---2
Thread-0---3
Thread-0---4
Thread-1---5
Thread-1---6
Thread-0---6 2个线程又一起执行了,只不过t1比t0早执行几个字母,这又会导致数据重复
综上所述:引起2线程操作共享数据发生不同步的原因有2条:
①并发执行的时间差太短,导致线程一还未来得及重新赋值,线程二就超前执行了。
②线程在执行共享数据的代码时,由于CPU分配的时间不够用,发生中断。
解决方法:
使用synchronized(Object ob)反锁住操作共享数据的代码,整个线程体就这一把锁,当多线程并发执行时,肯定只有一个是较早执行的,那么它就会先获得这把锁,当执行完锁住的语句,其他线程才会再执行被锁的语句块。保证了数据有充足的时间及时更新。另外这也防止由于中断导致数据错误,即使该线程在synchronized语句块中发生了中断,其他线程也无法来执行Synchronized语句块,直到CPU重新分配时间让该线程从中断处继续运行完毕,后续的线程才会执行Synchronized语句块。要注意:千万不要动不动就给整个run()方法加上个Synchronized(Object)锁,这样的话,会导致线程无法并发执行,只能一个个的单独执行,这回大大较低程序的执行速度。
2。我对synchronized(Object o){}的翻译?
很多翻译仅把它翻译为锁,我认为很不具体,你说多个线程要抢锁,谁神经病啊,抢锁干什么。
我认为应该这么解释,我们可以把synchronized比作是一扇门而{ }作用域括起的便是一个房间,
o是门的锁,并且是一把反锁,当有线程优先进入这扇门时,它为了防止其他线程来打扰自己,会立马将门反锁。并且,所有配了名字是o的锁的门,都会被反锁。这样其他线程便会被挡在门外。当将代码执行完毕时,才会从 } 走出,大门的锁也会自动解锁。
2.死锁的解释?死锁的解决方法?
我的解释:死锁的条件:首先一定是有2把不同的锁,4扇门,另外发生了锁的嵌套,而内层锁和外层锁是不同的。如下图所示:t1进入了灰色的大门,于是立即把门反锁,同时将另一个方法的大门也锁住了,t1还未来得及进入锁是o2的蓝色大门,碰巧的是t2已经进入了o2大门,并将门反锁。2个蓝色大门都被锁住,结果t1和t2都无法往下执行了,因为2个线程被前面的大门挡住了,可以说是卡在里面了,双方都想往下执行,但是被门挡住了,并且2个线程会永远的被卡住。双发都在等待对方释放锁,打开大门。
3.如何实现线程的第三种执行方式,即单次交替式执行?
我们有时需要人为的控制线程的执行方式,如:A线程先执行一次,然后B线程再执行一次,然后A线程再执行一次,即单次交替式执行。这需要借助开关变量、门锁和,wait(),notify()方法实现。
(1)首先wait()和notify()方法只能由synchronized中的锁来调用,任何对象调用都是无效的,因而2个方法只能写在synchronized的语句块中。这是2者的共同点。
(2)wait()和notify()的作用是什么,它们到底执行了什么?
wait():停止执行当前的t1线程,并释放当前的锁,或者说把synchronized大门后的锁解除。剥夺该线程反锁的资格。这样t2线程才有机会执行,否则程序有可能卡住。
notify():通知另外一个持相同锁的t2线程,不用无限期的等下去,您已经获得了反锁的资格,可以准备抢锁了,要注意,并不是说notify()一执行,刚才被等待的线程就会立即执行。必须等t2线程执行完notify()后面的语句,直到走出synchronized(){}语句块。释放了锁,t1才可以去抢锁,但同时,t2也依然可以抢锁,所以此时要通过一个开关变量和wait()要求t2释放抢到的锁。以保证轮到t1执行。这是我在notify()语句后加上sleep()语句测试的。
(3) 为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。