Thread_sir.w

一、线程是什么?

线程是进程当中更加微观的概念,程序当中一条独立的执行线索,而多线程编程就是让程序当中拥有多条独立执行线索
"同一时间"做多件事

二、为什么要使用多线程?

我们不否认在某些场景下使用多线程确实可以提高效率,但是使用多线程的根本目的并不是为了提高效率,而是为了让程序同时拥有多条独立的执行线索,从而可以服务多个用户,应对多个请求…

三、线程的五大状态

            新生       		就绪        		运行       		消亡
            NewBorn    		Runnable   		  Running    		 Dead


                                    阻塞
                                   Blocking

也可以分为七大状态,分别是新生,就绪,运行,消亡,普通阻塞,锁池阻塞,等待池阻塞

四、如何实现线程

//方式一
class xxxx extends Thread{		
    @Override
    public void run(){
		xxxx;
}
//方式二
class xxx implements Runnable{
    @Override
    public void run(){
		xxxx;
}

//方式三,必须配合线程池使用
class xxx implements Callable<String>{
    @Override
    public String call() throws Exception(){
		xxxx;
         return "xxx";
}
//Callable的优势:1.存在返回值,方便处理数据  2。call方法抛异常,不用我们书写try-catch

五、extends Thread和implements Runnable的区别

public class TestThread{
	public static void main(String[] args){
		ThreadOne t1 = new ThreadOne();//整车进口
		t1.start();//直接启动

		ThreadTwo tt = new ThreadTwo();//进口发动机
		Thread t2 = new Thread(tt);//国产车外壳(进口发动机)
		t2.start();//才能整体启动

		while(true){
			System.out.println("B玩家不停的躲闪 不停的闪现");
		}

	}
}
class ThreadOne extends Thread{
	@Override
	public void run(){
		while(true){
			System.out.println("A玩家不停的走位 不停的q");
		}
	}
}
class ThreadTwo implements Runnable{
	@Override
	public void run(){
		while(true){
			System.out.println("C玩家各种野区游走");
		}
	}
}

六、启动线程时,start和run的区别

当创建一个新的线程时,我们可以选择调用该线程对象的 start 方法或者 run 方法来启动线程。

start 方法的作用是启动一个新的线程,并在该线程中执行任务。调用 start 方法后,系统会为线程分配资源,包括内存空间和 CPU 时间,并在新的线程中运行 run 方法。启动完成后,start 方法会立即返回,程序继续执行后续的代码。这种方式会实现多线程的并发执行,因为每个线程在独立的执行流中运行。

相反,run 方法只是一个普通的方法调用,而不会启动一个新的线程。直接调用 run 方法会在当前线程中执行 run 方法的任务,这意味着任务会在主线程中顺序执行,而不会并发执行。

正常情况下,我们应该使用 start 方法来启动线程。这样可以确保线程在独立的执行流中并发执行。当调用 start 方法后,系统会自动创建一个新的线程,并调用线程对象的 run 方法来执行任务。这样可以充分利用系统资源,同时提高程序的执行效率。

总结来说,使用 start 方法会创建并启动一个新的线程,而使用 run 方法只是在当前线程中普通地调用方法。因此,如果想要实现多线程并发执行,应该使用 start 方法;如果不需要多线程,并且希望在当前线程中顺序执行任务,可以使用 run 方法。

七、如何控制线程

以下是对Java中一些常见的线程方法的详细解剖和简要代码示例:

