【大数据开发】Java语言基础——多线程安全问题、银行问题、懒汉式多线程安全问题、死锁、线程间通信day20

一、多线程线程安全问题

满足以下两个条件:
(1)多个线程共享了数据
(2)操作共享数据的语句有多条(超过1条),一个线程执行了操作共享数据的语句一部分时CPU就被抢走了,当再次获得CPU时,直接执行了后边的语句,从而导致了错误的修改。

则该程序存在多线程安全问题
如下:
在这里插入图片描述
下面利用Thread的sleep()方法来查看多线程不安全问题,修改上图代码
在这里插入图片描述
在这里插入图片描述
注意:这里需要处理异常InterruptedException,不能抛出,因为run()方法是重写的

class Ticket implements Runnable
{
	private  int num=50;
	private Object obj=new Object();

	public void run()
	{
		while(true)
		{
		  synchronized(obj)//获取锁
		  {
			   if(num>0)
				{
				   //睡着的线程不会释放锁
				  try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}
				  System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
				}
		  }//释放锁
		}
	}
}

class Demo1
{
	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();
		t2.start();
		t3.start();
		t4.start();
	}
}

问题:怎么解决安全问题? ------ synchronized()
操作共享数据的多条语句,只有一个线程全部执行完,其他线程才能执行。

同步代码块:

synchronized(Object obj)	//对象任意
{
	同步代码块
}
通过锁实现了多个线程之间的护持
多个线程需要使用同一把锁

原理如下:
在这里插入图片描述
用自己的话来说:一个线程先要获取到锁,才能执行同步代码块中的代码。即使一个线程睡着了,也不会放弃锁,当线程执行完同步代码块中的所有语句时,才会释放锁,从而实现了一个线程执行完所有操作共享数据的语句时,其他线程才能执行。
synchronized关键字可以修饰方法,可以修饰代码块。但不能修饰构造器、属性等
经典问题:银行问题

class Bank
{
	private int sum;
	private Object obj=new Object();

    //存钱功能  同步函数(方法)
	public synchronized void save(int money)//如果一个方法中的代码需要全部放在同步代码块中,那么这个方法就是同步函数
	{
		//synchronized(obj)
		//{
			sum=sum+money;//t1  t2
			//---->
			System.out.println("总钱数:sum="+sum);
		//}
	}
}
//线程的任务是存钱
class Task implements Runnable
{  
	private Bank bank=new Bank();
	public void run()
	{
		for(int i=1;i<=3;i++)
		{
			bank.save(100);
		}
	}
}
class Demo2 
{
	public static void main(String[] args) 
	{
		//创建任务对象
		Task cus=new Task();

		Thread t1=new Thread(cus);

		Thread t2=new Thread(cus);

		t1.start();
		t2.start();
	}
}

上面的例子都是函数中的全部代码都放在同步代码块中,因此可以将该方法使用synchronized关键字修饰,该方法也被称为同步函数。

同步函数使用的锁是------this
那么静态同步函数使用什么锁?------静态进内存的时候没有兑对象,不能使用this作为锁,只能使用它所属的字节码作为锁

二、单例模式中懒汉式多线程安全问题

//单例设计模式中懒汉式多线程安全问题
class Single
{
	private static Single s;

	private Single(){}

	public static Single getInstance()
	{
       synchronized(Single.class)
		{
			if(s==null)  //t1  t2
				--->
				s=new Single();
		}
		return s;
	}
}
class Task implements Runnable
{
	public void run()
	{
		Single single = Single.getInstance();
	}
}
class Demo4 
{
	public static void main(String[] args) 
	{
		Task task=new Task();

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

		t1.start();
		t2.start();
	}
}

由此可见单例模式中存在多线程安全问题,而饿汉式是线程安全的,因此实际生活中用饿汉式

三、死锁

//死锁:  避免死锁
class Ticket implements Runnable
{
	private   int num=2000;
	private Object obj=new Object();
	boolean flag=true;

	public void run()
	{
		if(flag)
		{
			while(true)
			{
			  synchronized(obj)
			  {
				   fun();
			  }
			}
		}else{
			while(true)
			{
				fun();
			}
		}
			 
	}

    //同步函数  this
	public  synchronized void fun()
	{
		     synchronized(obj)
			  {
				   if(num>0)
					{
					   //睡着的线程不会释放锁
					  try{Thread.sleep(10);}catch(InterruptedException e){e.printStackTrace();}
					  System.out.println(Thread.currentThread().getName()+"....sale..."+num--);
					}
			  }
	}

}
class Demo5
{
	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(InterruptedException e){e.printStackTrace();}

		t.flag=false;

		t2.start();
	}
}

四、线程间的通信

定义:多个线程的任务不同,但是操作的数据相同

