一.
至于同步函数用的是哪个锁,我们可以验证一下,借助原先卖票的例子
对于程序中的num,从100改为400,DOS的结果显示的始终都是0线程,票号最小都是1。
票号是没有问题的,因为同步了。
有人针对只出现0线程,说是票数太少,0线程都给操作完了。即使改成四万张票,也是0线程操作。
正常来说,四个线程0~3,谁抢到谁就运行。问题出现在哪儿?
程序中run函数是public synchronized void run(),没有搞清楚什么时候用同步,什么时候不用同步。
我们知道该同步的是if整个语句,
现在我们是从run方法就开始了同步,也就是说在run语句的上面,同时来了四个线程,一个线程在run方法里没执行完所有的语句,其他线程就无法进来。而run里面就包含着循环售票,也就是说一个线程进到run方法里,售完了票才出去。
看下图,在if中0线程处于sleep状态时,1~3号线程得到了执行权,但是进不来。
而且,0线程出不去了,因为while中的判断始终是正确的。(是不是说,即使循环执行完了,0线程还呆在里面,因为while始终是正确的?)
出现这样的原因就是不需要将run方法整体同步,只需要将if代码块同步,怎么解决呢?
单独地将if语句定义成函数,将该函数同步,接着调用即可。
DOS结果显示,四个线程都出来了。(这里show方法可以直接调用,不用创建对象啥的么?)
二. 验证同步函数的锁
基于上面的例子,我们来验证同步函数的锁是哪一个?
现在为了方便起见,将四个线程减少为两个线程。两个线程运行的动作是一样的,都在卖票。一个是在同步代码块里卖票,一个是在同步函数中卖票。(同步函数和同步代码块两者不是一样么?)
如果这两个线程用的是同一个锁的话,就不会出现安全隐患。0线程在同步函数里,1线程在同步代码块里,如果它俩用的是同一个锁,那说明0线程在运行同步函数的时候,1线程不能运行同步代码块的,(这是不是说明同步代码块和同步函数都是靠锁来操作的原理?)
想要在run方法里,既有同步代码块又能调用同步函数,这需要什么动作?这叫做线程的切换。为真的时候,运行同步代码块:为假的时候,运行同步函数,这需要一个boolean型变量。
(这里的while语句始终感觉没什么用,为什么要一直保留着?)
为真的时候,走同步代码块,为假的时候,走同步代码块。
两个线程都进到run方法里面去了,它们都有自己的run方法,判断的变量也是同一变量flag。
DOS结果线程,1线程和0线程都有,但是都在function里面执行。按理说,0线程为真应该在同步代码块中执行,怎么跑到同步函数中执行了?理由:主线程开启以后,创建对象,创建两个线程。开启线程1以后,它还持有cpu的执行权,所以瞬间,将t1.start(),t.flag=false,t2.start()这三句话全部都搞定了。一搞定后,这个flag就变为假了,主线程搞完假后,这两个线程在启动的时候都是flag=false,因此两个线程在执行的时候,执行的都是同步函数。(为什么主线程能执行这么多语句?怎么判别主线程和0,1线程是执行的哪些语句?)
有人说这里没切换啊。可以做切换。
主线程开启了0线程以后,把它置为假之前,可以让主线程停一下。也就是调用sleep方法,让主线程睡一下,这样0线程就掌握执行权了。
睡了10毫秒。
DOS结果显示如上。如果两个线程同步了,就不会出现负的情况,如果没同步就有可能出现安全问题。
怎么输出两个49?操作线程的代码有四句,obj两句,function两句。你判断完了,我也判断完了,你没输出,我也没输出。我49输出,我也49输出。
但是,我现在想说的是0号票,打印0号票肯定是不对的。加上同步的居然不安全。为什么?
首先这里面应该有多线程,同步代码块里面是一个线程,同步函数里用的又是另一个线程,它们用的 不是一个锁。如果用的是用一个锁,代表着同一个锁里有多个线程,意味着每次只能有一个线程进来。这里可以说明的一点的是,同步函数用的锁肯定不是obj,那用的是什么锁?同步函数仅仅是函数上带了同步性,同步本身不带锁。同步代码块后面是单独指定锁,synchronized是关键字,本身并不带锁。
应该是函数带的锁,函数有对象,函数被调用的时候,必须是对象来调用。函数是被哪个对象调用呢?
函数是被this调用,函数都有自己所属的this。函数被哪个对象调用?我哪儿知道,我肯定函数是被对象调用,凭什么去操作对应的数据啊?因为持有this。
这个show被谁调用?被run方法调用,至于run方法被谁调用,换句话说run方法所属于哪个对象。当然属于t。run方法不是封装线程任务么?不是把线程任务所属对象t创建出来了么,那么就是t在调用run方法对象。show怎么获取的t,当然this嘛。
一般方法调用一般方法,直接写个this,即this,show();
run方法也属于this,直接把this写入同步代码块中,哪个对象调用这个run方法,它就代表哪个对象。这个this所指的地址和下面的t地址是一致的
DOS结果显示,两者是同一个对象。
现在将添加的两个输出语句注释掉,
继续编译运行
0线程和1线程将票卖完了,也不存在0号票。由此可以验证同步函数使用的锁是this。
上面的程序可以不用写那么多,现在为什么写呢?是为了讲解同步函数使用的锁是this。
同步函数可以是同步代码块的简写,一简写就有前提,有弊端。如果同步代码块里的锁不是this,那就不能用同步函数了。
同步函数虽然简化,但是锁是唯一的。