多线程

一、线程和进程

1、进程

  • 进程指的是计算机内存中开辟的一块空间。例如:QQ、360等程序

2、线程

  • 线程是进程中的一个执行流程

3、多线程

  • 两个或两个以上并发的执行流程

4、多线程的原理

  • 单cpu单内核在某一时刻只能执行一个线程(执行流程/任务),多个线程之间是通过轮询执行的。宏观上是同时执行了多个任务,但实际上只执行了一个任务。

5、主线程和子线程

  • 主线程是最先启动的线程。在main()方法中,默认线程名叫main。
  • 子线程是在主线程中创建出来的。默认线程名叫Thread-N,N代表序号,从0开始。

二、创建子线程的三种方式

1、继承Thread类

package com.gaj.day03;

/**
 * 多线程 创建子线程
 * 创建子线程方案一:继承Thread类
 * 特点:继承类
 * @author Jan
 *
 */
class MyThread01 extends Thread{

	// 默认子线程名为Thread-0, 修改子线程名为myThread
	public MyThread01(String name){
		this.setName(name);
	}
	
	@Override
	public void run() {
		for(int i = 0; i < 10; i ++){
			System.out.println(Thread.currentThread() + ":" + i);
		}
	}
}
public class Demo1 {

	public static void main(String[] args) {
		// 新建线程
		MyThread01 myThread = new MyThread01("myThread");
		// 调用start() 处于就绪状态
		myThread.start(); 		// start()方法只能启动一次

		// 不能直接调用run()方法	这是主线程在执行
//		myThread.run();
		
		
		// 若优先级相同,先执行主线程 和代码顺序无关
		for(int i = 0; i < 10; i ++){
			System.out.println(Thread.currentThread() + ":" + i);
		}
	}
	
}

2、实现Runnable接口

在这里插入图片描述

package com.gaj.day03;

/**
 * 多线程 创建子线程
 * 创建子线程方案二:实现runnable接口,重写run()方法
 * 特点:函数式接口
 * @author Jan
 *
 */
class MyThread02 implements Runnable{

	@Override
	public void run() {
		for(int i = 0; i < 10; i++){
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
	
}
public class Demo2 {

	/**
	 * 争夺时间片
	 * 不是按顺序执行的,(优先级相同的情况)只是靠前的代码执行几率要更大些
	 * @param args
	 */
	public static void main(String[] args) {
		// 方式一:普通创建 适合使用多次
		MyThread02 myThread = new MyThread02();
		Thread thread = new Thread(myThread, "subThread1");
		thread.start();
		
		// 方式二:匿名内部类 适合使用一次
		new Thread(new Runnable() {
			public void run() {
				for(int i = 0; i < 10; i ++){
					System.out.println(Thread.currentThread().getName() + ":" + i);
				}
			}
		}, "subThread2").start();

		// 方式三:Lambda表达式 对匿名内部类的简化
		new Thread(() -> {for(int i = 0; i < 10 ; i ++) System.out.println(Thread.currentThread().getName() + ":" + i);}, "subThread3").start();
	}

}

3、实现Callable接口

在这里插入图片描述

package com.gaj.day03;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 多线程 创建子线程
 * 创建子线程方案三:实现callable<T>接口,重写call()方法
 * 特点:函数式接口,有返回值,call()能抛异常
 * @author Jan
 *
 */
class MyThread03 implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for(int i = 0; i < 10; i++){
			System.out.println(Thread.currentThread().getName() + ":" + i);
			sum += i;
		}
		return sum;
	}
	
}
public class Demo3{

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 方式一:
		MyThread03 myThread = new MyThread03();
		FutureTask<Integer> futureTask = new FutureTask<>(myThread);
		Thread thread = new Thread(futureTask, "subThread01");
		thread.start();
		// 通过FutureTask.get()方法获取返回值
		System.out.println("返回值:" + futureTask.get());
		
		// 方式二:匿名内部类,拿不到返回值结果
		new Thread(new FutureTask<List<Integer>>(new Callable<List<Integer>>() {
			List<Integer> list = new ArrayList<>();
			@Override
			public List<Integer> call() throws Exception {
				for(int i = 0; i < 10; i++){
					System.out.println(Thread.currentThread().getName() + ":" + i);
					list.add(i);
				}
				return list;
			}
		}), "subThread02").start();
		
