Android 同步代码块,synchronized加锁

本文通过车站售票的例子,探讨Android中如何使用synchronized关键字实现线程同步,确保同一时间只有一个线程能够执行特定代码,防止数据冲突。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


以车站售票为例,多个线程不能同时买一张票,所以把买票这个代码块加锁,等待前面线程买好了,后面线程才能进入买票


class BuyTicket implements Runnable {

	private int num = 10 ;  //有10张票
	
	@Override 
	public void run() {
		while (num>0){
		
			System.out.println(Thread.currentThread().getName() + "到后面排队 \n");
			
			synchronized(this){  // 锁住买票时不能让别人插队
			
				String name = Thread.currentThread().getName();
				if(num<=0){
					System.out.println("售票员对" + name +"说没票了,散了吧。");
					return;
				}
				try { 
					System.out.println( name + "正在买票... \n");
					Thread.sleep(3000); 
				} catch (InterruptedException e) {
					e.printStackTrace(); 
				}
				System.out.print( name + "买到了票" + num + ",下一位,");
				num--; // 卖掉了一张
			}
		}
		// 买到一张票后继续到后面排队,直到没票
		System.out.println("\n\n没票了 \n");
	}
	
}

public class test2 {

	public static void main ( String[] args ){
		
		BuyTicket bt = new BuyTicket(); // 新开个售票点
	
		Thread xiaoMing = new Thread(bt); // 小明去买票
		xiaoMin
<think>我们正在处理一个关于在Android开发中将异步请求通过加锁转换为同步执行的问题。用户希望了解如何实现这种转换。分析:在Android中,异步操作通常用于避免阻塞主线程(UI线程),但有时我们需要将异步操作转换为同步执行,以便在特定场景下(如按顺序执行多个任务)控制执行流程。常见方法:1.使用锁机制(如Object的wait/notify)或同步工具(如CountDownLatch、Semaphore)来阻塞当前线程,直到异步操作完成。2.使用Future和Callable结合ExecutorService来获取同步执行结果。3.在Android中,由于主线程不能阻塞,因此这种同步化操作通常在工作线程中进行。注意事项:-在Android主线程中阻塞会导致ANR(ApplicationNotResponding)错误,因此同步化操作必须在后台线程中执行。-使用锁机制需要小心死锁和性能问题。方案示例:方法1:使用CountDownLatchCountDownLatch是一个同步辅助类,允许一个或多个线程等待直到在其他线程中执行的一组操作完成。步骤:a.创建一个CountDownLatch,计数为1。b.在异步操作开始前,启动一个后台线程(或者使用已有的后台线程),并在该线程中执行异步操作。c.在异步操作完成时(例如在回调中),调用countDown()。d.在需要等待异步操作完成的地方(必须在后台线程,不能是主线程),调用await()方法。示例代码:```javafinalCountDownLatchlatch=newCountDownLatch(1);//发起异步请求,例如网络请求newAsyncTask<Void,Void,Void>(){@OverrideprotectedVoiddoInBackground(Void...params){//模拟异步操作try{Thread.sleep(2000);//网络请求耗时操作}catch(InterruptedExceptione){e.printStackTrace();}returnnull;}@OverrideprotectedvoidonPostExecute(Voidresult){//异步操作完成latch.countDown();}}.execute();try{//等待异步操作完成,注意:不能在主线程中调用await,否则会阻塞主线程//所以这个等待操作必须在后台线程中执行latch.await();}catch(InterruptedExceptione){e.printStackTrace();}//异步操作完成后,继续执行后续代码```方法2:使用Synchronized和wait/notify使用Object的wait和notify方法,但需要注意正确使用同步块。示例代码:```javafinalObjectlock=newObject();//发起异步请求newThread(newRunnable(){@Overridepublicvoidrun(){//模拟异步操作try{Thread.sleep(2000);}catch(InterruptedExceptione){e.printStackTrace();}synchronized(lock){lock.notify();//通知等待的线程}}}).start();synchronized(lock){try{lock.wait();//等待异步操作完成的通知}catch(InterruptedExceptione){e.printStackTrace();}}```方法3:使用Future和ExecutorService使用ExecutorService提交一个Callable任务,然后通过Future.get()方法同步获取结果,该方法会阻塞直到任务完成。示例代码:```javaExecutorServiceexecutor=Executors.newSingleThreadExecutor();Future<?>future=executor.submit(newCallable<Object>(){@OverridepublicObjectcall()throwsException{//模拟异步操作Thread.sleep(2000);returnnull;}});try{future.get();//阻塞直到任务完成}catch(InterruptedException|ExecutionExceptione){e.printStackTrace();}executor.shutdown();```在Android中,还可以使用AsyncTask的get方法,但同样要注意不能在主线程中调用get,否则会阻塞主线程。方法4:使用Handler和Looper(适用于需要在主线程处理结果的情况,但等待过程在后台线程)如果异步操作完成后需要更新UI,通常我们会使用Handler将结果发送到主线程。但如果需要同步等待,可以在后台线程中使用Looper来等待消息。然而,这种方法较为复杂,通常不推荐用于同步等待,而更推荐使用上述的同步工具。总结:以上方法都可以将异步操作转换为同步执行,但需要注意的是,在Android中,任何可能耗时的操作都不应该在主线程中执行,包括等待异步操作完成的阻塞操作。因此,上述的同步等待操作必须在后台线程中执行。另外,在Android开发中,随着异步编程模型的发展,现在更推荐使用协程(Coroutine)或RxJava等响应式编程库来处理异步操作,它们提供了更简洁的同步编程风格(非阻塞式等待)和更强大的异步组合能力。例如,使用Kotlin协程可以很容易地以同步的方式编写异步代码。例如,使用Kotlin协程:```kotlin//在协程作用域内valresult=withContext(Dispatchers.IO){//执行网络请求等异步操作,这里可以使用挂起函数doNetworkRequest()//挂起函数,内部使用suspendCoroutine或回调转挂起}//然后使用结果更新UI(在主线程中)```其中,挂起函数doNetworkRequest可以这样定义(假设有一个基于回调的异步请求):```kotlinsuspendfundoNetworkRequest()=suspendCoroutine<Result>{continuation->//发起异步请求,在回调中恢复协程someAsyncRequest(object:Callback{overridefunonSuccess(result:Result){continuation.resume(result)}overridefunonError(error:Throwable){continuation.resumeWithException(error)}})}```这样,在协程中调用doNetworkRequest时,看起来就像同步调用一样,但实际上是非阻塞的。因此,如果项目中使用Kotlin,强烈建议使用协程来处理异步转同步的问题。相关问题:1.在Android中,如何避免在将异步请求转换为同步执行时造成主线程阻塞?2.除了使用锁机制,还有哪些现代的方法可以在Android中处理异步操作?3.使用CountDownLatch和Future.get()有什么区别?在性能上有什么差异?4.在Kotlin协程中,如何将基于回调的异步API转换为挂起函数?</think>在Android开发中将异步请求通过加锁转换为同步执行,核心是**使用同步原语阻塞调用线程直到异步结果返回**。以下是具体实现方案和注意事项: --- ### 一、实现方案 #### 1. 使用 `CountDownLatch`(推荐) ```java // 创建计数为1的锁存器 final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<Response> resultRef = new AtomicReference<>(); // 发起异步请求 asyncRequest.enqueue(new Callback() { @Override public void onResponse(Response response) { resultRef.set(response); latch.countDown(); // 释放锁 } @Override public void onFailure(Exception e) { latch.countDown(); // 确保锁释放 } }); try { latch.await(5, TimeUnit.SECONDS); // 阻塞等待结果 Response response = resultRef.get(); // 处理同步结果 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } ``` #### 2. 使用 `Object.wait()`/`notify()` ```java final Object lock = new Object(); final Response[] resultHolder = new Response[1]; asyncRequest.enqueue(new Callback() { @Override public void onResponse(Response response) { synchronized(lock) { resultHolder[0] = response; lock.notifyAll(); // 唤醒等待线程 } } }); synchronized(lock) { lock.wait(5000); // 超时5秒防止死锁 if (resultHolder[0] != null) { // 处理结果 } } ``` #### 3. 使用 `CompletableFuture` (API 24+) ```java CompletableFuture<Response> future = new CompletableFuture<>(); asyncRequest.enqueue(new Callback() { @Override public void onResponse(Response response) { future.complete(response); } @Override public void onFailure(Exception e) { future.completeExceptionally(e); } }); Response response = future.get(5, TimeUnit.SECONDS); // 同步获取结果 ``` --- ### 二、关键注意事项 1. **禁止在主线程使用** - 所有同步阻塞操作**必须放在后台线程**(如`ThreadPoolExecutor`),否则会触发ANR(Application Not Responding)[^4]。 2. **超时机制** - 所有等待操作必须设置超时(如`latch.await(5, SECONDS)`),避免死锁导致线程永久阻塞。 3. **线程中断处理** - 捕获`InterruptedException`并恢复中断状态: ```java catch (InterruptedException e) { Thread.currentThread().interrupt(); } ``` 4. **资源释放** - 在`finally`块中确保锁释放,防止资源泄漏: ```java finally { if (latch.getCount() > 0) latch.countDown(); } ``` --- ### 三、替代方案(推荐) #### 使用协程(Kotlin) ```kotlin // 将回调转换为挂起函数 suspend fun requestSynchronously(): Response = suspendCancellableCoroutine { continuation -> asyncRequest.enqueue(object : Callback { override fun onResponse(response: Response) { continuation.resume(response) } override fun onFailure(e: Exception) { continuation.resumeWithException(e) } }) } // 在协程中同步调用 viewModelScope.launch(Dispatchers.IO) { val response = requestSynchronously() // 同步效果但非阻塞 updateUI(response) } ``` **优势**:协程通过挂起机制实现同步编程风格,避免线程阻塞[^1]。 --- ### 四、性能对比 | 方法 | 线程开销 | 安全性 | API要求 | |---------------------|----------|--------|---------| | `CountDownLatch` | 低 | 高 | 全版本 | | `Object.wait()` | 低 | 中 | 全版本 | | `CompletableFuture` | 中 | 高 | API 24+ | | **协程** | **极低** | **高** | Kotlin | > ⚠️ 警告:强制异步转同步通常违反Android设计规范,可能导致性能问题,仅应在特殊场景(如单元测试)使用。 --- ### 相关问题 1. Android主线程阻塞为什么会导致ANR?如何检测和避免ANR? 2. 协程的挂起机制如何实现非阻塞式同步?与线程阻塞的本质区别是什么[^1]? 3. 在多进程环境中(如`ContentProvider`跨进程调用),上述同步方案是否仍然有效? 4. 如何通过`RxJava`的`blockingGet()`实现异步转同步?有什么风险? [^1]: 协程通过挂起机制避免线程阻塞,实现高效的并发控制,区别于传统线程的上下文切换开销[引用1]。 [^4]: Android主线程阻塞超过5秒将触发ANR,系统会强制终止应用[引用4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值