  1. setPriority()方法:设置线程优先级。优先级范围是1(最低)到10(最高)。以下是示例代码:
SF ts = new SF();
ts.setPriority(1); // 设置线程优先级为最低
  1. static sleep()方法:使当前(写在谁的里面,谁睡觉)正在执行的线程暂停指定的时间。以下是示例代码:
public class TestSleep{
	public static void main(String[] args)throws Exception{
		EtoakThread et = new EtoakThread();
		et.start();
		
		
		//主方法先sleep500毫秒,然后打印叶莉,5000毫秒后打印姚明
		
		et.sleep(500);	
		while(true){
			System.out.println("叶莉");
		}
	}
}
class EtoakThread extends Thread{
	@Override
	public void run(){
		try{
			Thread.sleep(5000);
		}catch(Exception e){
			e.printStackTrace();
		}
		while(true){
			System.out.println("姚明");
		}
	}
}


//即便是单线程场景下 sleep()同样非常重要
public class TestSleepPlus{
	public static void main(String[] args)throws Exception{
		for(int i = 0;i<=100;i++){
			Thread.sleep(100);
			System.out.print("\r已经完成"+i+"%");
		}
	}
}


//sleep也可以用于排序,数字越小,睡得时间越短,先睡醒的数字,肯定最小
public class TestSleepSort{
	public static void main(String[] args){
		int[] data = new int[]{59,75,21,37,87};
		for(int x : data){
			SortThread st = new SortThread(x);
			st.start();
		}
	}
}
class SortThread extends Thread{
	int num;
	public SortThread(int num){
		this.num = num;
	}
	@Override
	public void run(){
		try{
			Thread.sleep(num);
		}catch(Exception e){
			e.printStackTrace();
		}
		System.out.println(num);
	}
}
  1. static yield()方法:提示线程调度器将CPU资源让给其他具有相同或更高优先级的线程。让当前(写在谁里面,谁就yield)线程放弃时间片直接返回就绪,以下是示例代码:
Thread.yield(); // 提示线程调度器让出CPU资源给其他线程
  1. join()方法:等待调用该方法的线程执行完毕。让当前线程邀请调用方法的线程优先执行,在被邀请的线程执行结束之前,当前线程将一直阻塞 不再继续执行,需要两个线程。以下是示例代码:
public class TestJoin{
	public static void main(String[] args)throws Exception{
		EtoakThread et = new EtoakThread();
		et.start();

		//当前线程(主线程) 邀请 调用方法的线程(et) 优先执行
		et.join();

		for(int i = 0;i<1000;i++){
			System.out.println("分析和处理数据的操作");
		}
	}
}
class EtoakThread extends Thread{
	@Override
	public void run(){
		for(int i = 0;i<1000;i++){
			System.out.println("生产和采集数据的操作");
		}
	}
}
  1. setName()方法和getName()方法:用于设置和获取线程的名称,它是继承的Thread的方法,以下是示例代码:
Thread thread = new Thread();
thread.setName("MyThread"); // 设置线程名称为"MyThread"

String threadName = thread.getName(); // 获取线程名称
  1. interrupt()方法:中断线程,给线程发送中断信号,需要两个线程。哪个对象调用interrupt哪个对象被中断。以下是示例代码:
public class TestInterrupt{
	public static void main(String[] args)throws Exception{
		EtoakThread et = new EtoakThread();
		et.start();

		Thread.sleep(3000);//主线程睡3秒

		//当前线程(主线程) 主动出手 中断 et线程的阻塞状态
		et.interrupt();
	}
}
class EtoakThread extends Thread{
	@Override
	public void run(){

		try{
			Thread.sleep(99999999999999L);
		}catch(Exception e){
			e.printStackTrace();
		}
		System.out.println("啊!神清气爽啊!");
	}
}
  1. setDaemon()方法:设置线程是否为守护线程。守护线程是一种特殊类型的线程,它主要用于为其他线程提供服务,当所有非守护线程执行完毕时,守护线程会自动退出。守护线程是为其它线程提供服务的,当程序当中只剩下守护线程的时候,守护线程会自行消亡,以下是示例代码:
/*
	*: 守护线程通常都是无限循环 以防止其过早消亡
	*: 设置线程成为守护线程的操作必须早于线程自身的start()
	*: 守护线程应当具有较低的优先级别 以防止其与核心业务争抢时间片
*/
public class TestSetDaemon{
	public static void main(String[] args){
		GYJJ gy = new GYJJ();

		//*: 设置线程成为守护线程的操作必须早于线程自身的start()
		gy.setDaemon(true);
		gy.start();

		//*: 守护线程应当具有较低的优先级别 以防止其与核心业务争抢时间片
		gy.setPriority(1);

		for(int i = 0;i<100;i++){
			System.out.println("西天取经上大路 一走就是几万里");
		}


	}
}
class GYJJ extends Thread{
	@Override
	public void run(){
		//*: 守护线程通常都是无限循环 以防止其过早消亡
		while(true){
			System.out.println("你这泼猴儿..");
		}
	}
}
  1. static currentThread()方法:获取当前正在执行的线程对象。以下是示例代码:
Thread currentThread = Thread.currentThread(); // 获取当前线程对象
1.用于在主方法当中得到主线程对象...
2.在run()调用的其它方法中 得到当前线程是谁
X.它绝对不该直接出现在run()当中,因为得到的线程对象 就是this...
  1. static activeCount()方法:获取当前线程组中活跃线程的数量。活跃:就绪 + 运行 + 阻塞,它能够帮程序员统计服务器在线用户数量