		//方式三:将FutureTask抽取出来拿结果
		FutureTask<List<Integer>> ft = new FutureTask<List<Integer>>(new Callable<List<Integer>>() {
			List<Integer> list = new ArrayList<>();
			@Override
			public List<Integer> call() throws Exception {
				for(int i = 0; i < 10; i++){
					System.out.println(Thread.currentThread().getName() + ":" + i);
					list.add(i);
				}
				return list;
			}
		});
		new Thread(ft, "subThread03").start();
		System.out.println(ft.get());
	}

}

注意:需要通过FutureTask的get()方法去接收返回值。

4、三种方式的区别

  • 继承Thread类:当前的子类本身就是线程类,不利于资源(对象)的共享。
  • 实现Runnable和Callable接口:
    共同点:
    有利于资源(资源)共享、都是函数式接口
    不同点:
RunnableCallable
实现的方法run()call()
返回值没有返回值有返回值
异常没有声明异常必须声明异常

三、线程的状态

在这里插入图片描述
线程共五种状态:新建、就绪、运行、阻塞和死亡。

1. 新建(New)

创建一个Thread对象。 new Thread();

2. 就绪(Runnable)

就绪处于等待CPU调用执行状态。调用start()方法进入就绪状态。

3. 运行(Running)

运行状态是指CPU调用了某个线程,执行了线程的任务,子线程中run()方法或call()方法。

4. 阻塞(Blocked)

运行状态的线程由于某种原因暂停CPU的调用执行。

5. 死亡(Dead)

死亡状态是指线程的结束。

四、blocked的一些方法

1、sleep()、join()、interrupt()、setDaemon()

package com.gaj.day03;

/**
 * sleep()和join() 睡眠暂停/优先加入
 * interrupt() 线程中断
 * Daemon 守护线程
 * @author Jan
 *
 */
class SubThread04 implements Runnable{

	@Override
	public void run() {
		for(int i = 0; i < 10; i++){
			if(i == 3){
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					System.out.println(Thread.currentThread().getName() + "进入了异常处理阶段");
				}
			}
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
	
}

public class Demo4 {

	public static void main(String[] args) throws InterruptedException {
		Thread thread1 = new Thread(new SubThread04(), "1号子线程");
		Thread thread2 = new Thread(new SubThread04(), "2号子线程");
		
//		thread1.setDaemon(true); // 将thread1设置为守护线程
		thread1.start();
		thread2.start();
		
		thread1.interrupt(); // 程序中断,thread1进入异常处理阶段 前提是该线程处于sleep或join状态
		
//		如何让main程序最后执行? 
//		方式一:
//		Thread.sleep(1000); // 暂停,睡眠 单位:毫秒
		
//		方式二:
//		thread1.join(); // 让该线程先执行
//		thread2.join(); 
		
//		System.out.println(Thread.currentThread().getName() + "线程结束");
		
//		方式三:
		while(!thread1.isAlive() && !thread2.isAlive()){ // 判断线程是否存活,存活为True
			System.out.println(Thread.currentThread().getName() + "线程结束");
			System.exit(0);
		}

	}
	
}

sleep(毫秒):线程A等待sleep了,那么线程A就处于了阻塞状态,当时间到了,恢复到就绪状态,等待cpu调用执行。
join():让其它线程先执行完。 在线程A中调用了线程B的join方法,那么线程A等待线程B执行完后才能恢复到就绪状态,等待cpu调用执行。
interrupt():线程中断。当执行了A.interrupt()方法,那么线程A被中断,进入到异常处理阶段。
Daemon 守护线程 只要所有的被守护线程结束了,那么所有的守护线程都会结束。

2、sleep()、wait()、notify()、notifyAll()

package com.gaj.day03;

/**
 * wait()和notify()、notifyAll() sleep()
 * 
 * @author Jan
 *
 */
class SubThread05 implements Runnable {
	int i = 10;

