黑马程序员_多线程安全问题

------- android培训、java培训、期待与您交流! ----------

多线程安全问题

第一部分

1、多线程安全问题的描述

在卖票程序中多线程安全问题的描述:当4个线程在分别执行程序的时候,0线程进入程序进行判断tick>0;刚判断完条件,这时候0线程就卧倒下了,卧倒就是0线程具备执行的资格但是执行权被其他的线程给强走了,换句话说就是cpu切换到其他人那边去了;这是1线程就获得了执行权,1线程判断条件tick>0,是满足条件的,然后依次类推都是有可能出现这些情况,也就是说4个线程都已经卧倒了,但是这是cpu这是切换到了0线程身上了,这是0线程就直接向下执行了,这时0线程输出的票是1号票,但是这时1线程输出的是0号票,2线程输出的是-1号票,3线程输出的是-2号票,按照人们生活常理是没有0号票还负数的票的;-->这时程序就有可能会出现安全隐患;

2、用代码体现多线程安全问题

//实现Runnable
class Ticket implements Runnable
{
	private int tick =100;
//	Object obj = new Object();
	public void run()
	{
		while(true)
		{
//			synchronized(obj)
//			{
				if(tick>0)
				{
//					sleep方法抛出了异常
					try
					{
						Thread.sleep(10);
					}
					catch (Exception e)
					{
						System.out.println(e.toString());
					}
					System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
//				}
			}
		}
	}
}
class TicketDemo2
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
//		创建线程;需要传一个Ticket对象;要指定run方法所属对象;
//		Thread(Runnable target) 
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
//		分别要开启线程;
		t1.start();
		t2.start();
		t3.start();
		t4.start();

	}
}

3、代码运行的结果


4、问题的描述

多线程最恐慌的就是安全问题,通过分析,发现出现了0和-1号票,因此就是多线程的运行出现了安全问题,

5、问题的原因

|--当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。
|--如果一个线程进来把所有的语句都执行完,然后下一个线程在进来把程序执行完,那么就没有问题了。

6、解决办法

|--对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行;换句话说就是0线程在程序里面执行,1线程即使拿到了执行权也不让1线程执行;那么这样就靠谱了;
|--java对于多线程的安全问题提供了专业的解决方式;就是同步代码块;
|--如何判断哪些代码需要同步:就要看哪些代码在操作共享数据;-->tick就是共享数据;
//同步代码块
synchronized(对象)
{
	需要被同步的代码
}

7、代码体现

//实现Runnable
class Ticket implements Runnable
{
	private int tick =500;
//	需要放一个对象;
	Object obj = new Object();
	public void run()
	{
		while(true)
		{
//			同步代码块
			synchronized(obj)
			{
				if(tick>0)
				{
//					sleep方法抛出了异常	
					try
					{
						Thread.sleep(10);
					}
					catch (Exception e)
					{
						System.out.println(e.toString());
					}
					System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
				}
			}
		}
	}
}
class TicketDemo2
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
//		创建线程;需要传一个Ticket对象;要指定run方法所属对象;
//		Thread(Runnable target) 
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
//		分别要开启线程;
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		/*
		Ticket t1 = new Ticket();
		Ticket t2 = new Ticket();
		Ticket t3 = new Ticket();
		Ticket t4 = new Ticket();

		t1.start();
		t2.start();
		t3.start();
		t4.start();
		*/
	}
}

8、运行结果


现在就是4个线程共同操作一个共享数据,并且没有出现0和负数的情况;

9、同步原理

|--四个线程,这时有两个标志位,一个是0,一个是1,当0线程获取到cpu执行权时,判断标志位是1,进入同步代码快中,0线程进入以后,就把标志位恩恩1变成了0;把锁给关上了,然后0线程就判断if语句,满足条件,就开始读取,这时读取到了sleep语句,这是0线程就处于卧倒状态,这时1线程就进入懂啊了语句中,1线程就判断标志位是0,-->但是1线程是进不来的,这时0线程进醒了,就继续执行语句,出了同步,这是0线程就又做了一件事,就是把标志位的0置成了1;这时3线程抢到了执行权,这时3线程就把标志位改成了0;synchronized(obj)-->这个是一个锁,只有拿到了这个锁才可以进入到程序中执行,否则就一直等;
|--同步经典例子:火车上卫生间
|--对象如同锁,持有锁的线程可以在同步中执行;没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁;

10、同步前提

|--必须要有两个或者两个以上的线程;
|--必须是多个线程使用同一个锁;

11、同步好处和弊端

好处:解决了多线程的安全问题;
弊端:多个线程需要判断锁,较为消耗资源;

第二部分

1、同步函数的描述

