第二周3天13章线程的同步与TimeTimer

本文详细解析了Java线程同步的关键方法,包括wait(), notify(), notifyAll(),并介绍了如何利用Timer和TimerTask实现定时任务。通过实例代码展示了schedule()方法的多种使用方式,包括设置执行时间、周期性和延迟执行。

13.4线程的同步与定时器

线程同步通信
为避免死锁,就应该让线程在进入阻塞状态时尽量释放其锁定的资源,以为其他的线程提供运行的机会,Object类中定义了几个有用的方法:wait()、notify()、notifyAll()。

wait():被锁定的对象可以调用wait()方法,这将导致当前线程被阻塞并释放该对象的互斥锁,即解除了wait()方法当前对象的锁定状态,其他的线程就有机会访问该对象。

notify():唤醒调用wait()方法后被阻塞的线程。每次运行该方法只能唤醒一个线程。

notifyAll():唤醒所有调用wait()方法被阻塞的线程。

Timer和TimerTask

java.util包中的Timer和TimerTask类也可实现多线程

Timer类实现的是类似闹钟的功能,也就是定时或者每隔一定时间间隔触发一次线程。

TimerTask类是一个抽象类,该类实现了Runnable接口,具备多线程的能力。

通过继承TimerTask类创建子类,使该子类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行。

schedule()方法

public void schedule(TimerTask task,Date time):该方法的作用是在到达time指定的时间或已经超过该时间时执行线程task。
Date d = new Date(2009-1900,10-1,1,10,0,0);
t. schedule(task,d);

public void schedule(TimerTask task, Date firstTime, long period):在时间到达firstTime开始,每隔period毫秒就启动一次task指定的线程,这种方式会重复启动线程。
Date d = new Date(2009-1900,10-1,1,10,0,0);
t. schedule(task,d,20000);

public void schedule(TimerTask task,long delay)在执行schedule方法delay毫秒以后启动线程task。
t.schedule(task,1000);//在执行该行启动代码1000毫秒后启动一次线程task

public void schedule(TimerTask task,long delay,long period):在执行schedule方法delay毫秒以后启动线程task,然后每隔period毫秒重复启动线程task。