	@Override
	synchronized public void run() {
		for (; i >= 0; i--) {
			if (i == 5 && Thread.currentThread().getName().equals("1号子线程")) {
				try {
//					Thread.sleep(1000); // 没有释放锁,另一个线程无法执行
//					wait(1000);  // 等待1秒后,释放锁了
					wait(); // 不手动唤醒,一直等待;没有带参数的需要notify()/notifyAll()唤醒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			if(i == 2){
				notify(); // 唤醒等待池中的任意一个线程
//				notifyAll(); // 唤醒等待池中所有线程
				System.out.println("已唤醒"); //会输出一个-1才结束
			}
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}

public class Demo5 {
	public static void main(String[] args) {
		SubThread05 subThread = new SubThread05();
		new Thread(subThread, "1号子线程").start();
		new Thread(subThread, "2号子线程").start();
	}
}

不同点:

sleepwait
sleep(参数 毫秒)wait(毫秒);wait()
没有放弃锁放弃了锁

3、yield()、setPriority()

package com.gaj.day03;

/**
 * yield() 线程让步* Priority 优先级 1-10 优先级越高 被优先执行的几率越高 当然还有很多其他阻塞因素影响
 * 由于cpu执行速度极快 有些情况下即使让步以及设置优先级也看不出来
 * @author Jan
 *
 */
class SubThread06 implements Runnable{
	@Override
	public void run() {
		for(int i = 0; i < 10; i++){
			if(i == 7 && Thread.currentThread().getName().equals("1号子线程")){
				Thread.yield(); // 让步,回到就绪状态
				System.out.println("1号子线程让步了");
			}
			System.out.println(Thread.currentThread()+ ":" + i);
		}
	}
}
public class Demo6 {
	public static void main(String[] args) {
		SubThread06 subThread = new SubThread06();
		Thread thread1 = new Thread(subThread, "1号子线程");
		Thread thread2 = new Thread(subThread, "2号子线程");
		
//		设置优先级
//		public final static int NORM_PRIORITY = 5; 默认为5
		thread1.setPriority(Thread.MIN_PRIORITY); // 1
		thread2.setPriority(Thread.MAX_PRIORITY); // 10
		
		thread1.start();
		thread2.start();
	}
}

yield() 线程让步 让正在运行的线程回到就绪状态。
Priority优先级:理论上,等优先级线程有同等的权利使用CPU,优先级高的线程比优先级低的线程获得更多的CPU时间;但实际上,线程获得的CPU时间通常由包括优先级在内的多个因素决定。

五、synchronized和ReentrantLock

1、synchronized

同步:当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用。达到此目的的过程叫做同步。
当一个线程需要锁定,它必须进入管程(管程:使用资源独占的一种权力)

package com.gaj.day03;

/**
 * synchronization关键字 同步锁
 * 访问同步代码块/同步方法时 需要争夺锁,争夺锁成功的线程进入锁定池,代码块/方法执行完成后释放锁
 * @author Jan
 *
 */

// 两人向同一账户存钱,每人存500
class SubThread07 implements Runnable{
	private int money = 0;
	
	// 同步方法 锁this对象
//	synchronized public void setMoney(){
	public void setMoney(){
		this.money += 100;
		System.out.println(Thread.currentThread().getName() + "存款:100,余额:" + money);
	}
	@Override
	public void run() {
		// 同步代码块
		synchronized (this) {
			for(int i = 0; i < 5; i++){
				setMoney();
			}
		}
	}
}
public class Demo7 {

	public static void main(String[] args) {
		SubThread07 subThread = new SubThread07();
		new Thread(subThread, "赵四").start();
		new Thread(subThread, "王五").start();
		
	}

}

  • 获得锁:

    当线程 执行 同步代码块或同步方法时,需要获得同步锁。当线程A获得同步锁成功了,那么其它线程就不能再访问此对象的同步代码块或同步方法了,只有线程A释放了锁,那么其它线程才可以继续争夺锁。

  • 释放锁:
    1.同步代码块或同步方法的代码都执行完了就会释放锁;
    2.同步代码块或同步方法中有没处理的Exception或 遇到了Error那么或自动释放锁;
    3.同步代码块或同步方法中break; return ;结束了方法或代码块,那么会自动释放锁;
    4.同步代码块或同步方法中wait()那么也会释放锁。

2、ReentrantLock

公平锁(可重入锁)
try{ 加锁lock } finally{ 释放锁unlock }

package com.gaj.day03;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Lock -> ReentrantLock 公平锁 
 * 通过使用lock()和unlock()方法进行加锁和解锁
 * @author Jan
 *
 */

// 两人向同一账户存钱,每人存500
class SubThread08 implements Runnable {
	private int money = 0;

	Lock lock = new ReentrantLock(true);

	public void setMoney() {
		try {
			lock.lock();
			this.money += 100;
			System.out.println(Thread.currentThread().getName() + "存款:100,余额:" + money);
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			// 一定要释放
			lock.unlock();
		}
	}

	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			setMoney();
		}
	}
}

public class Demo8 {

