Java多线程2——synchronized和Lock

    生产者消费者问题是多线程经常要碰到的问题,需要达成的目标是:生产者生产一个商品,消费者就要消费一个商品,然后生产者才能继续生产。

Demo1

class ProducerConsumerDemo {
	public static void main(String[] args) {
		Resource r = new Resource();

		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);

		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		t1.start();
		t2.start();
	}
}
//资源
class Resource{
	private int count = 1;
		
	public synchronized void set(){
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者.."+count);
	}
	public synchronized void out(){
		System.out.println(Thread.currentThread().getName()+"...消费者........."+count);
	}
}
//生产者
class Producer implements Runnable{
	private Resource res;

	Producer(Resource res){
		this.res = res;
	}
	public void run(){
		while(true){
			res.set();
		}
	}
}
//消费者
class Consumer implements Runnable{
	private Resource res;

	Consumer(Resource res){
		this.res = res;
	}
	public void run(){
		while(true){
			res.out();
		}
	}
}
 
    上面的代码有三个类,一个是资源,一个是生产者,另外一个是消费者,在资源类里有一个count计数器,代表第几个商品。在生产者消费者里定义一个资源类是为了接收同一个外面new出来资源类,这样同步函数用的this锁就唯一指向那个资源类。尽管这样,我们这个程序依然是不成功的,运行出来的结果是生产了N多个商品,才会去消费。所以我们必须想办法让生产者生产完后,就让他停下,等待消费者消费完之后生产者再继续生产。Java其实已经定义好了这个功能,那就是wait(),和notify()。
Demo2:单个生产者消费者

class ProducerConsumerDemo {
	public static void main(String[] args) {
		Resource r = new Resource();

		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);

		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		t1.start();
		t2.start();
	}
}
//资源
class Resource{
	private int count = 1;
	private boolean flag = false;
		
	public synchronized void set(){
		if(flag)
			try{this.wait();}catch(Exception e){}
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者.."+count);
		flag = true;
		this.notify();
	}
	public synchronized void out(){
		if(!flag)
			try{wait();}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"...消费者........."+count);
		flag = false;
		this.notify();
	}
}
//生产者
class Producer implements Runnable{
	private Resource res;

	Producer(Resource res){
		this.res = res;
	}
	public void run(){
		while(true){
			res.set();
		}
	}
}
//消费者
class Consumer implements Runnable{
	private Resource res;

	Consumer(Resource res){
		this.res = res;
	}
	public void run(){
		while(true){
			res.out();
		}
	}
}

    这段代码多了一个标志flag以及wait()和notify()。我们先来了解一下什么是wait(),notify()。他们不是Thread类里的方法,而是object类里就有的,他们必须在synchronized里面使用,wait的作用是,当一个线程拿到锁进入synchronized之后,释放该锁,并让线程进入休眠状态,直到其他线程调用notify之后,wait的线程才会被唤醒,并继续持有锁。需要注意的是,notify()之后并不会马上就释放锁,唤醒一个线程,而是等待相应synchronized(){}语句块全部执行完毕之后才会将锁释放。
    所以该段代码的执行流程是:先把flag置为false(可以理解为还没有商品),所以生产者不能等待,而必须去生产,生产完之后,flag置为true(告诉消费者有商品可以消费了),并唤醒消费者,如果此时cup的执行权还在生产者手里,那么生产者再次进入循环,而此时flag为true,生产者wait,释放锁,释放cpu的执行资格,进入休眠状态。那么cpu的执行资格给了消费者,此时flag为true,所以消费者必须去消费一个商品,然后把flag置为false(告诉生产者又可以生产了),最后又唤醒了生产者去生产。
    对于单个生产者消费者,这样做是没有问题的,但是如果有两个生产者消费者,上面的程序又会出错,有可能出现生产了两个商品,而消费者只消费一个,或者只生产一个商品,却消费了两个。出现这种情况的原因是:假如t1,t2线程是生产者,t3,t4线程是消费者,t1首先进入,生产完一个把标志置为true,再循环之后wait,这时t2也进入,因为标志为true,也wait,然后t3进入,消费完一个把标志置为false,然后notify一个线程,注意此时t4还没进入,所以能被唤醒的只有生产者,目前为止一切正常。假如t1被唤醒了,生产完一个,标志置为true,然后notify一个线程,重点来了,这时能被唤醒的除了消费者还有可能是也在wait的t2,如果t2被唤醒,那么t2将继续执行,再次生产一个商品,这时就一次性生产了两个商品,出错了。
    解决步骤:1.把if改成while,让线程每次被唤醒之后都再次去检测那个标志。当然光有这个不行,不然在上面的重点那里,t2被唤醒之后,再次去检查标志量,发现为true,被wait掉,此时所有线程都在等待状态,程序也无法执行下去(全部等待状态跟死锁又略微有点不同,死锁是几个线程共同争抢一个资源导致的,而这里是所有线程都休眠了)所以还应该有个步骤2
	      2.就是把notify改成notifyAll,notifyAll就是把所有线程都唤醒,当生产者消费者都被唤醒后,就不会出现1中的状况了。
下面是完整代码
Demo3:多个生产者消费者

class ProducerConsumerDemo {
	public static void main(String[] args) {
		Resource r = new Resource();

		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);

		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
//资源
class Resource{
	private int count = 1;
	private boolean flag = false;
		