    以下是示例代码:

ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
int activeCount = currentGroup.activeCount(); // 获取当前线程组中活跃线程数量

八、线程中的几个重点问题

线程章节所有的静态方法,不要关注谁调用,而要关注调用出现在谁的线程体**【别看谁点 看写哪】**

静态方法
static sleep()方法
static yield()方法
static currentThread()方法
static activeCount()方法

线程章节所有涉及主动进入阻塞状态的方法,都必须进行异常处理 ,因为它们都有throws InterruptedException声明
而InterruptedException是非运行时异常

主动进入阻塞状态的方法:
join  
sleep
await()->CountDownLatch
wait()
await()->ReentrantLock->Condition

九、倒计时门闩CountDownLatch

  • 主动制造阻塞 直到阻塞条件被解除
	CountDownLatch cdl = new CountDownLatch(3);//3代表当前的线程数

	*: cdl.await() : 主动制造阻塞 直到门闩都被打开
	*: cdl.countDown() : 打开一个门闩

十、并发错误

根本原因:多个线程共享操作同一份数据
直接原因:线程体当中连续的多行语句未必能够连续执行,例如wait( ) 和 notify()方法
导火索:时间片突然耗尽

多个线程共享操作同一份数据的时候,线程体当中连续的多行操作未必能够连续执行,很可能操作只完成了一部分,时间片就突然耗尽
而另一个线程抢到了时间片,直接访问了操作不完整的数据,这就导致了并发错误

*: 编译不报错 运行没异常 就是数据全是错的

十一、如何解决并发错误

11.1 synchronized

11.1.1 什么是synchronized

  • 互斥锁 = 互斥锁标记 = 锁标记 = 锁旗标 = 监视器 = Monitor

11.1.2 synchronized如何使用

  • 修饰代码块

    synchronized(临界资源){
        //需要连续执行的操作1;
        //需要连续执行的操作2;
        //...;
    }
    //多个线程共享的那个对象 = 临界资源,本质是一个对象
    
  • 修饰方法

    public synchronized void add(Object obj){
        //需要连续执行的操作1;
        //需要连续执行的操作2;
        //...;
    }
    //这等价于从方法的第一行到最后一行统统加锁,而且是对调用方法的当前对象进行加锁,本质还是对调用该方法的对象进行加锁
    

11.1.3 synchronized注意事宜

  1. Vector、Hashtable、StringBuffer,它们之所以线程安全 ,是因为底层的方法大量的使用了synchronized修饰符

  2. 单例模式之懒汉式 需要synchronized修饰那个getter~

    在单例模式中,懒汉式指的是在首次调用获取实例的方法时才实例化对象。在多线程环境下,如果不对获取实例的方法进行同步控制,可能会导致多个线程同时进入该方法并创建多个实例,从而违背了单例模式的要求。因此,为了保证在多线程环境下的线程安全性,我们需要使用synchronized关键字来修饰获取实例的方法(通常是getter方法)。这样,当一个线程进入该方法时,其他线程将被阻塞,直到进入的线程完成实例的创建并返回后,其他线程才能继续执行。这样就能保证只有一个实例被创建。

    public class TestSingleton{
    	public static void main(String[] args){
    
    	}
    }
    class Sun{
    	private Sun(){}
    	private static Sun only;//懒汉式
    	public static synchronized Sun getOnly(){
    		if(only == null)//如果没有对象,造一个新对象,如果存在对象,直接返回这个对象
    			only = new Sun();
    		return only;
    	}
    }
    
