java.util.concurrent小结

本文深入介绍了JDK1.5引入的java.util.concurrent包中的关键工具类,包括Semaphore、CountDownLatch、CyclicBarrier、ReentrantLock及BlockingQueue等,旨在帮助读者理解和掌握高质量并发程序设计的方法。

在JDK1.5之前,Java中要进行业务并发时,通常需要有程序员独立完成代码实现,当然也有一些开源的框架提供了这些功能,但是这些依然没有JDK自带的功能使用起来方便。而当针对高质量Java多线程并发程序设计时,为防止死蹦等现象的出现,比如使用java之前的wait()、notify()和synchronized等,每每需要考虑性能、死锁、公平性、资源管理以及如何避免线程安全性方面带来的危害等诸多因素,往往会采用一些较为复杂的安全策略,加重了程序员的开发负担。

万幸的是,JDK1.5出现之后,Sun大神(Doug Lea)终于为我们这些可怜的小程序员推出了java.util.concurrent工具包以简化并发完成。开发者们借助于此,将有效的减少竞争条件(race conditions)和死锁线程。concurrent包很好的解决了这些问题,为我们提供了更实用的并发程序模型。

部分API类图如下:



实用类介绍

Semaphore

计数信号量,只对可用许可的号码进行计数,并采取相应的行动。Semephore通常用于限制可以访问某些资源的线程数目。acquire方法请求一个许可,有可用资源之前一直阻塞。release方法释放一个许可给Semaphore。这里是一个实际的情况,大家排队上厕所,厕所只有两个位置,来了10个人需要排队。

public class MySemaphore extends Thread {
	private int id;
	private Semaphore s;
	
	public MySemaphore(int id, Semaphore s) {
		this.id = id;
		this.s = s;
	}
	
	public void run() {
		if (s.availablePermits() > 0) 
			System.out.println("顾客[" + this.id + "]进入厕所,有空位");
		else 
			System.out.println("顾客[" + this.id + "]进入厕所,无空位");
		
		try {
			s.acquire();
			System.out.println("顾客[" + this.id + "]获得坑位");
			Thread.sleep((int)(Math.random() * 1000));
			System.out.println("顾客[" + this.id + "]使用完毕");
			s.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		Semaphore s = new Semaphore(2);
		ExecutorService service = Executors.newCachedThreadPool();
		for (int i=0; i<10; i++) {
			service.submit(new MySemaphore(i, s));
		}
		service.shutdown();
		s.acquireUninterruptibly(2);// 请求2个信号量,在2个信号量可用之前,一直阻塞
		System.out.println("使用完毕,需要清扫了");
		s.release(2);// 释放2个新号量给Semaphore
	}
}

CountDownLatch

同步辅助类。调用countDown(),在当前计数到达零之前,await()方法会一直阻塞。可以将初始化计数为1的CountDownLatch作为一个简单的开关锁存器。用初始化为N的CountDownLatch可以使一个线程在N个线程执行某项操作之前或者一个线程执行N次操作之前一直等待。

public class MyCountDownLatch {
	static final CountDownLatch begin = new CountDownLatch(2);
	static final CountDownLatch end = new CountDownLatch(10);
	
	public static void main(String[] args) throws InterruptedException {
		ExecutorService service = Executors.newFixedThreadPool(10);
		for (int i=0; i<10; i++) {
			final int number = i + 1;
			Runnable task = new Runnable() {
				
				@Override
				public void run() {
					try {
						begin.await();// 一直阻塞
						Thread.sleep((int)(Math.random() * 10000));
						System.out.println("NO." + (number + 1) + " arrived");
					} catch (InterruptedException e) {
						e.printStackTrace();
					} finally {
						end.countDown();
					}
				}
			};
			service.submit(task);
		}
		
		System.out.println("Game start");
		begin.countDown();
		end.await();
		System.out.println("Game over");
		service.shutdown();
	}
}
注:上面调用begin.await()时一直阻塞。除非在调用await()方法之前,调用begin.countDown(),让计数到达零。

CyclicBarrier

同步辅助类。允许一组线程相互等待,直到达到一个公共屏障点。最重要的参数为参与者个数,创建对象时通过构造方法传入。另外重要的方法是await(),当所有参与者对应的线程都调用await()方法,这些线程都可以继续执行,否则就会等待。

public class MyCyclicBarrier {
	// 步行
	private static final int[] timeWalk = {5, 8, 15, 15, 10};
	// 自驾游
	private static final int[] timeSelf = {1, 3, 4, 4, 5};
	// 旅游大巴
	private static final int[] timeBus = {2, 4, 6, 6, 7};
	