	public synchronized void set(){
		while(flag)
			try{this.wait();}catch(Exception e){}
		count++;
		System.out.println(Thread.currentThread().getName()+"...生产者.."+count);
		flag = true;
		this.notifyAll();
	}



	public synchronized void out(){
		while(!flag)
			try{wait();}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"...消费者........."+count);
		flag = false;
		this.notifyAll();
	}
}
//生产者
class Producer implements Runnable{
	private Resource res;

	Producer(Resource res){
		this.res = res;
	}
	public void run(){
		while(true){
			res.set();
		}
	}
}
//消费者
class Consumer implements Runnable{
	private Resource res;

	Consumer(Resource res){
		this.res = res;
	}
	public void run(){
		while(true){
			res.out();
		}
	}
}
上面多次提到锁这个概念,但是在使用synchronized时,这个锁是隐式存在的,我们不能实际操控它,而且我们notifyAll的时候,同时唤醒了生产者和消费者,实际情况是只要唤醒一方就可以了。所以在JDK1.5之后,有了一个新的机制,那就是Lock,用来代替synchronized。先看看API里的介绍,


方法摘要
voidlock()
获取锁。
voidlockInterruptibly()
如果当前线程未被中断,则获取锁。
ConditionnewCondition()
返回绑定到此 Lock 实例的新 Condition 实例。
booleantryLock()
仅在调用时锁为空闲状态才获取该锁。
booleantryLock(long time, TimeUnit unit)
如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
voidunlock()
释放锁。
Lock是一个接口,有lock和unlock方法,介绍中还说支持多个Condition对象,在看看Condition对象是什么东西。


方法摘要
voidawait()
造成当前线程在接到信号或被中断之前一直处于等待状态。
booleanawait(long time, TimeUnit unit)
造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
longawaitNanos(long nanosTimeout)
造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
voidawaitUninterruptibly()
造成当前线程在接到信号之前一直处于等待状态。
booleanawaitUntil(Date deadline)
造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。
voidsignal()
唤醒一个等待线程。
voidsignalAll()
唤醒所有等待线程。
在介绍中说Lock替代了synchronized,Condition替代了Object监视器,其实这个监视器就是synchronized()括号中的对象。而且Condition中还有await和singal方法,这是用来替代wait和notify的。看下面的代码实际感受下这个Lock到底是怎么用的。

Demo4:Lock机制实现线程同步

import java.util.concurrent.locks.*;

class LockDemo {
	public static void main(String[] args) {
		Resource r = new Resource();

		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);

		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);

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

class Resource{
	private int count = 1;
	private boolean flag = false;
	
	private ReentrantLock lock = new ReentrantLock();	//获得一个锁

	private Condition condition_pro = lock.newCondition();	//生产者Condition
	private Condition condition_con = lock.newCondition();	//消费者Condition

	public  void set()throws InterruptedException{
		lock.lock();	//锁上
		try{
			while(flag)
				condition_pro.await();	//生产者等待
			count++;
			System.out.println(Thread.currentThread().getName()+"...生产者.."+count);
			flag = true;
			condition_con.signal();	//唤醒消费者 
		}
		finally{
			lock.unlock();//释放锁的动作一定要执行。
		}
	}
	
	public  void out()throws InterruptedException{
		lock.lock();
		try{
			while(!flag)
				condition_con.await();	//消费者等待
			System.out.println(Thread.currentThread().getName()+"...消费者........."+count);
			flag = false;
			condition_pro.signal();	//唤醒生产者
		}
		finally{
			lock.unlock();	
		}
		
	}
}

class Producer implements Runnable{
	private Resource res;

	Producer(Resource res){
		this.res = res;
	}
	public void run(){
		while(true){
			try{
				res.set();
			}
			catch (InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}

class Consumer implements Runnable{
	private Resource res;

	Consumer(Resource res){
		this.res = res;
	}
	public void run(){
		while(true){
			try{
				res.out();
			}
			catch (InterruptedException e){
				e.printStackTrace();
			}
		}
	}
}

    我们要使用这个锁,肯定得先弄一个锁,但是这个锁是一个接口,不能产生对象,好在它有几个实现类实现了这个接口,其中一个就是ReentrantLock,我们就用它new一个锁出来。刚才说了Lock是用来代替synchronized的,而Condition用来代替synchronized()括号中的对象的,所以我们还得有Condition。需要注意的是,对象是在synchronized中使用的,与synchronized相关,Condition也必须与Lock相关才能正常使用,在Lock的方法中我们可以发现有一个newCondition()方法,它就可以返回绑定到此Lock的Condition实例。那么为什么设定两个呢,接着看下面的代码,在set函数中,lock.lock到lock.unlock这段就是要同步的代码块,其他的与Demo3中的代码类似,只不过wait的时候我们可以指定是生产者wait,signal的时候指定是消费者唤醒,这样也不需要signalAll了,让一个消费者醒来程序就可以继续运行,所以这就是我们设定两个Condition的原因,这也是Lock机制比synchronized所强大的地方。另外await函数会抛出一个异常,这可能导致我们拿到锁之后,程序异常退出,锁依然没有释放,所以这里我们用了finally,让锁无论如何都会释放,这里没有catch,因此不是用来处理await异常的,await的异常被抛出去了,在外面被catch处理。out函数与set函数类似,就不解释了。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值