【多线程】锁(三)

67 阅读4分钟

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

与前文衔接的地方

解释:

大家认真看上述代码即注释

我们可以发现写法一 实质上就是把A的实例对象aa或者aa对象的成员或者A类的成员作为监听器 把aa对象的tickets或者A类的tickets作为票池

写法二的实质是把A类的成员作为监听器 A类的tickets作为票池

实际中我们采用写法一

代码示例

class A extends Thread
{
	public static int tickets = 10000;
	static String str = new String("aa");
	public void run()
	{
		while(true)
		{
			synchronized(str)
			{
				if(tickets>0)
				{
					System.out.printf("%s线程正在卖出第%d张票\n",Thread.currentThread().getName(),tickets);
					tickets--;				
				}
				else
				{
					break;
				}
					
			}
		}	
	}	
}

public class TestTickets_3
{
	public static void main(String[] args)
	{
		A aa1=new A();
		A aa2=new A();
		aa1.start();
		aa2.start();
		/*这种写法类似于TestTickets_2.java里的写法二,必须创建一个公共对象,不推荐使用*/
	}	
}

解释:

本质和写法二一样,只是之前的两种写法是用Runnable接口实现 这个写法是直接继承Thread类

错误代码1

class A implements Runnable
{
	public int tickets = 10000;
	public A()
	{
		System.out.println(this);	
	}
	public synchronized void run()
	{
		while(true)
		{
			if(tickets>0)
			{
				System.out.printf("%s线程正在卖出第%d张票\n",Thread.currentThread().getName(),tickets);
				tickets--;				
			}
			else
			{
				break;
			}
					
		}	
	}	
}

public class TestTickets_4
{
	public static void main(String[] args)
	{
		A aa=new A();
		Thread t1=new Thread(aa);	
		Thread t2=new Thread(aa);
		t1.start();
		t2.start();
	}	
}

PS:

  • 错误写法 对于此程序来说synchronized加在这里会导致 只有一个线程卖票 另一个线程完全不能卖
  • 因为放在方法前是保证此方法完全运行 但此方法就是卖10000张票 会导致一个线程把这10000张票全部卖完
  • 放在代码块是为了 保证 每卖一张票就必须减一张票 这个才是本程序的真正目的

解释:

  • 这里的错误很明显 此时锁定的是整个方法 这样会导致 某一个线程先得到锁 就会直接把票卖完 另一个线程完全陷入阻塞

错误代码2

class A implements Runnable
{
	public int tickets = 10000;
	public A()
	{
		System.out.println(this);	
	}
	public  void run()
	{
		//String str=new String("aa");
		String str="aa";
		while(true)
		{
			synchronized(str)
                        
			{
				if(tickets>0)
				{
					System.out.printf("%s线程正在卖出第%d张票\n",Thread.currentThread().getName(),tickets);
					tickets--;				
				}
				else
				{
					break;
				}
			}	
		}	
	}	
}

public class TestTickets_5
{
	public static void main(String[] args)
	{
		A aa=new A();
		Thread t1=new Thread(aa);	
		Thread t2=new Thread(aa);
		t1.start();
		t2.start();
	}	
}

PS:

  • 错误写法 此时str是局部变量 不再是属性对象
  • 正确写法 因为字符串存放在统一的数据区
  • 锁定的不是同一个str对象

解释:

  • 这个错误也很容易理解 因为此时str变量不是对象的成员变量 只是一个局部变量而局部变量当前代码块运行完毕后就会释放 起不了监听器的作用,但是==String str=“aa”;则可以 原因是这种创建方法的"aa"是在堆中不会随代码块释放 只有当程序运行结束由gc清除,但是String str=new String(“aa”);==就是栈中创建了一个对象会随代码块的结束释放。

线程总结

  • 代码块锁是一个防止数据发生错误的一个重要手段;

  • 对象的统一性是非常重要的,这要想到对象的传入问题,要操作的对象只能new一次,其他的操作都是对这个传入的对象进行的,才能保证数据一致性,完整性和正确性。

  • 保证所有线程使用的都是同一把锁

  • 锁可以使用任意一个对象(同一个对象就行)

  • 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

  • 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。