class Demo6 
{
	public static void main(String[] args) 
	{
		//线程间的通信:多个线程执行的任务不同,但是操作的数据相同
		Data data=new Data();
		//创建输入线程的任务
        InTask in = new InTask(data);
		//创建输出线程的任务
		OutTask out = new OutTask(data);

		//创建输入线程
		Thread ru=new Thread(in);
		//创建输出线程
		Thread chu=new Thread(out);

		ru.start();
		chu.start();
	}
}

//描述数据
class Data
{
	String name;
	String sex;
}
//定义输入线程的任务
class InTask implements Runnable
{
	private Data data;

    InTask(Data data)
	{
		this.data=data;
	}
	public void run()
	{
		int i=0;
		while(true)
		{
		   synchronized(data){
			if(i==0){
				data.name="黄文强";
				//----->
				data.sex="男";
			}else{
				data.name="黄蓉";
				data.sex="女";
			}
		  }
		   i=(i+1)%2;
		}
	}
}
//定义输出线程的任务
class OutTask implements Runnable
{
	private Data data;

    OutTask(Data data)
	{
		this.data=data;
	}
	public void run()
	{
		while(true){
           synchronized(data)
		   {
		     System.out.println(data.name+"...."+data.sex);
		   }
		}
	}
}

class Demo7 
{
	public static void main(String[] args) 
	{
		//线程间的通信:多个线程执行的任务不同,但是操作的数据相同
		Data data=new Data();
		//创建输入线程的任务
        InTask in = new InTask(data);
		//创建输出线程的任务
		OutTask out = new OutTask(data);

		//创建输入线程
		Thread ru=new Thread(in);
		//创建输出线程
		Thread chu=new Thread(out);

		ru.start();
		chu.start();
	}
}

//描述数据
class Data
{
	String name;
	String sex;
	boolean flag;//false
}
//定义输入线程的任务
class InTask implements Runnable
{
	private Data data;

    InTask(Data data)
	{
		this.data=data;
	}
	public void run()
	{
		int i=0;
		while(true)
		{
		   synchronized(data)
		   {    
			    //判断能不能赋值
				if(data.flag==true)
			    { 
					try{
				        data.wait();//让持有这个锁的线程进入等待状态
				    }catch(InterruptedException e){
					    e.printStackTrace();
				    }
			    }

				if(i==0){
					data.name="黄文强";
					//----->
					data.sex="男";
				}else{
					data.name="黄蓉";
					data.sex="女";
				}

				data.flag=true;
				data.notify();//唤醒对象,允许空唤醒
		  }
		   i=(i+1)%2;
		}
	}
}
//定义输出线程的任务
class OutTask implements Runnable
{
	private Data data;

    OutTask(Data data)
	{
		this.data=data;
	}
	public void run()
	{
		while(true){
           synchronized(data)
		   {
			   //判断能不能输出
			   if(data.flag==false)
			   {
				   try{
				        data.wait();
				    }catch(InterruptedException e){
					    e.printStackTrace();
				    }
			   }

		      System.out.println(data.name+"...."+data.sex);

              data.flag=false;
			  data.notify();
		   }
		}
	}
}


wait():让线程进入等待状态,被放入线程池
notify():唤醒线程池中的任意一个线程
notifyAll():唤醒的是线程池中的所有线程

这三个方法必须由锁来调用,因为锁是任意的对象,任意对象都能调用的方法只能定义在Object类中。
好的,现在实现了线程的安全同步。下面将代码进行优化。

class Demo8 
{
	public static void main(String[] args) 
	{
		//线程间的通信:多个线程执行的任务不同,但是操作的数据相同
		Data data=new Data();
		//创建输入线程的任务
        InTask in = new InTask(data);
		//创建输出线程的任务
		OutTask out = new OutTask(data);

		//创建输入线程
		Thread ru=new Thread(in);
		//创建输出线程
		Thread chu=new Thread(out);

		ru.start();
		chu.start();
	}
}
class Data
{
	private String name;
	private String sex;
	private boolean flag;

	public synchronized void set(String name,String sex)
	{    
		if(flag){
			try{
				this.wait();
			}catch(InterruptedException e)
			{
				e.printStackTrace();
			}
		}

		this.name=name;
		this.sex=sex;

		flag=true;
		this.notify();
	}

	public synchronized void out()
	{
		if(!flag){
			try{
				this.wait();
			}catch(InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		System.out.println(name+","+sex);

		flag=false;
		this.notify();
	}
}

class InTask implements Runnable
{
	private Data data;
	InTask(Data data)
	{
		this.data=data;
	}
	public void run()
	{
		int i=0;
		while(true)
		{
		   if(i==0)
		       data.set("黄文强","男");
		   else
			   data.set("黄蓉","女");

		   i=(i+1)%2;
		}
	}
}

class OutTask implements Runnable
{
	private Data data;
	OutTask(Data data)
	{
		this.data=data;
	}
	public void run()
	{
		while(true)
		{
			data.out();
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值