	public static String now() {
		return new SimpleDateFormat("HH:mm:ss").format(new Date());
	}
	
	static class Tour extends Thread {
		private CyclicBarrier barrier;
		private String tourName;
		private int[] times;
		
		public Tour(CyclicBarrier barrier, String tourName, int[] times) {
			this.barrier = barrier;
			this.tourName = tourName;
			this.times = times;
		}
		
		public void run() {
			try {
				Thread.sleep(times[0] * 1000);
				System.out.println(now() + ": " + tourName + " reached ShenZhen");
				barrier.await();
				
				Thread.sleep(times[1] * 1000);
				System.out.println(now() + ": " + tourName + " reached GuangZhou");
				barrier.await();
				
				Thread.sleep(times[2] * 1000);
				System.out.println(now() + ": " + tourName + " reached ShaoGuan");
				barrier.await();
				
				Thread.sleep(times[3] * 1000);
				System.out.println(now() + ": " + tourName + " reached ChangSha");
				barrier.await();
				
				Thread.sleep(times[4] * 1000);
				System.out.println(now() + ": " + tourName + " reached WhHan");
				barrier.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (BrokenBarrierException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) {
		CyclicBarrier barrier = new CyclicBarrier(3);
		ExecutorService service = Executors.newFixedThreadPool(3);
		service.submit(new Tour(barrier, "WalkTour", timeWalk));
		service.submit(new Tour(barrier, "SelfTour", timeSelf));
		// 当我们把下面的程序注释了,会发现程序阻塞了,无法继续进行下去
		service.submit(new Tour(barrier, "BusTour", timeBus));
		service.shutdown();		
	}
}

ReentrantLock 

互斥锁定Lock,与synchronized方法和语句具有相同的基本行为和语义,但功能更强大。lock方法请求锁,若资源未锁定,则可以获取锁。unlock方法释放持有的锁资源。

public class MyReentrantLock extends Thread {
	private int id;
	private TestReentrantLock test;
	
	public MyReentrantLock(int id, TestReentrantLock test) {
		this.id = id;
		this.test = test;
	}
	
	public void run() {
		this.test.print(id);
	}
	
	public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		TestReentrantLock lock = new TestReentrantLock();
		for (int i=0; i<10; i++) {
			service.submit(new MyReentrantLock(i, lock));
		}
		service.shutdown();
	}
}

class TestReentrantLock {
	private ReentrantLock lock = new ReentrantLock();
	
	public void print(int id) {
		try {
			lock.lock();
			System.out.println(id + ", 获得!");
			Thread.sleep((int)(Math.random() * 1000));
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			System.out.println(id + ", 释放!");
			lock.unlock();
		}
	}
}
BlockingQueue

该接口是线程安全的,主要用于实现生产者-消费者队列。可以设置初始化大小,若不设置,默认Integer.MAX_VALUE。可以通过add(E)、put(E)、offer(E)添加元素,但添加元素不能为null,否则报NullPointerException。可以通过take()移除队列的头部元素。

public class MyBlockingQueue extends Thread {
	private static BlockingQueue<String> queue = new LinkedBlockingQueue<String>(3);
	private int id;
	
	public MyBlockingQueue(int id) {
		this.id = id;
	}
	