  3. synchronized隔代丢失,继承父类synchronized修饰的方法,需要在子类重新覆盖并且加synchronized修饰符,synchronized修饰符不会由父类继承给子类,每次需要重新加synchronized修饰符

11.2 ReentrantLock

11.2.1 什么是ReentrantLock

​ ReentrantLock是Java中的一个线程同步机制,它提供了比synchronized更灵活和强大的功能。ReentrantLock是可重入锁,这意味着一个线程可以多次获得同一个锁。使用ReentrantLock需要显式地获取锁和释放锁。

11.2.2 导入ReentrantLock

java.util.concurrent.locks.ReentrantLock

11.2.3 ReentrantLock中提供的方法

​ lock( ) 加锁

​ unlock( ) 释放

11.2.4 ReentrantLock中的公平锁和非公平锁

公平锁:按照线程请求锁的顺序来获取锁的权限。当有多个线程竞争锁时,先到的线程会先获得锁的权限,后到的线程会进入等待队列。
非公平锁:线程在尝试获取锁时,如果当前锁没有被其他线程占用,则直接获取锁的权限。如果锁被其他线程占用,则进入等待队列,但不保证按照请求的顺序来获取锁。
ReentrantLock fairLock = new ReentrantLock(true); // 创建一个公平锁

11.2.5 ReentrantLock如何创建

//导包
import java.util.concurrent.locks.ReentrantLock;
//创建lock对象
Lock lock = new ReentrantLock();
//调用lock的方法
lock.lock();
lock.unlock();

十二、死锁

12.1什么是死锁

​ 互斥锁标记使用过多或者使用不当,就会造成多个线程相互持有对方想要申请的资源,不释放的情况下,又去申请对方已经持有的资源,从而双双进入对方已经持有的资源的锁池当中,产生永久的阻塞,通俗一点,锁标记如果过多,就会出现线程等待其他线程释放锁标记,而又都不释放自己的锁标记供其他线程运行的状况。就是死锁。著名死锁的案例,中美科学家联合国饿死事件 & 泉城路奔驰宝马事件

12.2如何解决死锁问题

​ 一块空间:对象的等待池

​ 三个方法:wait() / notify() / notifyAll()

wait() : 让当前线程放弃已经持有的锁标记,并且进入调用方法的那个对象的等待池当中阻塞,并且wait()以下的代码不执行,直到被另一个线程调用notify()唤醒

notify() : 从调用方法的那个对象的等待池当中,随机的唤醒一个线程

notifyAll() : 从调用方法的那个对象的等待池当中,唤醒所有阻塞的线程

注意这三个方法不是线程类的方法 ,而是Object类的方法,因为Java当中每个对象都有等待池,每个对象都可能需要操作等待池,所以这三个方法被直接定义到Object类当中,需要类名去调用

注意这三个方法都必须在已经持有锁标记的前提下才能使用,否则不但操作失败,还会触发运行时异常,IllegalMonitorStateException
所以wait() notify() notifyAll()必然出现在synchronized的	{ }  当中

十三、如何让两个线程交替进行(synchronized版本)

public class TestSwitchThread{
	public static void main(String[] args){
		RightThread rt = new RightThread();//创建RightThread对象,RightThread线程进入新生状态
		LeftThread lt = new LeftThread(rt);//创建LeftThread对象,将RightThread作为参数传入LeftThread中,LeftThread线程进入新生状态
		lt.start();//启动LeftThread线程,进入就绪状态,此时主线程消亡
	}
}
class X{
	static Object obj = new Object();//创建一个静态的临界资源,即两个线程的共用的对象
}
class LeftThread extends Thread{
	RightThread rt;
	public LeftThread(RightThread rt){
		this.rt = rt;
	}
	@Override
	public void run(){
		synchronized(X.obj){//X.obj为临界资源,LeftThread线程进入运行状态,获得锁标记,进入内部
			rt.start();//第一步:启动RightThread线程,RightThread线程进入就绪状态,然后进入就绪状态
			for(int i = 0;i<1000;i++){
				System.out.println("左脚");
				try{X.obj.wait();}catch(Exception e){e.printStackTrace();}
                  /*第二步:让LeftThread线程上交锁标记,LeftThread线程然后进入obj的等待池,
                  此时代码不再往下顺序执行,因为此时的LeftThread线程处于阻塞状态*/
				X.obj.notify();//第六步:LeftThread线程从获得从RightThread线程那里上交的锁标记,进入就绪状态,然后进入运行状态,然后唤醒RightThread线程,RightThread线程从等待池进入锁池,然后LeftThread线程执行i++,进入下一轮循环,再次上交锁标记,然后进入obj的等待池,此时RightThread线程获得锁标记,进入就绪状态,然后进入运行状态,再次开始RightThread线程的for循环,以此类推。
			}
		}
	}
}
class RightThread extends Thread{
	@Override
	public void run(){
		synchronized(X.obj){//第三步:当LeftThread线程进入obj的等待池,RightThread线程获得锁标记,进入循环
			for(int i = 0;i<1000;i++){
				System.out.println("				右脚");
				X.obj.notify();//第四步:RightThread线程唤醒LeftThread线程,LeftThread线程从obj的等待池塘进入obj的锁池
				try{X.obj.wait();}catch(Exception e){e.printStackTrace();}//第五步:RightThread线程上交锁标记,进入obj的等待池,进入阻塞状态,此时进入第25行
			}
		}
	}
}

十四、如何让两个线程交替进行(ReentrantLock版本)

对应关系一览表
synchronized等待池wait()notify()notifyAll()
ReentrantLockConditionawait()signal()signalAll()
import java.util.concurrent.locks.*;//第一步:导包
public class TestSwitchThreadWithLock{
	public static void main(String[] args){
		RightThread rt = new RightThread();
		LeftThread lt = new LeftThread(rt);
		lt.start();
	}
}
class X{
	static Lock lock = new ReentrantLock();//第二步:创建锁对象
	static Condition c = lock.newCondition();//第三步:利用锁创建一个阻塞条件
}
class LeftThread extends Thread{
	RightThread rt;
	public LeftThread(RightThread rt){
		this.rt = rt;
	}
	@Override
	public void run(){
			X.lock.lock();//加锁
			rt.start();
			for(int i = 0;i<1000;i++){
				System.out.println("左脚");
				try{X.c.await();}catch(Exception e){e.printStackTrace();}
				X.c.signal();
			}
			X.lock.unlock();//解锁
	}
}
class RightThread extends Thread{
	@Override
	public void run(){
			X.lock.lock();//加锁
			for(int i = 0;i<1000;i++){
				System.out.println("				右脚");
				X.c.signal();
				try{X.c.await();}catch(Exception e){e.printStackTrace();}
			}
			X.lock.unlock();//解锁
	}
}

十五、等待池和锁池的区别

Java当中每个对象都有:属性 方法 互斥锁标记 锁池 等待池

其中锁池和等待池 是每个对象都有一份的空间用于存放线程任务的...

锁池:存放那些想要拿到对象锁标记 但是还没成功的线程

等待池:存放那些原本已经拿到对象锁标记,为了避免跟其它线程相互制约,又发扬风格主动释放资源的线程

等待池和锁池的区别主要如下几个方面

1.进入的时候是否需要释放资源 锁池:不需要释放 等待池:需要先释放资源,释放锁标记

2.离开的时候是否需要调用方法 锁池:不需要调用方法 等待池:必须有其它线程调用notify() / notifyAll(),由等待池进入锁池

3.离开之后去往什么状态 锁池->离开锁池 前往就绪状态,然后争抢时间片,如果成功抢到,进入运行状态 等待池->离开等待池 前往锁池!

十六、线程的一生

线程的一生

十七、什么是线程池

​ 是一种标准的资源池模式,所谓资源池指的是在用户出现之前提前预留活跃资源,从而在用户出现的第一时间,直接满足用户对资源的需求,并且将资源的创建和销毁都委托给资源池完成,从而优化用户体验

十八、如何创建线程池

第一步:导包

import java.util.concurrent.*;

第二步:创建线程池对象