需求:银行有个金库,有两个储户分别往里面存钱,每次存100,分别存3次;
问题的描述:当储户在存钱的时候,第一个储户出了100元,但是没有立即执行输出语句,而是卧倒了,这是又来了一个储户,来存钱,存了100后,变成了200元,这时就执行了输出语句,打印了200元,而没有打印100;这就是出现了安全问题;
如何找问题:
|--明确哪些代码是多线程运行代码;
|--明确共享数据;
|--明确多线程运行代码中,哪些语句是操作的是共享数据;

2、同步函数

同步有两种表现形式:
|--同步代码:把同步放在代码中;
|--同步函数:把同步作为修饰符放在函数上;

3、同步函数的锁是this

|--如果卖票的程序在函数上使用同步的话,那么是不OK的,这样输出的是只有0线程一直在运行,把锁就一直锁起来了,其他的线程就没有运行;
|--同步函数用的是哪一个锁呢?
|--函数需要被对象调用,那么函数都有一个所属对象应用,就是this,所以同步函数使用的锁是this;

验证:

|--当同步代码块里面的锁的对象是obj的时候,同步函数里面的锁是this的时候,运行的结果是线程0和线程1交替运行,而且还出现了不同步的现象;

|--当同步代码块里面的锁的对象是this的时候,同步函数里面的锁是this的时候,运行的结果只有线程1运行;

|--由此可见,同步函数里面的锁是this锁;

4、代码体现

//卖票的程序
//线程0是同步代码块,线程1是同步函数;
class Ticket implements Runnable
{
	private int tick =100;
	Object obj = new Object();
//	boolean型的标记
	boolean flag = true;
	public void run()
	{
//		是true的话
		if(flag)
		{
			while(true)
			{
//			同步代码块
				synchronized(this)
				{
					if(tick>0)
					{
						try
						{
							Thread.sleep(10);
						}
						catch (Exception e)
						{
							System.out.println(e.toString());
						}
//						同步代码快
						System.out.println(Thread.currentThread().getName()+"--code--"+tick--);
					}
				}
			}
		}
//		如果是false的话
			else
				while(true)
					show();
	}
//	同步函数
	public synchronized void show()//这个锁是this;
	{
		if(tick>0)
		{
			try
			{
				Thread.sleep(10);
			}
			catch (Exception e)
			{
				System.out.println(e.toString());
			}
//			同步函数
			System.out.println(Thread.currentThread().getName()+"--show--"+tick--);
		}
	
	}
}
class ThisLockDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
//		Thread t3 = new Thread(t);
//		Thread t4 = new Thread(t);

		t1.start();
//		让主线程停10毫秒
		try
		{
			Thread.sleep(10);
		}
		catch (Exception e)
		{
			System.out.println(e.toString());
		}
//		把flag改为false,并开启t2
		t.flag =false;
		t2.start();
//		t3.start();
//		t4.start();
	}
}

5、运行结果


由此可见,同步函数用的是this锁;

第三部分

1、同步静态函数描述

|--通过验证法发现同步静态函数出现了不同步的现象;也就是说静态函数用的锁不是this锁;其实静态方法中是没有this的,因为静态中是没有对象的;

|--静态静内存后,内存中没有本类对象,但是一定有该类所属的字节码文件对象;

|--格式是:类名.class;该对象的类型是Class;

2、通过代码展现

class Ticket implements Runnable
{
	private static int tick =100;
	Object obj = new Object();
	boolean flag = true;
	public void run()
	{
		if(flag)
			while(true)
			{
//			该类所属的字节码文件对象;
				synchronized(Ticket.class)
				{
					if(tick>0)
					{
						try
						{
							Thread.sleep(10);
						}
						catch (Exception e)
						{
							System.out.println(e.toString());
						}
						System.out.println(Thread.currentThread().getName()+"--code--"+tick--);
					}
				}
			}
			else
				while(true)
					show();
	}
	public static synchronized void show()
	{
		if(tick>0)
		{
			try
			{
				Thread.sleep(10);
			}
			catch (Exception e)
			{
				System.out.println(e.toString());
			}
			System.out.println(Thread.currentThread().getName()+"--show--"+tick--);
		}
	
	}
}
class StaticMethodDemo
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();

		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);

		t1.start();
		try
		{
			Thread.sleep(10);
		}
		catch (Exception e)
		{
			System.out.println(e.toString());
		}

		t.flag =false;
		t2.start();
	}
}

3、运行结果


当在静态同步函数,发现其的对象不是this锁了,而是该类所属的字节码文件对象,类名.class

第四部分

我的总结

|--在同步代码块里面用的锁是任意对象,
|--在同步函数里面用的锁是this;
|--在静态同步函数上用的锁是该类所属的字节码文件对象,格式:类名.class;
|--想要解决多线程的安全问题,就要先明确同步的两个前提:是不是有多个线程,是不是用的是同一把锁;
发布了40 篇原创文章 · 获赞 0 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/java9832/article/details/46417161