[Java]多线程之同步及死锁

调用多线程的关键是要重写run()方法,所以继承thread类和实现runnable接口本质上是一样的,都要重写run(),只不过继承只能有一个父类,更推荐使用实现接口的方式

几个小问题:

启动线程应该是start()而不是直接去run()


线程的相关调度完全看CPU

在代码中设置优先级似乎没用,主要看CPU调度

sleep方法,Thread.sleep(1000),当前线程睡眠1秒。需要知道的是,1秒后,线程是回到可执行状态并不是执行状态什么时候执行那是由cpu来决定的。所以sleep(1000)并不是在睡眠1秒后立即执行

yield方法,yield()方法只是把线程的状态由执行状态打回准备就绪状态,所以,执行这个方法后,有可能马上又开始运行,有可能等待很长时间。实际上,yield()方法对应了如下操作;先检测当前是否有相同优先级的线程处于可运行状态,如有,则把CPU的占有权交给次线程,否则继续运行原来的线程,所以yield()方法称为“退让”,它把运行机会让给了同等级的其他线程,

关于stop和interrupt

interrupt()方法是给处于阻塞的线程,如sleep(),抛出异常,使线程退出阻塞,中断发生,但线程仍然在进行,有个好处是能在异常中给出通知。

stop方法就是强行关闭线程,后遗症就多,最好不用

 

在启用多线程后,首先要解决同步问题,然后要解决线程间通信问题~

先引入窗口卖票的例子来说明下同步的问题

某电影院正在上映《速度与激情7》,共有100张票。它又三个售票窗口正在售票。请设计一个应用程序来模拟该电影院的售票

主程序:

窗口:

走起,有问题

Ticketrun内定义的,实际上互不影响了

                                          

拿出来

正常了一些~

 

利用继承去实现,则需要直接new三个窗口

实现接口的时候,是由一个类 new出来的,是同一个类的引用,这就把业务和数据分开了,数据只是在类里面(Window),而业务由线程完成,实质上票数是共享的;

而继承的方法,直接new出三个线程,数据也在线程中,三个窗口独立,互不干涉,所以出现同一张票卖多次

这是声明runnable的另一个好处,数据与业务的分离,前面提到一个好处是继承只能有一个父类

这个时候应该加一个static,声明静态共享

                                             

 

 

另外再加一个需求,实际处理中肯定是要操作时间的,数据也要返回服务器,所以要加一个sleep,但是runnable中没有这个,加currentThread引用。注意,runnable只有一个方法,那就是run(),别的一概没有,想要用就加引用

 

                                                    

 

 

但是实际上,还是会出现同一张票卖多次的情况,自减这一步实际上是两步,先取值,再自减,很有可能CPU在这个间隔内把卖票的线程挂起,没来得及自减,而这时另一个线程进来,正好可以运行,票数没来得及变,这也会造成最后一张票出现负数的情况

 

线程安全问题

3个先决条件:多线程、共享、非原子操作

其中前两者不可避免,非原子操作,也即一个可以分成多步的操作,这个间隙会出问题。实质就是多个线程同时获得对某个对象的操作权。

 

解决方法

同步机制。使用关键字synchonized。使得资源获得锁,在一个线程使用这个对象的时候,即使其他线程也可以调用,被锁着的对象无法再次被操作

 

Synchonized把代码段包起来,这样没有实际效果,因为他的参数,内部类实现的锁对象是在run方法内,此时相当于每一个进程都有自己的一把锁,这就等于没有

 

 

把锁对象拿出来实现,再传回去,但此时有新问题,别人进不来,一直被锁着 

锁应该在循环内,但又有一个问题,当ticket=1的时候,三个线程都能进来,导致最后一张票出了问题

                                                                                                                                                  

 

 

锁内再判断一下

同步锁的好处很明显,缺点就是开销大,消耗资源~

 

然后可以规整一下,把sellTicket作为一个方法拿出来,在run里调用,另外它的object就是一个锁对象,至于是什么对象,可以自己单独定义。

 

方法2:也可以把synchronized作为类似一个类定义的方式写出来,是一个同步块,成了一个同步函数,此时没有object了,锁从哪来的?虚拟机会自己找一个对象,此例中,由于是同一个window引用的,这里的锁实际上是调用了this

 

 

                                                                      

 