  • ExecutorService es = Executors.newFixedThreadPool(2);修复后可重用的线程池
  • ExecutorService es = Executors.newCachedThreadPool();缓存机制的线程池 1Min = 60S后关闭
  • ExecutorService es = Executors.newSingleThreadExecutor();单一实例的线程池(单线程的线程池代表只有一个工作线程的线程池。它可以按顺序执行被提交的任务,每次只有一个任务在执行,其他任务会按照提交顺序进行排队等待)

第三步:将某个线程对象放入线程池中

es.submit(某个线程对象);

第四步:将线程池关闭

es.shutdown();

shutdown() 和 shutdownNow()的异同
一、相同点
shutdown() 和 shutdownNow() 都能够禁止新任务再次提交
shutdown() 和 shutdownNow() 都不能停止执行中的线程
二、不同点
它们的区别在于那些已经提交上去,但是还没开始执行的线程任务

	shutdown() 则所有线程任务都会执行完

	shutdownNow() 会直接将排队线程任务退回

十九、核心类库当中Sun公司官方提供的常用的线程池种类有哪些

  1. 固定大小修复后可重用的 newFixedThreadPool(指定数量)
  2. 缓存机制的 newCachedThreadPool()
  3. 单一实例的 newSingleThreadExecutor()

二十、自定义线程池

自定义线程池的参数

