java 线程 同步

本文深入浅出地介绍了线程同步的基本概念,包括并行、并发和中断的区别,并通过实例详细阐述了同步机制的重要性,以及如何利用synchronized关键字解决数据同步问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.什么是线程同步?

要弄清什么是线程同步必须先弄清楚以下概念?

并行、并发、中断?

并行:2个线程在同一时刻一起执行。要注意:计算机中不存在同时并行发生的事情,因为计算机的时间单位是毫秒,任何2件事情的发生时间只要精 确到毫秒便会看到先后顺序,这个世界上也不存在同时发生的事情,例如奥运会上,我们肉眼看到2个运动员似乎在同一时间通过终点,但是经过摄像机的放慢,将时间精确到毫秒,我们便会看到先后顺序。

并发:以2个人赛跑为例,AB在跑道上一起赛跑,但是他们的起跑时间是有差异的(若以毫秒为单位)。我们称AB在并发赛跑。

中断:暂停的意思,例如用暴风影音看视频,点击暂停,再点击播放,会从暂停出继续往下播放,而不是从头播放。

 

多线程是如何执行的,有哪几种执行方式?

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先输出0t2后输出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个线程又一起执行了,只不过t1t0早执行几个字母,这又会导致数据重复

 

综上所述:引起2线程操作共享数据发生不同步的原因有2条:

①并发执行的时间差太短,导致线程一还未来得及重新赋值,线程二就超前执行了。

②线程在执行共享数据的代码时,由于CPU分配的时间不够用,发生中断。

解决方法:

使用synchronizedObject ob)反锁住操作共享数据的代码,整个线程体就这一把锁,当多线程并发执行时,肯定只有一个是较早执行的,那么它就会先获得这把锁,当执行完锁住的语句,其他线程才会再执行被锁的语句块。保证了数据有充足的时间及时更新。另外这也防止由于中断导致数据错误,即使该线程在synchronized语句块中发生了中断,其他线程也无法来执行Synchronized语句块,直到CPU重新分配时间让该线程从中断处继续运行完毕,后续的线程才会执行Synchronized语句块。要注意:千万不要动不动就给整个run()方法加上个SynchronizedObject)锁,这样的话,会导致线程无法并发执行,只能一个个的单独执行,这回大大较低程序的执行速度。

 

2。我对synchronizedObject o{}翻译?

很多翻译仅把它翻译为锁,我认为很不具体,你说多个线程要抢锁,谁神经病啊,抢锁干什么。

我认为应该这么解释,我们可以把synchronized比作是一扇门而{  }作用域括起的便是一个房间,

o是门的锁,并且是一把反锁,当有线程优先进入这扇门时,它为了防止其他线程来打扰自己,会立马将门反锁。并且,所有配了名字是o的锁的门,都会被反锁。这样其他线程便会被挡在门外。当将代码执行完毕时,才会从 } 走出,大门的锁也会自动解锁。

2.死锁的解释?死锁的解决方法?

 

我的解释:死锁的条件:首先一定是有2把不同的锁,4扇门,另外发生了锁的嵌套,而内层锁和外层锁是不同的。如下图所示:t1进入了灰色的大门,于是立即把门反锁,同时将另一个方法的大门也锁住了,t1还未来得及进入锁是o2的蓝色大门,碰巧的是t2已经进入了o2大门,并将门反锁。2个蓝色大门都被锁住,结果t1t2都无法往下执行了,因为2个线程被前面的大门挡住了,可以说是卡在里面了,双方都想往下执行,但是被门挡住了,并且2个线程会永远的被卡住。双发都在等待对方释放锁,打开大门。

 

3.如何实现线程的第三种执行方式,即单次交替式执行?

我们有时需要人为的控制线程的执行方式,如:A线程先执行一次,然后B线程再执行一次,然后A线程再执行一次,即单次交替式执行。这需要借助开关变量、门锁和,wait(),notify()方法实现。

(1)首先wait()和notify()方法只能由synchronized中的锁来调用,任何对象调用都是无效的,因而2个方法只能写在synchronized的语句块中。这是2者的共同点。

2wait()notify()的作用是什么,它们到底执行了什么?

wait():停止执行当前的t1线程,并释放当前的锁,或者说把synchronized大门后的锁解除。剥夺该线程反锁的资格。这样t2线程才有机会执行,否则程序有可能卡住。

notify():通知另外一个持相同锁的t2线程,不用无限期的等下去,您已经获得了反锁的资格,可以准备抢锁了,要注意,并不是说notify()一执行,刚才被等待的线程就会立即执行。必须等t2线程执行完notify()后面的语句,直到走出synchronized(){}语句块。释放了锁,t1才可以去抢锁,但同时,t2也依然可以抢锁,所以此时要通过一个开关变量和wait()要求t2释放抢到的锁。以保证轮到t1执行。这是我在notify()语句后加上sleep()语句测试的。

(3) 为什么这些操作线程的方法要定义Object类中呢?

因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,

只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。

不可以对不同锁中的线程进行唤醒。

也就是说,等待和唤醒必须是同一个锁。

而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值