	public static void main(String[] args) {
		SubThread08 subThread = new SubThread08();
		new Thread(subThread, "赵四").start();
		new Thread(subThread, "王五").start();

	}

}

试图获得锁:tryLock() boolen 获得锁成功 true
试图获得锁: tryLock(long,TimeUnit) 再指定的时间内获得锁,获得成功返回true

package com.gaj.day03;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock(true) 释放锁后让等得更久的线程先执行
 * @author Jan
 *
 */
class SubThread09 implements Runnable{
	Lock lock = new ReentrantLock(true);
	@Override
	public void run() {
		boolean tag = false;
		try {
			tag = lock.tryLock(1, TimeUnit.SECONDS); // 在一定时间内试图获得锁 返回True
//			tag = lock.tryLock(); // 不给时间可能会冲突 有一个线程直接走else不执行了
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		if(tag){
			System.out.println(Thread.currentThread().getName() + "获得锁成功!");
			try {
				for(int i = 0; i < 10; i++ ){
					System.out.println(Thread.currentThread().getName() + ":" + i);
				}
			}finally{
				lock.unlock();
				System.out.println(Thread.currentThread().getName() + "释放了锁");
			}
		}else{
			System.out.println(Thread.currentThread().getName() + "获得锁失败!");
		}
	}
}

public class Demo9 {
	public static void main(String[] args) {
		SubThread09 subThread = new SubThread09();
		new Thread(subThread, "1号子线程").start();
		new Thread(subThread, "2号子线程").start();
	}
}

不同点:

synchronizedLock
简单创建对象
不能显示的获得了所和释放锁可以显示的获得锁和释放锁
关键字(同步对象)提供更多的功能。例如:可以获得锁的状态tryLock()

六、多线程下的单例模式

package com.gaj.day04;

/**
 * 单例模式
 * 懒汉式
 * 饿汉式
 * 最佳线程安全单例模式
 */

// 懒汉式 线程不安全 加上synchronized -> 线程安全
class LazySingle{
	private static LazySingle lazySingle; // null;
	private LazySingle(){}
	synchronized public static LazySingle getInstance(){
		if(lazySingle == null){
			lazySingle = new LazySingle();
		}
		return lazySingle;
	}
	
}

// 饿汉式 线程安全
// 但是Class.forName() 也会触发类加载
class HungrySingle{
	private static HungrySingle hungrySingle = new HungrySingle(); // 初始化类时加载一次
	public static HungrySingle getInstance(){
		return hungrySingle;
	}
}

// 单例模式线程安全的最佳方案
class PreferredSingle{
	// 静态内部类
	private static class SingleInstance{
		private static PreferredSingle preferredSingle = new PreferredSingle();
	}
	private PreferredSingle(){}
	public static PreferredSingle getInstance(){
		return SingleInstance.preferredSingle;
	}
}

public class Demo1 {
	public static void main(String[] args) {
		
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("懒汉式" + LazySingle.getInstance());
				System.out.println("饿汉式" + HungrySingle.getInstance());
				System.out.println("最佳方案" + PreferredSingle.getInstance());
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("懒汉式" + LazySingle.getInstance());
				System.out.println("饿汉式" + HungrySingle.getInstance());
				System.out.println("最佳方案" + PreferredSingle.getInstance());
			}
		}).start();
	}
}

七、死锁以及解决方案

  • 死锁发生在当多个线程进入到了循环等待状态;死锁是很难调试的错误,我们在编写多线程并含有同步方法调用的程序中更格外小心,避免死锁的发生。
    在这里插入图片描述
package com.gaj.day04;

class ZhangSan {
	public void say() {
		System.out.println("张三:你给我书,我就给你画");
	}

	public void get() {
		System.out.println("张三获得了书");
	}
}

class LiSi {
	public void say() {
		System.out.println("李四:你给我画,我就给你书");
	}