  • 简单来讲

    1.线程池当中核心线程数量(无论有没有任务 都得活着)
    2.线程池当中最大线程数量(全职+兼职的总和)
    3.KeepAliveTime => 保持活着的时间(数词)
    4.TimeUnit => 时间单位(量词)
    5.一个队列 用于存放到达最大之后还提交的任务
    
  • 详细来讲

    1. 核心线程数(Core Pool Size):表示线程池中保持活动状态的最小线程数。即使线程池中没有任务需要执行,核心线程也会一直保持不变。核心线程在处理任务时不会被回收。
    
    2. 最大线程数(Maximum Pool Size):表示线程池中允许存在的最大线程数。当核心线程都在忙于执行任务时,增加新的任务会创建新的线程,直到达到最大线程数。超过最大线程数的任务可能会等待或被返回。
    
    3. 线程存活时间(Keep Alive Time):表示当线程池中的线程数量超过核心线程数时,多余的空闲线程在被回收之前等待新任务的最长时间。
    
    4. 线程存活时间的时间单位(TimeUnit):表示线程存活时间(Keep Alive Time)的单位,例如年月日,时分秒
    
    5. 任务队列(Blocking Queue):表示存储待执行任务的队列。当线程池中的所有线程都在执行任务时,新的任务会被放入任务队列中,等待有空闲线程时被取出执行。
    

二十一、知识拓展

CopyOnWriteArrayList

CopyOnWriteArrayList是Java集合框架中的一个类,它实现了List接口,可以用于存储对象的有序集合。与普通的ArrayList不同,CopyOnWriteArrayList采用了一种特殊的机制来实现并发访问的线程安全。

CopyOnWriteArrayList的名字中的"CopyOnWrite"表示在对集合进行修改时,它会创建一个集合副本,并对副本执行修改操作。原始集合本身不会被修改,这样可以避免并发访问时的数据冲突。当对副本进行修改完成后,CopyOnWriteArrayList会将修改后的副本替换原始集合,以保持线程安全。

由于每次修改操作都需要复制整个集合,所以CopyOnWriteArrayList的写操作代价较高,适合在读操作频繁、写操作较少的场景中使用。它适用于多线程环境下的读多写少的情况,例如缓存、观察者模式等。

需要注意的是,由于CopyOnWriteArrayList的写操作可能会覆盖读操作所看到的内容,因此在使用时需要注意并发正确性。此外,CopyOnWriteArrayList不支持使用迭代器执行修改操作,否则会抛出UnsupportedOperationException异常。

下面是一个简单的示例代码,展示了CopyOnWriteArrayList的基本用法:

import java.util.concurrent.CopyOnWriteArrayList;

public class Main {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        list.add("apple");
        list.add("banana");
        list.add("orange");

        // 遍历集合
        for (String item : list) {
            System.out.println(item);
        }

        // 修改集合
        list.add("grape");

        // 再次遍历集合
        for (String item : list) {
            System.out.println(item);
        }
    }
}

输出结果:

apple
banana
orange
apple
banana
orange
grape

在上述示例中,我们首先创建了一个CopyOnWriteArrayList对象,并向其中添加了三个元素。然后,我们通过迭代器遍历了集合并输出了元素。接着,我们向集合中添加了一个新的元素,并再次遍历集合,验证了新元素的添加。

当使用CopyOnWriteArrayList的迭代器进行修改操作时,会抛出UnsupportedOperationException异常。下面是一个简单的示例代码展示了这个异常的产生:

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

public class Main {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        list.add("apple");
        list.add("banana");
        list.add("orange");

        // 使用迭代器遍历集合
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            System.out.println(item);

