大数据之Java基础(二十四):多线程--2

37 篇文章 3 订阅
主要内容
  • 线程安全
  • 线程同步
  • 死锁
  • Lock锁
  • 等待唤醒机制

一、线程安全

1.概念
如果多个线程同时运行,这些线程可能会同时运行某一段代码,这个时候如果全局变量和静态变量只有读操作,没有写操作,那么线程是安全的。可是一旦有写操作(更改变量值),那么此线程就是不安全的。

2.演示代码
public class TsShowTicket implements Runnable{

	/*
	 * 模拟多线程售票
	 */
	private int ticketCount = 100;	//目前剩余的所有电影票
	
	@Override
	public void run() {
		
		while(ticketCount > 0)
		{
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "窗口售出一张票,还剩余 票数:" + ticketCount--);
		}		
	}	
}


	public static void main(String[] args) {
			
		TsShowTicket task = new TsShowTicket();
		
		Thread t1 = new Thread(task, "窗口1");
		Thread t2 = new Thread(task, "窗口2");
		Thread t3 = new Thread(task, "窗口3");
		
		t1.start();
		t2.start();
		t3.start();			
	}

3.线程同步:解决线程安全隐患的问题

  • 线程同步有两种变现形式:同步代码块,同步方法
    • 同步代码块:将代码块用大括号括起来,加上synchronized关键字;
synchronized (锁对象) {
可能会产生线程安全问题的代码
}

  • 锁对象可以是任意的对象obj,但是多线程中,要保证这些线程中,obj是同一个锁才能保证线程安全。大家共用同一个锁,其中A线程拿走了锁,其余的线程没有锁,就只能等着A线程执行完毕,归还锁,然后再去拿着锁去执行线程。可以理解有锁的线程才能去执行。
@Override
	public void run() {
			
		while(true)
		{
			synchronized (lock) {
				
				if(ticketCount > 0)
				{				
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "窗口售出一张票,还剩余 票数:" + ticketCount--);
				}					
			}
		}	
	}


  • 同步方法

  • 同步方法需要在方法的声明上加上synchronized关键字
public synchronized void mothod()
{
可能会产生线程安全问题的代码或者方法;
}
其中,同步方法不需要自己额外的定义锁,因为同步方法的安全锁对象是:this,就是子类实例对象本身。


    • 同步静态方法
public static synchronized void func()
{
可能会产生线程安全问题的代码或者方法;
}
其中,静态同步方法不需要自己额外的定义锁,因为同步方法的安全锁对象是:这个类,就是类本身。

  • 代码展示
@Override
	public void run() {
			
		while(true)
		{
			func();
		}	
	}
	
	
	private synchronized void func()
	{
		
			
			if(ticketCount > 0)
			{				
				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "窗口售出一张票,还剩余 票数:" + ticketCount--);
			}					
		
	}




4.死锁

  • 同步锁使用的弊端:当线程任务中出现了多个同步锁的时候,如果同步中嵌入了其他同步,那么这个时候很容易出现死锁:无限等待。
  • 比如A锁(外层锁)中嵌套B锁(内层锁),如果B锁不能归还,那么A锁就永远不能结束,就无限等待。
  • synchronzied(A锁){
synchronized(B锁){
          //如果B锁
}
}

  • public class TsDathLock implements Runnable{
    
    	private Object lockA = new Object();
    	private Object lockB = new Object();
    	
    	private int x = 0;//new Random().nextInt(1);	//产生随机数0-1
    	
    	@Override
    	public void run() {
    		
    		//一旦出现线程1使用A锁未归还,线程2使用A锁未归还,那么就死了
    		while(true)
    		{
    			if(x % 2 == 0)
    			{
    				synchronized (lockA) {
    					
    					System.out.println(Thread.currentThread().getName() + "偶数lockA");
    					
    					synchronized (lockB) {
    						
    						System.out.println(Thread.currentThread().getName() + "偶数lockB");
    					}									
    				}
    			}
    			else
    			{
    				synchronized (lockB) {
    					
    					System.out.println(Thread.currentThread().getName() + "偶数lockA");
    					
    					synchronized (lockA) {
    						
    						System.out.println(Thread.currentThread().getName() + "偶数lockB");
    					}
    					
    					
    				}
    			}
    			x++;
    		}
    		
    		
    	}
    
    	
    	
    }

5.Lock接口

  • 概念
Lock接口比 synchronized 关键字提供了更多的方法和操作。

  • 常用方法
    • Lock() : 获取锁
    • unlock(); 归还锁,释放锁

  • 使用方法:在需要加锁的代码前添加 lock对象.lock();在代码块结束后添加lock对象.unlock();

  • @Override
    	public void run() {
    		
    		
    		while(true)
    		{
    			//获取锁
    			lock.lock();
    			if(ticketCount > 0 )
    			{
    				try {
    					Thread.sleep(1);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    				
    				System.out.println(Thread.currentThread().getName() + "窗口售出一张票,还剩余 票数:" + ticketCount--);
    				
    			}
    			//归还锁
    			lock.unlock();
    		}
    	}
    



6.等待唤醒机制

  • 线程之间的通信:多个线程处理同一个资源的。
  • 多个线程处理同一个资源,但是往往不同线程之间的处理方式不尽相同,那么通过等待唤醒机制,就可以很好的正确的利用和处理资源。
  • 常用方法
    • wait(); //等待,让正在执行的线程进入线程池中,进行等待操作
    • notify(); //唤醒,唤醒线程池中被wait()的线程。一次只能唤醒一个,而且是任意的
    • notifyAll(); //唤醒线程池中所有wait()等待的线程
  • 其实唤醒的意思就是让wait的线程重新具有执行的资格,实质上还得CPU去分配执行。而且等待和唤醒都是基于锁去执行的。这些方法属于object对象的方法,任何对象都可以调用,但是只有在线程中才会起作用。

  • 代码展示
----------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
public class Resource {

	private boolean flag = false;
	private String name;
	private String sex;

	public synchronized void out() {
		if (!flag) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			// 输出
			System.out.println("姓名: " + name + ",性别: " + sex);

			// 改变标记
			flag = false;

			// 开启等待的输入
			this.notify();
		}

	}

	public synchronized void set(String name, String sex) {
		if (flag) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} else {
			// 设置成员变量
			this.name = name;
			this.sex = sex;
			// 设置之后,Resource中有值,将标记该为 true ,
			flag = true;
			// 唤醒output
			this.notify();
		}

	}

}

---------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------

public class Input implements Runnable{

	
	private Resource r;

	public Input(Resource r) {
		this.r = r;
	}

	@Override
	public void run() {
		int count = 0;
		while (true) {
			if (count == 0) {
				r.set("小明", "男生");
			} else {
				r.set("小花", "女生");
			}
			// 在两个数据之间进行切换
			count = (count + 1) % 2;
		}
	}
	
}

---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
public class Output implements Runnable{

	private Resource r;

	public Output(Resource r) {
		this.r = r;
	}

	@Override
	public void run() {
		while (true) {
			r.out();
		}
	}
}


---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------
public class TsMain {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		// 资源对象
		Resource r = new Resource();
		// 任务对象
		Input in = new Input(r);
		Output out = new Output(r);
		// 线程对象
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		// 开启线程
		t1.start();
		t2.start();

	}

}



7.Sleep()和wait()的区别
sleep和wait都会释放CPU的使用权(不再使用CPU),但是sleep不会释放锁,不能唤醒,时间到了自动激活。wait会释放锁,需要通过notify进行唤醒



全文完!






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值