-
Java并发编程——线程安全性
2018-12-02 15:37:45当多个线程访问某个类时,不管何种调度方式,这些线程如何调度执行,在调用代码中不需要额外的同步或协同时,这个类都能表现正确的行为,这个类就是线程安全的。 2.2原子性 例如上一章说的,一个++操作需要进行读取-...2.1 定义
当多个线程访问某个类时,不管何种调度方式,这些线程如何调度执行,在调用代码中不需要额外的同步或协同时,这个类都能表现正确的行为,这个类就是线程安全的。
2.2原子性
例如上一章说的,一个++操作需要进行读取-修改-写入三步,所以如果两个线程调用同一个非线程安全的函数,并且函数中有类似于a++这样的操作,则可能会出现两个线程同时读为9最后都设置为10,结果就将偏差1,也有可能出现其他不可预料的情况。
2.2.1 竞态条件
当某个计算的正确性取决于多个线程交替执行的顺序,就会发生竞态条件,也就是说结果取决于"运气"。
其实上面原子性的例子就是一个例子,竞态意味着竞争,而上面的例子就是两个线程竞争同一资源而导致结果不可预料,这就是一个典型的竞态条件。
2.2.2 实例:延迟初始化中的竞态条件
延迟初始化的目的是将对象的初始化操作推迟到实际被使用时才进行,也是常见的竞态条件之一——先检查后执行
@NotThreadSafe //线程不安全 public class LazyInitRace{ private ExpensiveObject instance = null; public ExpensiveObject getInstance(){ if(instance == null){ instance = new ExpensiveObject(); } return instance; } }
假设线程A和B同时执行getInstance,A看到instance为空,从而创建一个新的ExpensiveObject实例。当B判断instance是否为空时要取决于不可预测的顺序,例如当A在初始化的过程中,B对instance进行了判断,此时为空,则会也创建一个新的对象,那么这两个线程就会返回不同的结果。
2.2.3 复合操作
++操作以及例如延迟初始化一类的操作如果要避免竞态条件都需要以原子方式执行,也就是当其中一个线程执行时,其他线程都不能访问。
例如有两个操作AB,从执行A的线程来看,另一个线程执行B时,要么执行完B要么不执行B,那么A和B对彼此来说就是原子的。
类似于递增操作这种需要进行多步的操作统称为复合操作:必须以原子方式执行以确保线程安全性。
解决这个问题的其中一种操作就是原子变量类。例如用AtomicLong来代替long类型的计数器能够保证访问这个变量的操作都是原子的。2.3 加锁机制
尽管使用原子变量可以保证操作是原子性的,但是在方法调用时仍然会出现竞态条件,因为如果有两次原子性操作,在两个线程调用时仍然会出现交替的情况,不变性条件就破坏了。
2.3.1 内置锁
Java提供一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。
同步代码块包括两部分:锁的对象引用,锁保护的代码块。
以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,每个Java 对象都可以用作一个实现同步的锁,这些锁被称为内部锁,线程进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,而最多只有一个线程可以持有这种锁。
当我们使用synchronized修饰一个代码块时,这个同步代码块会以原子方式执行,多个线程在执行时也不会互相干扰,然而这种方法会使得同一时刻只有一个线程可以执行该方法,过于极端,效率很低。2.3.2 重入
当某个线程请求一个由其他线程持有得锁时,请求会阻塞。而内置锁是可以重入的,如果某个线程请求一个已经由它自己持有的锁时,请求就会成功。
机制实现方法是每个锁关联一个获取计数值和一个所有者线程,线程请求一个未被持有的锁时,jvm记下锁的持有者,并且将计数值加一,当数值为0时锁将被释放。
例子:public class Widget{ public synchronized void doSomething(){ //.......... } } public class LoggingWidget extends Widget{ public synchronized void doSomething(){ super.doSomething; } }
代码中子类改写父类的同一方法,然后调用父类方法,如果锁不可重入,在调用父类方法时将无法获得Widget上的锁,那么将会发生死锁。
2.4 用锁来保护状态
对于可能被多个线程同时访问的可变状态变量,在访问呢它时都需要持有同一个锁,在这种情况下,状态变量时由这个锁保护的
2.5 活跃性与性能
当我们在一个servlet中使用共享状态,就需要通过servlet对象的内置锁来保护每一个状态变量,也就是对整个service方法进行同步,但代价很高。
当给出多个请求同时到达servlet时,请求将排队处理
A——>servlet
B——>serlvet
C——>servlet
这将大大降低程序的性能,所以我们要缩小同步代码块的作用范围,来使得某一部分操作时同步的,而同时可以处理很多请求。 -
Java安全性编程实例_完整版[PDF]和光盘源代码
2011-03-03 15:45:34本章解决的是数据内容的安全性,介绍Java的加密和解密技术。学完该章可以通过Java编程对各种数据进行各种形式的加密。密码学也是安全机制的基础。 第三章 解决的主要问题——和源代码相关的安全性 编写好的程序给... -
java连接sql server数据库的代码如何改成连接mysql_java连接SQL数据库(JDBC)相关设置...
2021-01-27 02:04:472016-06-14一、SQL server中的相关设置(以sql server2012 版本为例)建立一个SQL server 身份认证的服务器登录名首先启动SQL客户端,以windows身份认证方式登录到服务器,依次点击 安全性-->右击登录名-->新建...2016-06-14
一、SQL server中的相关设置(以sql server 2012 版本为例)
建立一个SQL server 身份认证的服务器登录名
首先启动SQL客户端,以windows身份认证方式登录到服务器,依次点击 安全性-->右击登录名-->新建登录名-->输入登录名-->选择SQL Server 身份验证-->输入密码-->先不要确定 在当前对话框左边选择 服务器角色 -->勾选sysadmin(相当于管理员权限) 重启数据库 选择sql server身份认证登录 并能对数据库中的表进行一些常规操作就OK了
二、eclipse 中的相关设置
准备:下载jtds-1.3.1-dist(JDBC-SQLServer驱动)
地址:链接:http://pan.baidu.com/s/1boW5s7t 密码:hkqn
1.首先新建一个java项目 命名为JDBCTEST ;
2.在该项目下新建一个文件夹(Folder) 命名为libs ;
3.将刚刚下载好的文件保存好,解压 ,将其中的jtds-1.3.1.jar包和x86或x64(32位和64位根据你的系统其中一个即可) 直接拉进刚刚的文件夹libs中(复制) ;
4.现在右单击JDBCTEST-->Build Path-->Configure Build Path...-->右边Libraries-->Add Library...-->JRE System Library -->next-->Alternate JRE -->选择jdk版本(最好是1.7)-->finish
5.右单击JDBCTEST-->Build Path-->Configure Build Path...-->右边Libraries-->Add JARs...-->找到JDBCTEST下的libs下的jtds-1.3.1.jar 点OK即可
6.现在Libraries页多了一个jtds-1.3.1.jar 字样的包 点击它左边的展开按钮-->点击Native...字样-->点击Edit-->找到JDBCTEST下的libs下的x64或x86下的SSO文件夹 选中 --> OK即可
7.测试 在该项目下 建一个包com.cqvie 类Test 复制下面程序 运行后没错误且SQL相应表中有数据就可以了。
package com.cqvie;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class Test {
//常量定义(一般用全大写)
public static final String DBDRIVER="net.sourceforge.jtds.jdbc.Driver";
public static final String DBURL
="jdbc:jtds:sqlserver://127.0.0.1:1433;DatabaseName=";//IP、端口和数据库名
public static final String DBUSER="";//账号
public static final String PASSWORD="";//密码
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER);
Connection cn=DriverManager.getConnection(DBURL,DBUSER,PASSWORD);
//Connection cn=DriverManager.getConnection(DBURL);
Statement st=cn.createStatement();
st.executeUpdate("insert into java_test(sno,sname,sex,birthday,sd)"
+ "values('9998','','1','','2')");//进行的操作
cn.close();
}
}
注意 : 如果运行程序有错误,打开SQL配置管理器检查相关设置是否正确,相关服务是否启动。如tcp/ip是否启动,端口号是不是上面程序中的1433等等。可以参照下面步骤进行检查:
1.确定SQL Sever服务器已经启动;
2. 确定1433端口号已经打开, 确认请按以下步骤:
(1)win7 系统在打开控制面板--->程序 (xp一下的忽略这一步)
win10系统 控制面板-->查看方式调到 类别-->程序-->程序和功能
再
点击:程序和功能
在左侧点击'打开或关闭Windows功能'
找到'Telnet客户端', 打勾
(2)开始--->运行--->输入'cmd' ,回车
(3)输入'telnet 127.0.0.1 1433' , 含1和1433之间空格, 如果提示'无法打开主机连接, 在端口1433 连接失败'。自行www.baidu.com解决。
3.打开1433端口号:
(1)在开始菜单找到sql 的管理配置器
(2)猛击SQLEXPRESS协议
(3)在右侧找到TCP/IP, 右键属性
(4)在IP地址上的IPALL的端口号改成1433
(5)在(3)步的TCP/IP右键,启动服务
(6)启动SQL Sever服务器
如果实在不能解决,-->www.baidu.com是你永远的港湾。
-
Java理论与实践:理解JTS―平衡安全性和性能
2020-03-04 12:27:50本文定义了什么是事务,列举了事务的基本特性,并探讨了Java事务服务和J2EE容器如何合作为事务提供对 J2EE 组件的透明支持。在本文中,我们将讨论事务的划分和隔离这个主题。为EJB组件定义事务划分和隔离属性的职责... -
android 添加java服务器_Android(Java)与服务器的简单发送和接收-快速设置挑战
2021-02-28 17:36:43我正在编写一个Android应用,正在寻找最快的方式(就设置而言),以便将数据发送到服务器并按...我不需要安全性,不需要高吞吐量或并发连接(我可以玩3部手机),但是我需要快速设置它!我记得在过去,XAMPP的设置特别...我正在编写一个Android应用,正在寻找最快的方式(就设置而言),以便将数据发送到服务器并按要求接收信息。
我们在说基本的东西。我有一个日志文件,该文件告诉我用户如何使用我的应用程序(在beta中,我通常不会通过不断记录日志来获得用户体验),并且我想将其传达给服务器(我尚未设置)。
我不需要安全性,不需要高吞吐量或并发连接(我可以玩3部手机),但是我需要快速设置它!
我记得在过去,XAMPP的设置特别笨拙,那么也许我可以使用PHP将文件从手机发送到服务器?
理想情况下,服务器将能够响应GET,这将使我能够发回一些最终影响UI的SQL语句。(这意味着可以根据最常用的选项来调整显示的选项)。
因此,有了它,我大约在4年前就使用PHP,如果它是最好的,它将沿这条路线走,但是如果有某种新的有端口的开放式关闭二进制流唱歌和跳舞方法取代了我想知道的那个选项。
本教程似乎很有用,但我实际上并不需要对象序列化,只需来回地压缩文本文件即可。
Android 内置了 Apache HTTP Client 4.0
以及java.net.URL和java.net.HttpUrlConnection,我不希望使用第三方库向我的应用程序添加过多的内容。
请记住,我也在设置服务器端,因此我正在寻找总体上最少的代码行!
-
java redis密码_Redis 设置密码登录
2021-02-12 19:28:09前言redis在生产环境中通常都会设置密码以保证一定的安全性,本篇文章就简单记录一下如何在redis中设置客户端登录密码。修改redis.confRT,打开redis.conf文件,搜索requirepass关键字,如下图:关注标记的那一行,#...前言
redis在生产环境中通常都会设置密码以保证一定的安全性,本篇文章就简单记录一下如何在redis中设置客户端登录密码。
修改redis.conf
RT,打开redis.conf文件,搜索requirepass关键字,如下图:
关注标记的那一行,#requirepass foobared。设置密码的方法就是去掉注释的#,把foobared替换成自己的密码即可,例如将密码设置为123456:
修改完成后重启redis,再次通过redis客户端redis-cli登录并操作可以发现会报一个身份认证错误:
这就说明我们已经成功的设置了密码,所以通过客户端连接的话必须加上密码参数才能正常连接:
如上图所示,加了-a参数之后即可正常连接并操作redis。
jedis设置密码
当我们用Java客户端连接redis时会遇到同样的问题,下面看一段简单的jedis连接redis的测试代码:
package com.firstelite.test;
import org.junit.Test;
import redis.clients.jedis.Jedis;
public class Test4Jedis {
@Test
public void testTwo() {
Jedis jedis = new Jedis("192.168.145.10");
System.out.println("Connection to server sucessfully");
// 查看服务是否运行
System.out.println("Server is running: " + jedis.ping());
}
}
非常简单,仅仅是测试一下Jedis是否连通redis服务器,运行junit后我们发现报异常了:
redis.clients.jedis.exceptions.JedisDataException: NOAUTH Authentication required.
at redis.clients.jedis.Protocol.processError(Protocol.java:117)
at redis.clients.jedis.Protocol.process(Protocol.java:142)
at redis.clients.jedis.Protocol.read(Protocol.java:196)
at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:288)
at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:187)
at redis.clients.jedis.BinaryJedis.ping(BinaryJedis.java:109)
at com.firstelite.test.Test4Jedis.testTwo(Test4Jedis.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
显而易见,由于我们设置了密码但在这里又没有指定密码,所以报了和刚才相同的错误,那么如何指定密码呢?很简单,Jedis的父类BinaryJedis提供了这样一样方法:
public String auth(final String password) {
checkIsInMulti();
client.auth(password);
return client.getStatusCodeReply();
}
所以在创建了Jedis的实例后再加上一行jedis.auth("123456"); 即可,最后看一下运行结果:
spring-data-redis设置密码
通常情况下在实际的java项目中我们会选择Spring提供的spring-data-redis来操作redis,spring的封装可以给我们提供很多便捷之处。那么spring-data-redis又是如何设置密码的呢?首先定义一个redis.properties配置文件,定义一组redis属性供spring加载使用,其中就包含密码(redis.password):
# Redis settings
redis.host=192.168.145.10
redis.port=6379
redis.password=123456
redis.timeout=100000
redis.maxTotal=300
redis.maxIdle=100
redis.maxWaitMillis=1000
redis.testOnBorrow=true
然后在由Spring封装的JedisConnectionFactory中来设置密码属性即可,下面是完整redis配置:
p:host-name="${redis.host}" p:port="${redis.port}"
p:password="${redis.password}" p:pool-config-ref="poolConfig" />
总结
简单整理一下关于Redis设置密码相关的内容,希望对遇到同样问题的朋友有所帮助,The End。
下面关于Redis的文章您也可能喜欢,不妨参考下:
Redis 的详细介绍:请点这里
Redis 的下载地址:请点这里
-
java jframe图标_java-如何使用动画gif作为JFrame标题栏图标
2021-02-27 14:44:43不保证正确性或线程安全性.基本思想是拥有一个单独的线程来处理图标动画.该线程的工作是不断设置框架的图标图像.这是一个演示框架:import java.awt.EventQueue;import javax.swing.JFrame;public class ... -
如何设置WebSphere 6.0管理控制台的帐号密码?
2008-12-31 12:29:08全局安全性,勾选“启用全局安全性”,注意“强制 Java 2 安全性”不要选中,其它几项缺省即可,确定后保存。 </li><li>重启WebSphere,再次进入管理控制台“http://localhost:9060/admin”,会弹出一个关于安全证书... -
java httponly设置_请问怎么判断一个cookie是否为httponly?
2021-02-27 21:08:40将cookie设置成HttpOnly是为了防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性,即便是这样,也不要将重要信息存入cookie。如何在Java中设置cookie是HttpOnly呢?Servlet 2.5 API 不支持 cookie设置... -
多线程编程-如何安全的取消线程
2020-06-06 14:17:16以前的什么stop() suspend()方法都已经别取消了,那如何安全的终止线程呢? 我们在多线程编程中,总是会终止线程,那该如何操作呢? 今天看《Java并发编程艺术》这本书,在书中学到一种终止线程的方法。 通过设置... -
如何为cookie设置HttpOnly
2015-05-20 12:41:25将cookie设置成HttpOnly是为了防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性,即便是这样,也不要将重要信息存入cookie。 如何在Java中设置cookie是HttpOnly呢? Servlet 2.5 API 不支持 cookie设置... -
java sqlserver2014_java SE 如何连接sql server2014?
2021-02-28 09:16:51首先,在连接数据库之前,必须保证SQL Server 2014是采用SQL Server身份验证方式而不是...右击对象资源管理器下的第一行的MATTJASON\SQLEXPRESS(视具体机器名而变),选择属性,打开后选择安全性,选择好后确认并关... -
java win7 管理员权限_如何让 Win7 下的桌面应用程序以管理员权限运行
2021-02-28 11:30:42Vista 和 Windows 7 操作系统为了加强安全,增加了 ...这个机制大大增强了系统的安全性,但对应用程序开发者来说,我们不能强迫用户去关闭UAC,但有时我们开发的应用程序又需要以 Administrator 的方式运行,即 Wi... -
使用WebSphere Studio为Web Services客户端设置安全连接
2020-06-29 01:46:25现在,基于HTTP的安全性机制是确保Web服务安全的最佳方法。 本文介绍了如何为Java TM Web服务客户端设置安全性,以便它与服务器建立SSL连接。 为了验证Web服务客户端,在Web服务请求的HTTP标头中包含用户ID和密码。 ... -
wshttpbinding java_java - WCF Java客户端与使用wsHttpBinding签名的服务器进行通信 - 堆栈内存溢出...
2021-02-26 09:04:38我需要在Java中创建一个WCF客户端,它应该与WCF服务器通信,该服务器使用带有wsHttpBinding和证书的消息安全性。 目前我只需要签署消息(因此不需要加密)。 我设法创建一个Java客户端,以便在没有实现消息安全性时与... -
Java 如何连接数据库SQL sever2008
2015-09-19 20:57:402.打开安全性——登录名。右击登录名,选择新建登录名。会出现登录名——新建对话框。 3.在选择页一栏,常规一项,填写登录名、选择SQL Serve身份验证、填写密码、去除强制密码过期。 4.权限设置:在服 -
semaphore-demo-java-spring:使用Java Spring的Semaphore演示CICD管道-源码
2021-01-30 10:08:46使用Spring Data JPA设置该项目的持久性,并使用m2数据库,该数据库是运行时数据库,可简化测试和持续集成,但是与Postgres等许多标准数据库技术完全兼容。 只有一个数据库实体和一个对应的存储库 测验 测试分为两... -
问九:如何设计一个线程安全的HashMap?
2019-04-18 10:56:341. 将所有public方法都加上synchronized: 相当于设置了一把全局锁,所有操作都需要先获取锁(比如直接使用Collections.synchronizedMap()方法对其进行加锁操作),java.util.HashTable就是这么做的,性能很低 ... -
沙箱Java代码
2020-05-09 08:05:39在上一篇文章中,我们研究了如何保护... Java中支持沙箱的安全性设施是java.lang.SecurityManager 。 默认情况下,Java在没有SecurityManager情况下运行,因此您应在应用程序中添加代码以启用以下代码: Syste... -
Java封装
2020-03-13 21:17:091.提高代码的安全性 2.提高代码的复用性 三、如何对代码进行封装 (1)成员变量变为private,生成setter方法,为成员变量赋值;生成getter方法获取成员变量的值 private int age; public void setAg... -
如何利用response.addHeader()方法设置cookie
2014-07-02 14:44:14将cookie设置成HttpOnly是为了防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性,即便是这样,也不要将重要信息存入cookie。 如何在Java中设置cookie是HttpOnly呢? Servlet 2.5 API 不支持 cookie设置... -
Java多线程
2021-01-22 17:30:15如何设置线程的优先级? 多线程下的线程控制 休眠线程 阻塞线程 礼让线程 守护线程 中断线程 小练习:复制2个文件,先复制大文件,再复制小文件 线程不安全 同步代码块 同步方法与静态同步方法 Java中类的线程安全与... -
因为,sharding-jdbc 是一款轻量级 Java 框架,以 jar 包形式提供服务,不要我们做额外的运维工作,并且兼容性也很好。 负载均衡 负载均衡系统通常用于将任务比如用户请求处理分配到多个服务器处理以提高网站、...
-
因为,sharding-jdbc 是一款轻量级 Java 框架,以 jar 包形式提供服务,不要我们做额外的运维工作,并且兼容性也很好。 负载均衡 负载均衡系统通常用于将任务比如用户请求处理分配到多个服务器处理以提高网站、...
-
超级有影响力霸气的Java面试题大全文档
2012-07-18 09:47:04它是基于Java的远程方法调用(RMI)技术的,所以EJB可以被远程访问(跨进程、跨计算机)。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中,EJB客户从不直接访问真正的EJB组件,而是通过其容器访问。EJB容器是... -
java 封装
2017-06-29 19:13:39封装把对象的所有组成... 适当的封装可以让程式码更容易理解和维护,也加强了程式码的安全性。 public class Ti me { //封装的好处 //设置成private属性,不允许直接调用赋值,只能通过方法。 private int hour; -
java面试宝典
2013-02-28 16:04:0175、socket通信(tcp/udp区别及JAVA的实现方式) 18 76、什么是java序列化,如何实现java序列化? 18 77、简述synchronized和java.util.concurrent.locks.Lock的异同 ? 18 78、abstract class Name { private ... -
Java经典入门教程pdf完整版
2020-09-03 17:00:26Classloader能够加强代似的安全性,主要方式是:把本机上的类和內络资源类相分离, 在调入类的时候进行检查,因而可以限制任何“特洛伊木马”的应用。 4:字节码(byte-code)校验 功能是对 class文件的代码进行校验,保证... -
Java并发编程实战
2013-06-20 16:49:53Java并发编程实战 本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册。书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的...16.3 初始化过程中的安全性287 附录A 并发性标注289