            // 修改元素
            iterator.remove(); // 这里会抛出UnsupportedOperationException异常
        }
    }
}

在上述代码中,我们首先创建了一个CopyOnWriteArrayList对象,并向其中添加了三个元素。然后,我们使用迭代器遍历集合并输出元素。在遍历过程中,当我们尝试使用迭代器的remove()方法进行修改操作时,会抛出UnsupportedOperationException异常。

异常输出:

apple
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1174)
	at Main.main(Main.java:13)

这是因为CopyOnWriteArrayList的迭代器是只读的,不支持修改操作。如果需要对集合进行修改,应该使用集合自身的方法,例如add()remove()等。

CopyOnWriteArraySet

CopyOnWriteArraySet是Java集合框架中的一个类,它是Set接口的线程安全实现之一。它基于CopyOnWriteArrayList实现,为存储唯一元素的无序集合提供了线程安全的操作。

CopyOnWriteArraySet的特点和用法与CopyOnWriteArrayList类似,它在每次修改集合时都会创建一个副本,并在副本上执行修改操作。原始集合本身是不可变的,这样可以避免并发修改时发生数据冲突。

由于每次修改操作都涉及复制整个集合,因此CopyOnWriteArraySet适用于读操作频繁、写操作较少的场景。它提供了线程安全的遍历和获取操作,并保证不会发生并发修改异常。

以下是一个简单的示例代码,展示了CopyOnWriteArraySet的基本用法:

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArraySet;

public class Main {
    public static void main(String[] args) {
        CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();

        set.add("apple");
        set.add("banana");
        set.add("orange");

        // 遍历集合
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            System.out.println(item);
        }

        // 修改集合
        set.add("grape");

        // 再次遍历集合
        for (String item : set) {
            System.out.println(item);
        }
    }
}

输出结果:

apple
banana
orange
apple
banana
orange
grape

在上述示例中,我们首先创建了一个CopyOnWriteArraySet对象,并向其中添加了三个元素。然后,我们使用迭代器遍历集合并输出元素。接着,我们向集合中添加了一个新的元素,并再次使用增强型for循环遍历集合,验证了新元素的添加。

需要注意的是,CopyOnWriteArraySet是通过复制整个集合来实现线程安全,因此在元素数量较大时,每次修改操作都会有一定的性能开销。因此,它适用于对元素数量不是特别多、读操作频繁、写操作较少的场景。

使用QTimer对象代替QBasicTimer对象,修改程序class MyWindow(QWidget): def init(self): super().init() self.thread_list = [] self.color_photo_dir = os.path.join(os.getcwd(), "color_photos") self.depth_photo_dir = os.path.join(os.getcwd(), "depth_photos") self.image_thread = None self.saved_color_photos = 0 # 定义 saved_color_photos 属性 self.saved_depth_photos = 0 # 定义 saved_depth_photos 属性 self.init_ui() def init_ui(self): self.ui = uic.loadUi("C:/Users/wyt/Desktop/D405界面/intelrealsense1.ui") self.open_btn = self.ui.pushButton self.color_image_chose_btn = self.ui.pushButton_3 self.depth_image_chose_btn = self.ui.pushButton_4 self.open_btn.clicked.connect(self.open) self.color_image_chose_btn.clicked.connect(lambda: self.chose_dir(self.ui.lineEdit, "color")) self.depth_image_chose_btn.clicked.connect(lambda: self.chose_dir(self.ui.lineEdit_2, "depth")) def open(self): self.profile = self.pipeline.start(self.config) self.is_camera_opened = True self.label.setText('相机已打开') self.label.setStyleSheet('color:green') self.open_btn.setEnabled(False) self.close_btn.setEnabled(True) self.image_thread = ImageThread(self.pipeline, self.color_label, self.depth_label, self.interval, self.color_photo_dir, self.depth_photo_dir, self._dgl) self.image_thread.saved_color_photos_signal.connect(self.update_saved_color_photos_label) self.image_thread.saved_depth_photos_signal.connect(self.update_saved_depth_photos_label) self.image_thread.start() def chose_dir(self, line_edit, button_type): my_thread = MyThread(line_edit, button_type) my_thread.finished_signal.connect(self.update_line_edit) self.thread_list.append(my_thread) my_thread.start()
05-26
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值