【多线程】锁(二)

23 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第23天,点击查看活动详情

与前文衔接的地方

可以看到线程1已经卖到了第82张票但此时线程0又卖了一次之前的第100张票,它的逻辑是这样的:最开始线程1运行了System.out.printf(“%s线程正在卖出第%d张票\n”,Thread.currentThread().getName(),tickets);语句但是并没有减1,然后切换到了线程0,然后线程0已经加载了System.out.printf(“%s线程正在卖出第%d张票\n”,Thread.currentThread().getName(),tickets);语句但此时还没有来的及输出(注意:因为输出语句已经加载好 所以此时要输出的应该是Thread-0线程正在卖出第100张票 只是没有来得及输出),之后又切换到了线程1继续从上次线程1运行的地方运行 此时运行tickets–;,然后一直到Thread-0线程正在卖出第100张票之前都是线程1在正常运行 运行到Thread-1线程正在卖出第82张票,此时切换到线程0 因为之前线程0已经加载了==System.out.printf(“%s线程正在卖出第%d张票\n”,Thread.currentThread().getName(),tickets);所以继续从这里运行 所以接下来运行System.out.printf(“%s线程正在卖出第%d张票\n”,Thread.currentThread().getName(),tickets);语句输出Thread-0线程正在卖出第100张票 但是此时的票数实际上是81 然后线程0运行tickets–==后 此时票数是80,所以之后线程0才会输入Thread-0线程正在卖出第80张票,那么第81张呢?其实第81张也是和第100张的情况一样,第81张被线程1的输出语句加载但还没有输出 但是之后 再次切换到线程1时会再次输出81。

线程同步

知识了解

线程同步实际上是通过synchronized关键字实现的,它的作用是规定一个锁 这个锁可以锁定一块代码段也可以锁定一个方法, 这个锁 锁住一个公共对象 只有拿到这个锁的线程才可以运行锁内部的代码且不可被打断 直到这块代码运行完毕 释放锁 之后所有线程再来争抢锁。这样做的目的是为了 公共资源部分的代码在同一时刻只能由一个线程运行,避免线程不安全。对于卖票程序来说 它们的公共资源的就是 票池 锁住的部分就是卖票和票数减一。实质上 锁 锁住的对象叫做监听器 锁住对象的动作叫做锁定。

代码示例

class A implements Runnable
{
	public static int tickets = 10000;//写法一的话 static可以不用加 但写法二必须加
	String str = new String("aa");//a  这样写 只有写法一正确
	//static String str = new String("aa"); //b  这样写 写法二写法一都正确
	public A()
	{
		System.out.println(this);	
	}
	public void run()
	{
		while(true)
		{
			//synchronized(this)//这样写 只有写法一正确
			synchronized(str)//这样写写法一一定正确,但写法二 只有在b的情况下才能正确
			{
				if(tickets>0)
				{
					System.out.printf("%s线程正在卖出第%d张票\n",Thread.currentThread().getName(),tickets);
					tickets--;				
				}
				else
				{
					break;
				}
			}
		}	
	}	
}

public class TestTickets_2
{
	public static void main(String[] args)
	{
		A aa=new A();
		Thread t1=new Thread(aa);	
		Thread t2=new Thread(aa);
		t1.start();
		t2.start();//写法一
		
		/*A aa1=new A();
		Thread t1=new Thread(aa1);
		t1.start();	
		A aa2=new A();
		Thread t2=new Thread(aa2);
		t2.start();//写法二*/
	}	
}

PS:synchronized总结: 格式: synchronized(类对象名aa) { 同步代码块 } synchronized(类对象名 aa)的含义是:判断aa是否已经被其他线程霸占,如果发现已经被其他线程霸占,则当前线程陷入等待,如果发现aa 没有被其他线程霸占,则当前线程霸占注aa对象,并执行3行的代码同步块,在当前线程执行3行代码时,其他线程将无法再执行3行的代码 (因为当前线程以及霸占了aa对象),当前线程执行完3行的代码后,会自动释放对aa对象的霸占,此时其他线程会相互竞争对aa的霸占,最终 cpu会选择其中的某一个线程执行

补充: 霸占的专业术语叫做锁定,霸占住的那个对象专业术语叫做监听器,监听器可以是this指向的那个对象也可以是类里的其他对象。

锁定类似于 有n个人上厕所 有一个厕所牌 只有当某人拿到厕所牌才可以上厕所,关键点在于只有一个厕所牌 即厕所牌是公共的,区别在于 是哪个人拿了厕所牌 则锁定那个人的厕所牌 如果上完厕所则 其他人再争抢厕所牌。

当修饰代码块时需要(类对象名 aa),当修饰方法时 则不需要只需要在方法前加上 synchronized 不需要括号 默认监听器是this。

当在运行同步代码块时,线程是可以随机改变的 只不过因为线程的监听器被锁定 导致其他的线程的监听器无法被锁定 导致只有监听器被锁定 的那个线程运行完同步代码块后 把监听器释放 才能让其他线程的监听器取竞争。

当在创建线程时,只有写法一是对的,写法二虽然也创建了两个线程 但这两个线程的synchronized没有锁定的关系,依旧会出现代码不同步 的问题。

如果保证写法二也是正确的 则必须保证 有唯一的厕所牌 即 公共对象,这个公共对象不能是this 因为this会指向不同的对象,所以这种 情况下 必须用一个公共的类对象 才能保证写法二正确,但写法一则不用考虑 因为这个的公共对象就是aa

推荐使用写法一

结果:

测试了一万张票,没有出现错误。