<think>我们有两个线程需要设计: 1. 第一个线程:每5分钟调用一次接口,但需要卡整点(即0分钟、5分钟、10分钟...),获取数据并存储到本地JSON文件。 2. 第二个线程:在第一个线程执行完后(即存储完JSON文件后),从该JSON文件读取数据,处理数据结构,然后插入到Oracle数据库中。 注意:第一个线程的整点调用意味着我们需要对齐到5分钟的整数倍时间点(例如,当前时间是12:03,那么下一次调用应该在12:05)。 设计思路: 第一个线程我们可以使用定时器,但是要计算到下一个整5分钟的时间点,然后每隔5分钟执行一次。 第二个线程需要等待第一个线程完成文件写入后再读取,因此我们可以使用线程同步机制,例如事件(Event)或者条件(Condition)。但这里更简单的做法是:第一个线程在完成写入后,触发第二个线程的执行。由于第二个线程只需要在第一个线程写入完成后执行,我们可以将第二个线程设计为在第一个线程的存储操作完成后立即启动。但是考虑到两个线程是独立的,且第二个线程可能需要处理多个文件(如果第一个线程多次执行后产生了多个文件),我们可以使用队列(Queue)来传递文件名。 然而,题目要求两个线程,所以我们分别创建两个线程,并让它们按照条件协调。 另一种设计:我们可以让第一个线程在每次存储完JSON文件后,将一个消息(比如文件名)放入一个队列中,第二个线程则从这个队列中获取消息,然后进行读取和插入数据库的操作。 但是注意,第二个线程的执行不能阻塞第一个线程的定时调用,所以我们可以使用一个生产者-消费者模型。 具体步骤: 1. 创建一个队列(例如使用queue.Queue),用于存放第一个线程存储的JSON文件路径。 2. 第一个线程(生产者)每5分钟调用接口,获取数据,然后将数据存储为JSON文件(文件名可以包含时间戳以确保唯一性),并将该文件路径放入队列。 3. 第二个线程(消费者)循环运行,从队列中取出文件路径,读取文件,处理数据,然后插入Oracle数据库。如果队列为空,则等待。 但是,题目要求第二个线程在第一个线程执行完后执行,这意味着每次第一个线程完成一次操作,第二个线程就立即执行一次。使用队列可以实现,因为第一个线程放一个文件路径,第二个线程就会取出来处理。 另外,关于定时调用,我们可以使用sched模块或者threading.Timer,但考虑到需要循环定时,我们可以使用一个循环,每次计算下一次执行的时间(下一个5分钟整点),然后等待到那个时间点再执行。 但是,使用Timer可能会导致时间漂移,我们可以每次执行完后再设置下一次的Timer。另外,我们可以使用APScheduler等第三方库,但这里要求纯Python实现。 为了简单,我们使用循环和time.sleep,计算到下一个整5分钟的时间点的秒数,然后sleep那么长时间,然后执行任务。 步骤: 1. 计算当前时间到下一个整5分钟的时间点的等待时间(秒)。 2. 等待该时间。 3. 执行任务:调用接口,存储JSON,将文件路径放入队列。 4. 循环。 第二个线程则是一个循环,从队列中取文件路径,然后处理。 注意:两个线程都需要设置为守护线程或者在主程序中适当终止。 下面给出一个示例代码框架: 注意:这里假设我们使用两个线程,一个为定时获取并存储的线程,另一个为处理数据的线程。我们使用一个队列来传递文件路径。 由于涉及Oracle数据库,这里不详细写数据库插入部分,而是用伪代码表示。 另外,调用接口和存储JSON的具体实现需要根据接口情况编写,这里用伪代码表示。 代码结构: - 导入必要的模块:threading, queue, time, json, requests(用于调用接口), cx_Oracle(用于连接Oracle)等。 - 定义全局队列:file_queue = queue.Queue() - 第一个线程函数:thread_fetch_and_save while True: # 计算到下一个整5分钟的时间点的秒数 # 等待 # 调用接口,获取数据 # 将数据保存为JSON文件,生成文件名(包含时间戳) # 将文件名放入队列 - 第二个线程函数:thread_process_and_insert while True: # 从队列中取出文件路径 # 读取JSON文件 # 处理数据结构 # 插入Oracle数据库 # 处理完后,可以选择删除文件或保留(根据需求) - 创建并启动两个线程 但是,注意第一个线程中如何计算到下一个整5分钟的时间点? 我们可以这样计算: 获取当前时间戳,转换为时间元组,然后计算下一个整5分钟的时间点。 例如: current_time = time.time() local_time = time.localtime(current_time) # 计算当前分钟数在5分钟间隔中的余数 current_minute = local_time.tm_min # 下一个5分钟整点分钟数:当前分钟数加上5减去余数,再对60取模?注意进位 # 其实我们可以计算下一个5分钟整点的时间戳: # 首先计算当前分钟所属的5分钟段的开始时间(向下取整),然后再加上5分钟,得到下一个整点。 # 但题目要求是卡整点,即0,5,10,...,55分钟。 具体计算: # 当前分钟数 current_minute = local_time.tm_min # 当前分钟数对5取余 remainder = current_minute % 5 # 如果余数为0,说明正好是整点,那么下一个整点就是当前分钟+5分钟(注意分钟超过60则小时加1) # 如果余数不为0,则下一个整点是当前分钟 + (5 - remainder) next_minute = current_minute - remainder + 5 # 这样计算,如果current_minute=7,则next_minute=10 # 但是如果当前分钟是56,那么next_minute=60,此时分钟变成0,小时要加1 因此,我们可以构造下一个整点的时间元组,然后转换为时间戳。 步骤: next_time = local_time # 先复制当前时间元组(不可直接修改,因为time.struct_time不可变) # 所以我们可以构造一个新的时间元组,将分钟设置为next_minute,秒和微秒设置为0 # 注意:如果next_minute为60,那么分钟设为0,小时+1 if next_minute == 60: next_hour = local_time.tm_hour + 1 next_minute = 0 # 如果小时超过23,则要调整日期 # 这里我们可以用mktime来处理,它会自动处理日期进位 # 构造新的时间元组 next_time_tuple = (local_time.tm_year, local_time.tm_mon, local_time.tm_mday, next_hour, next_minute, 0, 0, 0, -1) else: next_time_tuple = (local_time.tm_year, local_time.tm_mon, local_time.tm_mday, local_time.tm_hour, next_minute, 0, 0, 0, -1) 然后转换成时间戳:next_timestamp = time.mktime(next_time_tuple) 等待时间 = next_timestamp - current_time 但是,上面的构造可能会出现问题,因为mktime要求使用本地时间,且元组中的星期和一年中的第几可以设为0,然后mktime会计算。另外,我们可以使用datetime模块来简化。 为了简化,我们使用datetime模块(推荐): from datetime import datetime, timedelta now = datetime.now() # 计算当前分钟所属的5分钟段的下一个整点 # 当前分钟数 current_minute = now.minute # 计算下一个5分钟整点的分钟数 next_minute = (current_minute // 5 + 1) * 5 if next_minute == 60: # 下一个整点是下一个小时的0分钟 next_time = now.replace(hour=now.hour+1, minute=0, second=0, microsecond=0) else: next_time = now.replace(minute=next_minute, second=0, microsecond=0) # 如果当前时间已经超过这个小时的55分钟,那么next_minute=60,就会进位到小时,然后分钟为0 # 但是有一种情况:比如当前时间是23:58,那么下一个整点应该是第二的00:00 # 计算等待时间 wait_seconds = (next_time - now).total_seconds() 然后我们time.sleep(wait_seconds) 注意:这里计算出的等待时间可能是负数吗?不会,因为next_time一定大于now(即使跨,datetime也能正确处理)。 另外,由于时间的计算和等待需要时间,所以实际执行时间可能会有很小的误差,但可以接受。 现在我们开始写代码框架: 注意:由于需要长时间运行,我们需要考虑异常处理,避免线程崩溃。 以下代码为示例框架,部分地方用伪代码表示。 </think>以下是Python实现两个线程的代码,满足定时调用接口存储JSON和读取处理插入数据库的需求: ```python import threading import time import json import requests import cx_Oracle from datetime import datetime, timedelta # 配置文件路径 JSON_FILE = "data.json" # 数据库配置 DB_CONFIG = { &#39;user&#39;: &#39;your_username&#39;, &#39;password&#39;: &#39;your_password&#39;, &#39;dsn&#39;: &#39;your_dsn&#39; } def calculate_wait_time(): """计算到下一个整5分钟点的等待时间(秒)""" now = datetime.now() next_minute = ((now.minute // 5) + 1) * 5 if next_minute >= 60: next_time = now.replace(hour=now.hour+1, minute=0, second=0, microsecond=0) else: next_time = now.replace(minute=next_minute, second=0, microsecond=0) wait_seconds = (next_time - now).total_seconds() return max(0, wait_seconds) # 防止负值 def fetch_data(): """模拟API调用获取数据""" # 替换实际调用接口的代码 print(f"[{datetime.now()}] 调用接口获取数据...") return {"timestamp": str(datetime.now()), "values": [1, 2, 3]} def save_to_json(data): """保存数据到JSON文件""" with open(JSON_FILE, &#39;w&#39;) as f: json.dump(data, f) print(f"[{datetime.now()}] 数据已保存到 {JSON_FILE}") def process_and_insert(): """处理JSON数据并插入数据库""" try: with open(JSON_FILE, &#39;r&#39;) as f: data = json.load(f) # 数据结构处理(示例) processed = { &#39;time&#39;: data[&#39;timestamp&#39;], &#39;sum&#39;: sum(data[&#39;values&#39;]) } # 数据库插入 with cx_Oracle.connect(**DB_CONFIG) as conn: cursor = conn.cursor() cursor.execute(""" INSERT INTO sensor_data (timestamp, total) VALUES (TO_TIMESTAMP(:time, &#39;YYYY-MM-DD HH24:MI:SS.FF&#39;), :sum) """, processed) conn.commit() print(f"[{datetime.now()}] 数据已插入数据库") except Exception as e: print(f"处理数据错误: {str(e)}") def api_thread(): """线程1:定时获取数据并存储JSON""" while True: wait_sec = calculate_wait_time() time.sleep(wait_sec) data = fetch_data() save_to_json(data) # 触发数据库线程 db_event.set() def db_thread(): """线程2:等待JSON更新后处理数据""" while True: db_event.wait() # 等待事件触发 process_and_insert() db_event.clear() # 重置事件 # 创建线程同步事件 db_event = threading.Event() # 启动线程 t1 = threading.Thread(target=api_thread, daemon=True) t2 = threading.Thread(target=db_thread, daemon=True) t1.start() t2.start() # 主线程保持运行 try: while True: time.sleep(3600) except KeyboardInterrupt: print("\n程序已终止") ``` ### 关键说明: 1. **定时控制**: - 使用`calculate_wait_time()`精确计算到下一个5分钟整点(如00、05、10...) - 跨小时处理:当分钟≥55时自动跳至下一小时00分 2. **线程同步**: - 通过`threading.Event`实现线程间通信 - API线程完成存储后触发事件,DB线程收到事件后执行 3. **数据库操作**: - 使用`cx_Oracle`连接Oracle数据库 - 示例将JSON中的数值求和后插入`sensor_data`表 4. **错误处理**: - DB线程包含异常捕获防止崩溃 - 守护线程确保主程序退出时自动终止 ### 使用前需配置: 1. 修改`DB_CONFIG`中的数据库连接参数 2. 创建数据库表(示例): ```sql CREATE TABLE sensor_data ( id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, timestamp TIMESTAMP, total NUMBER ); ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值