	public void run() {
		try {
			queue.put(String.valueOf(id));
			System.out.println("{" + id + "} in queque");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		for (int i=0; i<10; i++) 
			service.submit(new MyBlockingQueue(i));
		
		Runnable task = new Runnable() {
			
			@Override
			public void run() {
				while (true) {
					try {
						Thread.sleep((int)(Math.random() * 1000));
						if (queue.isEmpty())
							break;
						System.out.println(queue.take() + " has take");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};
		service.submit(task);
		service.shutdown();
	}
}

CompletionService

将生产新的异步任务与已完成任务的结果分离开来的服务。submit方法提交一个Runnable任务执行,并返回一个Future代表任务。take()释放并移除代表下一个即将完成任务的Future。

public class MyCompletionService implements Callable<String> {
	private int id;
	
	public MyCompletionService(int id) {
		this.id = id;
	}
	
	@Override
	public String call() throws Exception {
		int time = (int)(Math.random() * 1000);
		
		try {
			System.out.println("{" + this.id + "}, 启动");
			Thread.sleep(time);
			System.out.println("{" + this.id + "}, 关闭");
		} catch (Exception e) {
			e.printStackTrace();
		} 
		
		return this.id + "," + time;
	}
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ExecutorService executor = Executors.newCachedThreadPool();
		final CompletionService<String> cs = new ExecutorCompletionService<String>(executor);
		for (int i=0; i<10; i++) {
			cs.submit(new MyCompletionService(i));
		}
		
		for (int i=0; i<10; i++)
			System.out.println(cs.take().get());
		executor.shutdown();
	}
	
}

ScheduledExecutorService

使用Executors的execute(Runnable)方法和ExecutorService的submit(Runnable)方法,提交的请求,为0延迟进行安排。所有schedule接受相对周期和延迟作为参数,ScheduledExecutorService提供了scheduleAtFixedRate 和 scheduleWithFixedDelay 方法创建并执行某些在取消前一直定期运行的任务。

public class TestSchedule {
	public static void main(String[] args) {
		final ScheduledExecutorService scheduleService = Executors.newScheduledThreadPool(2);
		final Runnable beeper = new Runnable() {
			int count = 0;
			@Override
			public void run() {
				System.out.println(new Date() + " beep " + (++count));
			}
		};
		// 1秒后运行,每隔两秒运行一次
		final ScheduledFuture beeperHandler = scheduleService.scheduleAtFixedRate(beeper, 1, 2, TimeUnit.SECONDS);
		// 2秒后运行,上次任务结束后等待5秒继续运行
		final ScheduledFuture beeperHandler2 = scheduleService.scheduleWithFixedDelay(beeper, 2, 5, TimeUnit.SECONDS);
		// 30秒后关闭schedule
		scheduleService.schedule(new Runnable() {
			
			@Override
			public void run() {
				beeperHandler.cancel(true);
				beeperHandler2.cancel(true);
			}
		}, 30, TimeUnit.SECONDS);
		scheduleService.shutdown();
	}
}

参考资料:http://www.open-open.com/bbs/view/1320131360999

zhouweixiang@KI-ZJ-2961:~$ ps aux | grep -i studio zhouwei+ 2303845 21.3 24.0 13819648 3875352 ? Sl 11:36 53:15 /home/zhouweixiang/下载/android-studio/jbr/bin/java -classpath /home/zhouweixiang/下载/android-studio/lib/platform-loader.jar:/home/zhouweixiang/下载/android-studio/lib/util-8.jar:/home/zhouweixiang/下载/android-studio/lib/util.jar:/home/zhouweixiang/下载/android-studio/lib/util_rt.jar:/home/zhouweixiang/下载/android-studio/lib/trove.jar:/home/zhouweixiang/下载/android-studio/lib/app.jar:/home/zhouweixiang/下载/android-studio/lib/opentelemetry.jar:/home/zhouweixiang/下载/android-studio/lib/jps-model.jar:/home/zhouweixiang/下载/android-studio/lib/stats.jar:/home/zhouweixiang/下载/android-studio/lib/rd.jar:/home/zhouweixiang/下载/android-studio/lib/external-system-rt.jar:/home/zhouweixiang/下载/android-studio/lib/protobuf.jar:/home/zhouweixiang/下载/android-studio/lib/bouncy-castle.jar:/home/zhouweixiang/下载/android-studio/lib/intellij-test-discovery.jar:/home/zhouweixiang/下载/android-studio/lib/forms_rt.jar:/home/zhouweixiang/下载/android-studio/lib/lib.jar:/home/zhouweixiang/下载/android-studio/lib/externalProcess-rt.jar:/home/zhouweixiang/下载/android-studio/lib/groovy.jar:/home/zhouweixiang/下载/android-studio/lib/annotations.jar:/home/zhouweixiang/下载/android-studio/lib/idea_rt.jar:/home/zhouweixiang/下载/android-studio/lib/kotlinx-coroutines-slf4j-1.8.0-intellij.jar:/home/zhouweixiang/下载/android-studio/lib/resources.jar -XX:ErrorFile=/home/zhouweixiang/java_error_in_studio_%p.log -XX:HeapDumpPath=/home/zhouweixiang/java_error_in_studio_.hprof -Xms256m -Xmx2048m -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -XX:CICompilerCount=2 -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:TieredOldPercentage=100000 -ea -Dsun.io.useCanonCaches=false -Dsun.java2d.metal=true -Djbr.catch.SIGABRT=true -Djdk.http.auth.tunneling.disabledSchemes="" -Djdk.attach.allowAttachSelf=true -Djdk.module.illegalAccess.silent=true -Djdk.nio.maxCachedBufferSize=2097152 -Djava.util.zip.use.nio.for.zip.file.access=true -Dkotlinx.coroutines.debug=off -Dsun.tools.attach.tmp.only=true -Dawt.lock.fair=true -Djb.vmOptionsFile=/home/zhouweixiang/下载/android-studio/bin/studio64.vmoptions -Xbootclasspath/a:/home/zhouweixiang/下载/android-studio/lib/nio-fs.jar -Djava.system.class.loader=com.intellij.util.lang.PathClassLoader -Didea.vendor.name=Google -Didea.paths.selector=AndroidStudio2025.1.1 -Djna.boot.library.path=/home/zhouweixiang/下载/android-studio/lib/jna/amd64 -Djna.nosys=true -Djna.noclasspath=true -Dpty4j.preferred.native.folder=/home/zhouweixiang/下载/android-studio/lib/pty4j -Dio.netty.allocator.type=pooled -Dintellij.platform.runtime.repository.path=/home/zhouweixiang/下载/android-studio/modules/module-descriptors.jar -Didea.platform.prefix=AndroidStudio -XX:FlightRecorderOptions=stackdepth=256 --add-opens=java.base/sun.net.www.protocol.https=ALL-UNNAMED -Djava.security.manager=allow -Dij.startup.error.report.url=https://issuetracker.google.com/issues/new?component=192708 -XX:CompileCommand=exclude,org.jetbrains.kotlin.serialization.deserialization.TypeDeserializer::simpleType -XX:CompileCommand=exclude,org.jetbrains.kotlin.serialization.deserialization.TypeDeserializer::toAttributes -Dsplash=true -Daether.connector.resumeDownloads=false -Dcompose.swing.render.on.graphics=true --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.ref=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.text=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED --add-opens=java.base/jdk.internal.vm=ALL-UNNAMED --add-opens=java.base/sun.net.dns=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/sun.nio.fs=ALL-UNNAMED --add-opens=java.base/sun.security.ssl=ALL-UNNAMED --add-opens=java.base/sun.security.util=ALL-UNNAMED --add-opens=java.desktop/com.sun.java.swing=ALL-UNNAMED --add-opens=java.desktop/com.sun.java.swing.plaf.gtk=ALL-UNNAMED --add-opens=java.desktop/java.awt=ALL-UNNAMED --add-opens=java.desktop/java.awt.dnd.peer=ALL-UNNAMED --add-opens=java.desktop/java.awt.event=ALL-UNNAMED --add-opens=java.desktop/java.awt.font=ALL-UNNAMED --add-opens=java.desktop/java.awt.image=ALL-UNNAMED --add-opens=java.desktop/java.awt.peer=ALL-UNNAMED --add-opens=java.desktop/javax.swing=ALL-UNNAMED --add-opens=java.desktop/javax.swing.plaf.basic=ALL-UNNAMED --add-opens=java.desktop/javax.swing.text=ALL-UNNAMED --add-opens=java.desktop/javax.swing.text.html=ALL-UNNAMED --add-opens=java.desktop/sun.awt=ALL-UNNAMED --add-opens=java.desktop/sun.awt.X11=ALL-UNNAMED --add-opens=java.desktop/sun.awt.datatransfer=ALL-UNNAMED --add-opens=java.desktop/sun.awt.image=ALL-UNNAMED --add-opens=java.desktop/sun.font=ALL-UNNAMED --add-opens=java.desktop/sun.java2d=ALL-UNNAMED --add-opens=java.desktop/sun.swing=ALL-UNNAMED --add-opens=java.management/sun.management=ALL-UNNAMED --add-opens=jdk.attach/sun.tools.attach=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-opens=jdk.internal.jvmstat/sun.jvmstat.monitor=ALL-UNNAMED --add-opens=jdk.jdi/com.sun.tools.jdi=ALL-UNNAMED com.android.tools.idea.MainWrapper zhouwei+ 2304091 0.0 0.0 4136 2280 ? S 11:36 0:01 /home/zhouweixiang/下载/android-studio/bin/fsnotifier zhouwei+ 2304143 0.2 3.5 1898840 571504 ? Sl 11:36 0:39 /home/zhouweixiang/下载/android-studio/plugins/c-clangd/bin/clang/linux/x64/bin/clangd --clion-mode=clion-main -update-debounce=0 -index=false -include-ineligible-results -clang-tidy=0 -resource-dir=/home/zhouweixiang/下载/android-studio/plugins/c-clangd/bin/clang/linux/x64 -keep-asts=30 -ranking-model=heuristics -clion-extra-completion-preamble -clion-keep-obsolete-ast=false -header-extensions=h;h; zhouwei+ 2361297 7.0 9.9 10161420 1604020 ? Ssl 13:54 7:48 /home/zhouweixiang/下载/android-studio/jbr/bin/java --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.prefs/java.util.prefs=ALL-UNNAMED --add-opens=java.base/java.nio.charset=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.xml/javax.xml.namespace=ALL-UNNAMED -Xmx2048m -Dfile.encoding=UTF-8 -Duser.country=CN -Duser.language=zh -Duser.variant -cp /home/zhouweixiang/test/git/code-S32/03IMPLEMENT/0301Code/ISA-NaviService/wrapper/dists/gradle-8.11.1-bin/aqblo1612keip6ox4t4vpymx1/gradle-8.11.1/lib/gradle-daemon-main-8.11.1.jar -javaagent:/home/zhouweixiang/test/git/code-S32/03IMPLEMENT/0301Code/ISA-NaviService/wrapper/dists/gradle-8.11.1-bin/aqblo1612keip6ox4t4vpymx1/gradle-8.11.1/lib/agents/gradle-instrumentation-agent-8.11.1.jar org.gradle.launcher.daemon.bootstrap.GradleDaemon 8.11.1 zhouwei+ 2386683 0.0 0.0 14488 5340 pts/6 Ss+ 14:37 0:00 /bin/bash --rcfile /home/zhouweixiang/下载/android-studio/plugins/terminal/shell-integrations/bash/bash-integration.bash -i zhouwei+ 2415177 0.2 1.3 1864704 210168 ? Sl 15:35 0:01 /home/zhouweixiang/下载/android-studio/plugins/android-ndk/resources/lldb/bin/LLDBFrontend 42885 zhouwei+ 2420184 0.0 0.0 12132 2604 pts/0 S+ 15:45 0:00 grep --color=auto -i studio zhouweixiang@KI-ZJ-2961:~$
08-06
源码来自:https://pan.quark.cn/s/a4b39357ea24 ### 操作指南:洗衣机使用方法详解#### 1. 启动与水量设定- **使用方法**:使用者必须首先按下洗衣设备上的“启动”按键,同时依据衣物数量设定相应的“水量选择”旋钮(高、中或低水量)。这一步骤是洗衣机运行程序的开端。- **运作机制**:一旦“启动”按键被触发,洗衣设备内部的控制系统便会启动,通过感应器识别水量选择旋钮的位置,进而确定所需的水量高度。- **技术执行**:在当代洗衣设备中,这一流程一般由微处理器掌管,借助电磁阀调控进水量,直至达到指定的高度。#### 2. 进水过程- **使用说明**:启动后,洗衣设备开始进水,直至达到所选的水位(高、中或低)。- **技术参数**:水量的监测通常采用浮子式水量控制器或压力感应器来实现。当水位达到预定值时,进水阀会自动关闭,停止进水。- **使用提醒**:务必确保水龙头已开启,并检查水管连接是否牢固,以防止漏水。#### 3. 清洗过程- **使用步骤**:2秒后,洗衣设备进入清洗环节。在此期间,滚筒会执行一系列正转和反转的动作: - 正转25秒 - 暂停3秒 - 反转25秒 - 再次暂停3秒- **重复次数**:这一系列动作将重复执行5次,总耗时为280秒。- **技术关键**:清洗环节通过电机驱动滚筒旋转,利用水流冲击力和洗衣液的化学效果,清除衣物上的污垢。#### 4. 排水与甩干- **使用步骤**:清洗结束后,洗衣设备会自动进行排水,将污水排出,然后进入甩干阶段,甩干时间为30秒。- **技术应用**:排水是通过泵将水抽出洗衣设备;甩干则是通过高速旋转滚筒,利用离心力去除衣物上的水分。- **使用提醒**:...
代码下载地址: https://pan.quark.cn/s/c289368a8f5c 在安卓应用开发领域,构建一个高效且用户友好的聊天系统是一项核心任务。 为了协助开发者们迅速达成这一目标,本文将分析几种常见的安卓聊天框架,并深入说明它们的功能特性、应用方法及主要优势。 1. **环信(Easemob)** 环信是一个专为移动应用打造的即时通讯软件开发套件,涵盖了文本、图片、语音、视频等多种消息形式。 通过整合环信SDK,开发者能够迅速构建自身的聊天平台。 环信支持消息内容的个性化定制,能够应对各种复杂的应用场景,并提供多样的API接口供开发者使用。 2. **融云(RongCloud)** 融云作为国内领先的IM云服务企业,提供了全面的聊天解决方案,包括一对一交流、多人群聊、聊天空间等。 融云的突出之处在于其稳定运行和高并发处理性能,以及功能完备的后台管理工具,便于开发者执行用户管理、消息发布等操作。 再者,融云支持多种消息格式,如位置信息、文件传输、表情符号等,显著增强了用户聊天体验。 3. **Firebase Cloud Messaging(FCM)** FCM由Google提供的云端消息传递服务,可达成安卓设备与服务器之间的即时数据交换。 虽然FCM主要应用于消息推送,但配合Firebase Realtime Database或Firestore数据库,开发者可以开发基础的聊天软件。 FCM的显著优势在于其全球性的推送网络,保障了消息能够及时且精确地传输至用户。 4. **JMessage(极光推送)** 极光推送是一款提供消息发布服务的软件开发工具包,同时具备基础的即时通讯能力。 除了常规的文字、图片信息外,极光推送还支持个性化消息,使得开发者能够实现更为复杂的聊天功能。 此...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值