	public void get() {
		System.out.println("李四获得了画");
	}
}

class SubThread01 implements Runnable {
	private static ZhangSan zhangSan = new ZhangSan();
	private static LiSi liSi = new LiSi();
	boolean tag = false;
	@Override
	public void run() {
		if(tag){ // 张三
			synchronized (zhangSan){
				zhangSan.say();
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized(liSi){
					zhangSan.get();
				}
			}
		}else{ // 李四
			// 解决方案:让李四多等一会
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			synchronized (liSi){
				liSi.say();
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized(zhangSan){
					liSi.get();
				}
			}
		}
	}
}

public class Demo2 {
	public static void main(String[] args) {
		SubThread01 subThread1 = new SubThread01();
		subThread1.tag = true;
		SubThread01 subThread2 = new SubThread01();
		subThread2.tag = false;

		new Thread(subThread1).start();
		new Thread(subThread2).start();
	}
}

八、BlockingQueue

阻塞队列:生产和消费模式的缓冲区
在这里插入图片描述
用Queue作为缓冲区
在这里插入图片描述

package com.gaj.day04;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 生产消费者模式
 * BlockingQueue 阻塞队列 指定队列大小
 * @author Jan
 *
 */
// 生产者
class Producer implements Runnable{
	private BlockingQueue<Integer> queue;
	public Producer(BlockingQueue<Integer> queue){
		this.queue = queue;
	}
	@Override
	public void run() {
		for(int i = 1; i <= 10; i++){
			try {
				Thread.sleep(100);
				queue.put(i); // 阻塞方法:只要队列满了,那么会一致阻塞,直到队列有空位
//				queue.add(i); // 非阻塞方法:添加失败会引发异常
				System.out.println("生产了:" + i);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}
// 消费者
class Consumer implements Runnable{
	private BlockingQueue<Integer> queue;
	public Consumer(BlockingQueue<Integer> queue){
		this.queue = queue;
	}
	@Override
	public void run() {
		for(int i = 1; i <= 10; i++){
			try {
				Thread.sleep(1000);
				queue.take(); // 阻塞:队列为空会一直阻塞,直到队列添加进入元素
//				queue.remove(); // 非阻塞:移除失败会引发异常
				System.out.println("消费了:" + i);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
public class Demo3 {
	public static void main(String[] args) {
		BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3); // 指定队列大小为3
		new Thread(new Producer(queue)).start();
		new Thread(new Consumer(queue)).start();
	}
}

put()和take()方法,阻塞了会等待。
add()和remove()方法,阻塞了则抛出异常。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
图像识别技术在病虫害检测中的应用是一个快速发展的领域,它结合了计算机视觉和机器学习算法来自动识别和分类植物上的病虫害。以下是这一技术的一些关键步骤和组成部分: 1. **数据收集**:首先需要收集大量的植物图像数据,这些数据包括健康植物的图像以及受不同病虫害影响的植物图像。 2. **图像预处理**:对收集到的图像进行处理,以提高后续分析的准确性。这可能包括调整亮度、对比度、去噪、裁剪、缩放等。 3. **特征提取**:从图像中提取有助于识别病虫害的特征。这些特征可能包括颜色、纹理、形状、边缘等。 4. **模型训练**:使用机器学习算法(如支持向量机、随机森林、卷积神经网络等)来训练模型。训练过程中,算法会学习如何根据提取的特征来识别不同的病虫害。 5. **模型验证和测试**:在独立的测试集上验证模型的性能,以确保其准确性和泛化能力。 6. **部署和应用**:将训练好的模型部署到实际的病虫害检测系统中,可以是移动应用、网页服务或集成到智能农业设备中。 7. **实时监测**:在实际应用中,系统可以实时接收植物图像,并快速给出病虫害的检测结果。 8. **持续学习**:随着时间的推移,系统可以不断学习新的病虫害样本,以提高其识别能力。 9. **用户界面**:为了方便用户使用,通常会有一个用户友好的界面,显示检测结果,并提供进一步的指导或建议。 这项技术的优势在于它可以快速、准确地识别出病虫害,甚至在早期阶段就能发现问题,从而及时采取措施。此外,它还可以减少对化学农药的依赖,支持可持续农业发展。随着技术的不断进步,图像识别在病虫害检测中的应用将越来越广泛。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值