OK,完整代码

主程序:

<span style="font-size:14px;">public class SellTicketDemo {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
        Window window= new Window();
		
		Thread window1 = new Thread(window,"window1:--");
		Thread window2 = new Thread(window,"window2:----");
		Thread window3 = new Thread(window,"window3:-------");
		
		window1.start();
		window2.start();
		window3.start();
	}
}
</span>

 

线程窗口:

<span style="font-size:14px;">public class Window implements Runnable {
	int ticket = 100;
	MyClass obj = new MyClass();
	@Override
	public void run() {  		
	   while(ticket > 0){
		sellticket();
	   }
	}
	synchronized void sellticket(){
	  if(ticket > 0){
			System.out.println(Thread.currentThread().getName()+"  正在卖第"+  (ticket--)  +"张票");
		try {
			Thread.currentThread().sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		  }
		}
		else{
			System.out.println("获取到了锁,但是没有票了");
		}
	  }
}
	//这个object 让系统判断是不是一个类,也就是说一把锁
	class MyClass{	
		
    }
</span>


 

 

以上为实现接口的方式,用继承thread类看看有什么不同

   

        

由于继承,直接new出两个线程,相当于分别获得ticket所以锁对象应该加一个static

 

                   

 

 

但是很多时候代码较多,看着不方便,看不见在哪上锁,在哪解锁

Java后来提供了更方便的  ReentrantLock类和lock方法,Lock实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

OK,在代码较多的时候用这个很方便

 

ž      同步弊端

›         影响效率

›         如果出现了嵌套锁,容易产生死锁

ž      死锁问题及其代码

›         死锁是指两个以上的线程在执行过程中,因为争夺资源而产生的一种相互等待的现象

 

复制,再添加一个线程,走起

在新建另一个锁的时候,new两个对象实现两个锁,现在不实例化这个类,直接用成员变量

变成静态的,加上static,可以使用 类名.变量名来使用 省事

模拟需要两个锁

嵌套锁,有了A锁后还需要B

甲拿了A锁,想要B锁,但是B锁目前又在乙那,可以利用flag变量来实现这个场景

怎么给两个线程传两个参数?

可以初始化的时候重载两个构造函数,现在使用的默认的无参构造函数

                         

如何用到线程上了,初始化的时候一个给true,一个给false

             

理想情况下,线程1先拿到A锁,然后再拿到B锁,结束,释放;然后线程2进来,拿B再拿A

走起,停住了,死锁产生

          

第一次线程0顺利拿到A  B锁,第二次拿到A后,B锁被线程1拿去

虽然有锁,但是那一时刻只是拿了A锁,这时CPU调度,线程1发现锁2能用,所以就访问了另一个代码块~

这时候CPU看线程0不能用,0在等待,又去调度1,但是1也只有等待,从而互相等待,死锁了~

完整代码

main:

<span style="font-size:14px;">public class MyDeadLockDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyThread thread1 =new MyThread(true);
		MyThread thread2 =new MyThread(false);	
		thread1.start();
		thread2.start();
	}

}
</span>


thread

<span style="font-size:14px;">public class MyThread extends Thread {
	private static  int ticket =100;
    boolean flag = false;	
	static 	ReentrantLock lock = new ReentrantLock();
	//无参构造函数
	public MyThread() {
		// TODO Auto-generated constructor stub
	}  
	//有参
    public MyThread(boolean b) {
 		// TODO Auto-generated constructor stub
    	flag=b;
 	}
    
	@Override
	public void run() {	
	   while(true){
		 if(flag){
		  synchronized (MyLock.ObjA) {				
			System.out.println(getName()+ " I got lock A in if");				 
				 synchronized (MyLock.ObjB) {					  
					System.out.println(getName()+ " I got lock B if");
					System.out.println(getName()+ " Now i can do my job..");					
				 }
		  }
		 }else{
			 synchronized (MyLock.ObjB) {				
					System.out.println(getName()+ " I got lock B in else");				 
					  synchronized (MyLock.ObjA) {					  
							System.out.println(getName()+ " I got lock A in else");
					  }
			 }
		 }
	   }
	}
}
	</span>


lock

<span style="font-size:14px;">public class MyLock {
	public static Object ObjA= new Object();
	public static Object ObjB= new Object();
}
</span>



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值