Java-初步认识-第十三章-多线程(验证同步函数的锁)

一.

至于同步函数用的是哪个锁,我们可以验证一下,借助原先卖票的例子

对于程序中的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,那就不能用同步函数了。

同步函数虽然简化,但是锁是唯一的。

package test;
/**
 * @author 高小硕
 * 同步函数的使用的锁是this
 * 
 * 同步函数和同步代码块的区别:
 * 同步函数的锁是固定的this
 * 同步代码块的锁是任意的对象
 * 
 * 开发的时:
 * 建议使用同步代码块
 * 
 */
class Tickets implements Runnable {
	private static int num = 10;
	// Object obj = new Object();

	boolean flag = true;

	public void run() {
		System.out.println("this:"+this);
		if (flag)
			while (true) {
				synchronized (this) {
					if (num > 0) {
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							// TODO: handle exception
						}

						System.out.println(Thread.currentThread().getName() + "..sale..." + num--);
					}

				}

			}
		else
			while (true)
				this.show();
	}

	// 同步函数
	public synchronized void show() {
		if (num > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO: handle exception
			}
			System.out.println(Thread.currentThread().getName() + "..sale..." + num--);
		}
	}
}

class SynFunctionLockDemo {

	public static void main(String[] args) {
		Tickets t = new Tickets();// 创建一个线程任务对象
		System.out.println("t:"+t);
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		// Thread t3 = new Thread(t);
		// Thread t4 = new Thread(t);

		t1.start();
		

		t.flag = false;
		t2.start();
		// t3.start();
		// t4.start();
	}

}

转载自:http://www.cnblogs.com/wsw-bk/p/8033847.html

猜你喜欢

转载自blog.csdn.net/Fighting_Future/article/details/80338517