C#编程语言中,属性(Properties)是一种特殊的类成员,它们提供了对字段(Fields)的灵活访问。通过属性,我们可以控制对类内部数据的访问,并执行一些额外的逻辑,如数据验证或转换。C#中的属性通常是通过get和set访问器来定义的,这两个访问器分别用于读取和写入属性的值。
一、属性的基本概念
属性在C#中是一种特殊的类成员,它们提供了对私有字段的公共访问。通过属性,我们可以隐藏类的内部状态,只暴露必要的接口给类的使用者。这样,我们可以更好地控制对类内部数据的访问,确保数据的完整性和安全性。
二、get访问器
get访问器用于读取属性的值。当我们在代码中引用一个属性时,实际上是在调用该属性的get访问器。get访问器必须返回一个值,该值的类型必须与属性的声明类型相匹配。
下面是一个简单的示例,展示了一个带有get访问器的属性:
public class Person
{
private string _name; // 私有字段
public string Name // 公共属性
{
get // get访问器
{
return _name; // 返回私有字段的值
}
}
}
在上面的示例中,Name
属性通过get访问器暴露了_name
字段的值。当我们创建一个Person
对象并尝试访问其Name
属性时,实际上是在调用Name
属性的get访问器,并返回_name
字段的值。
三、set访问器
set访问器用于写入属性的值。当我们为属性赋值时,实际上是在调用该属性的set访问器。set访问器通常接受一个与属性类型相同的参数,并将其赋值给内部的私有字段。
下面是一个带有get和set访问器的属性的示例:
public class Person
{
private string _name; // 私有字段
public string Name // 公共属性
{
get // get访问器
{
return _name; // 返回私有字段的值
}
set // set访问器
{
_name=value; // 将传入的值赋给私有字段
}
}
}
在上面的示例中,Name
属性不仅可以通过get访问器读取值,还可以通过set访问器写入值。当我们为Name
属性赋值时,实际上是在调用set访问器,并将传入的值赋给_name
字段。在set访问器内部,我们使用了一个特殊的value
关键字来表示传入的值。
四、属性的使用场景
属性的使用场景非常广泛,它们可以用于控制对类内部数据的访问权限、执行数据验证、转换数据类型等。例如,我们可以在set访问器中添加一些逻辑来确保赋给属性的值是有效的,或者在get访问器中返回计算后的值而不是直接返回字段的值。
此外,属性还可以用于实现一些设计模式,如观察者模式(Observer Pattern)或依赖注入(Dependency Injection)。通过属性的灵活访问机制,我们可以更好地控制类的行为和状态。
五、总结
C#中的get和set访问器是属性访问机制的重要组成部分。它们允许我们灵活地控制对类内部数据的访问,并执行额外的逻辑。通过合理使用get和set访问器,我们可以创建出更加健壮、安全和易于使用的类库和应用程序。掌握这一机制对于深入理解C#面向对象编程至关重要。
impleDateFormat 是 Java提供的一个格式化和解析日期的工具类,日常开发中应该经常会用到,但是它是线程不安全的。
多线程公用一个 SimpleDateFormat实例 对日期进行解析或者格式化会导致程序出错,本节就讨论下它为何是线程不安全的,以及如何避免。
public class TestSimpleDateFormat {
//(1)创建单例实例
static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
//(2)创建多个线程,并启动
for (int i=0; i <10 ; ++i) {
Thread thread=new Thread(new Runnable() {
public void run() {
try {//(3)使用单例日期实例解析文本
System.out.println(sdf.parse("2017-12-13 15:17:27"));
} catch (ParseException e) {
e.printStackTrace();
}
}
});
thread.start();//(4)启动线程
}
}
代码(1)创建了SimpleDateFormat的一个实例;
代码(2)创建10个线程,每个线程都共用同一个sdf对象对文本日期进行解析,多运行几次就会抛出java.lang.NumberFormatException异常,加大线程的个数有利于该问题复现。
为了便于分析首先奉上SimpleDateFormat的类图结构:
SimpleDateFormat 类结构图
下面从代码层面看下parse方法做了什么事情:
#### parse() 方法
public Date parse(String text, ParsePosition pos)
{
//(1)解析日期字符串放入CalendarBuilder的实例calb中
.....
Date parsedDate;
try {//(2)使用calb中解析好的日期数据设置calendar
parsedDate=calb.establish(calendar).getTime();
...
}
catch (IllegalArgumentException e) {
...
return null;
}
return parsedDate;
}
## establish() 方法
Calendar establish(Calendar cal) {
...
//(3)重置日期对象cal的属性值
cal.clear();
//(4) 使用calb中中属性设置cal
...
//(5)返回设置好的cal对象
return cal;
}
public final void clear() {
for (int i=0; i < fields.length; ) {
stamp[i]=fields[i]=0; // UNSET==0
isSet[i++]=false;
}
areAllFieldsSet=areFieldsSet=false;
isTimeSet=false;
}
从上面步骤可知步骤(3)(4)(5)操作不是原子性操作。
当多个线程调用parse方法时候比如线程A执行了步骤(3)(4)也就是设置好了cal对象,在执行步骤(5)前线程B执行了步骤(3)清空了cal对象,由于多个线程使用的是一个cal对象,所以线程A执行步骤(5)返回的就可能是被线程B清空后的对象,当然也有可能线程B执行了步骤(4)被线程B修改后的cal对象。从而导致程序错误。
每次使用时候new一个SimpleDateFormat的实例,这样可以保证每个实例使用自己的Calendar实例,但是每次使用都需要new一个对象,并且使用后由于没有其它引用,就会需要被回收,开销会很大。
究其原因是因为多线程下步骤(3)(4)(5)三个步骤不是一个原子性操作,那么容易想到的是对其进行同步,让(3)(4)(5)成为原子操作,可以使用synchronized进行同步。
public class TestSimpleDateFormat {
// (1)创建单例实例
static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
// (2)创建多个线程,并启动
for (int i=0; i < 10; ++i) {
Thread thread=new Thread(new Runnable() {
public void run() {
try {// (3)使用单例日期实例解析文本
synchronized (sdf) {
System.out.println(sdf.parse("2017-12-13 15:17:27"));
}
} catch (ParseException e) {
e.printStackTrace();
}
}
});
thread.start();// (4)启动线程
}
}
}
使用同步意味着多个线程要竞争锁,在高并发场景下会导致系统响应性能下降。
每个线程只需要使用一个 SimpleDateFormat 实例相比第一种方式大大节省了对象的创建销毁开销,并且不需要对多个线程直接进行同步。
public class TestSimpleDateFormat2 {
// (1)创建threadlocal实例
static ThreadLocal<DateFormat> safeSdf=new ThreadLocal<DateFormat>(){
@Override
protected SimpleDateFormat initialValue(){
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static void main(String[] args) {
// (2)创建多个线程,并启动
for (int i=0; i < 10; ++i) {
Thread thread=new Thread(new Runnable() {
public void run() {
try {// (3)使用单例日期实例解析文本
System.out.println(safeSdf.get().parse("2017-12-13 15:17:27"));
} catch (ParseException e) {
e.printStackTrace();
}
}
});
thread.start();// (4)启动线程
}
}
}
如果线程调用多个类的其他方法,并且其他地方需要日期格式化,在线程代码中new的对象,其他地方不一定会访问得到。如果想复用的话,
【ThreadLocal 中设置的变量是线程本身变量池的值,所以只要是同一线程,在执行任何类的代码的时候都可以获取得到;只需要创建一个实例】。
就像在使用pagehelper设置分页参数时,它就是放在ThreadLocal中的,所以后续的查询调用其他类的其他方法,需要这几个值都是直接从线程本身取这个值。
不过ThreadLocal使用完其中的值后最好remove下,不然一些情况会造成内存泄露。
原文 https://cn-blogs.cn/archives/10783.html
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
名称 | 链接 | 备注 |
项目主页 | https://github.com/zq2599/blog_demos | 该项目在GitHub上的主页 |
git仓库地址(https) | https://github.com/zq2599/blog_demos.git | 该项目源码的仓库地址,https协议 |
git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
bind-service: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: NodePort
selector:
app: nginx
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30000
kubectl apply -f deployment-svc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: other-deployment
spec:
selector:
matchLabels:
app: other
replicas: 1
template:
metadata:
labels:
app: other
bind-service: none
spec:
containers:
- name: other
image: nginx:latest
ports:
- containerPort: 80
kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-544dc8b7c4-xlkj8 1/1 Running 0 2m20s
other-deployment-7659c57b9d-2slm8 1/1 Running 0 5s
tree client-go-tutorials
client-go-tutorials
├── action
│ ├── action.go
│ ├── conflict.go
│ ├── controller.go
│ ├── controller_demo.go
│ ├── label.go
│ └── list_pod.go
├── go.mod
├── go.sum
└── main.go
1 directory, 9 files
type Lable struct{}
// listPods 根据传入的selector过滤
func listPods(clientset *kubernetes.Clientset, selector labels.Selector, prefix string) {
namespace :="default"
// 查询pod列表
pods, err :=clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{
// 传入的selector在这里用到
LabelSelector: selector.String(),
})
if err !=nil {
panic(err.Error())
}
nums :=len(pods.Items)
log.Printf("[%v] 查到[%d]个pod\n", prefix, nums)
// 如果没有pod就返回了
if nums < 1 {
return
}
// 遍历列表中的每个pod
for index, pod :=range pods.Items {
log.Printf("[%v] %v. pod : %v\n", prefix, index+1, pod.Name)
}
}
func (lable Lable) DoAction(clientset *kubernetes.Clientset) error {
// 第一种: 创建Requirement对象,指定类型是Equals(等于)
equalRequirement, err :=labels.NewRequirement("app", selection.Equals, []string{"other"})
if err !=nil {
log.Println("1. create equalRequirement fail, ", err)
return err
}
selector :=labels.NewSelector().Add(*equalRequirement)
// 验证,应该只查到app等于other的pod
listPods(clientset, selector, "用Requirement创建,Equal操作")
// 第一种: 创建Requirement对象,指定类型是In,not_exists不会有任何pod匹配到
inRequirement, err :=labels.NewRequirement("app", selection.In, []string{"other", "nginx", "not_exists"})
if err !=nil {
log.Println("2. create equalRequirement fail, ", err)
return err
}
selector=labels.NewSelector().Add(*inRequirement)
// 验证,应该查到app=other的pod
listPods(clientset, selector, "用Requirement创建,In操作")
// 第二种:labels.Parse方法
parsedSelector, err :=labels.Parse("bind-service=none,app notin (not_exists)")
if err !=nil {
log.Println("3. create equalRequirement fail, ", err)
return err
}
// 验证,应该查到app=other的pod
listPods(clientset, parsedSelector, "用Parse创建")
// 第三种:labels.SelectorFromSet方法
setSelector :=labels.SelectorFromSet(labels.Set(map[string]string{"app": "nginx"}))
// 验证,应该查到app=nginx的pod
listPods(clientset, setSelector, "用SelectorFromSet创建")
// 第四种:metav1.LabelSelectorAsSelector方法
// 适用于当前环境已有资源对象的场景,可以取出LabelSelector对象来转换成labels.Selector
// 先创建一个LabelSelector
labelSelector :=&metav1.LabelSelector{
MatchLabels: map[string]string{"app": "other"},
}
// 将LabelSelector转为labels.Selector
convertSelector, err :=metav1.LabelSelectorAsSelector(labelSelector)
if err !=nil {
log.Println("4. create equalRequirement fail, ", err)
return err
}
// 验证,应该查到app=nginx的pod
listPods(clientset, convertSelector, "用LabelSelector转换")
// labels.Selector的第五种用法:用labels.Selector匹配
// 准备好一个selector
matchSelector :=labels.SelectorFromSet(labels.Set(map[string]string{"app": "nginx"}))
// 查询pod列表
pods, err :=clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{})
if err !=nil {
panic(err.Error())
}
// 遍历列表中的每个pod
for _, pod :=range pods.Items {
if matchSelector.Matches(labels.Set(pod.GetLabels())) {
log.Printf("app=nginx匹配成功[%s]\n", pod.Name)
} else {
log.Printf("app=nginx匹配失败[%s]\n", pod.Name)
}
}
return nil
}
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"args": ["-action=label"]
}
]
}
2023/03/11 19:57:53 解析命令完毕,开始加载配置文件
2023/03/11 19:57:53 加载配置文件完毕,即将执行业务 [label]
2023/03/11 19:57:53 [用Requirement创建,Equal操作] 查到[1]个pod
2023/03/11 19:57:53 [用Requirement创建,Equal操作] 1. pod : other-deployment-7b57cc4f89-bdxj8
2023/03/11 19:57:53 [用Requirement创建,In操作] 查到[2]个pod
2023/03/11 19:57:53 [用Requirement创建,In操作] 1. pod : nginx-deployment-5659dc6c45-hsx7j
2023/03/11 19:57:53 [用Requirement创建,In操作] 2. pod : other-deployment-7b57cc4f89-bdxj8
2023/03/11 19:57:53 [用Parse创建] 查到[1]个pod
2023/03/11 19:57:53 [用Parse创建] 1. pod : other-deployment-7b57cc4f89-bdxj8
2023/03/11 19:57:53 [用SelectorFromSet创建] 查到[1]个pod
2023/03/11 19:57:53 [用SelectorFromSet创建] 1. pod : nginx-deployment-5659dc6c45-hsx7j
2023/03/11 19:57:53 [用LabelSelector转换] 查到[1]个pod
2023/03/11 19:57:53 [用LabelSelector转换] 1. pod : other-deployment-7b57cc4f89-bdxj8
2023/03/11 19:57:53 执行完成
func (controllerDemo ControllerDemo) DoAction(clientset *kubernetes.Clientset) error {
setSelector :=labels.SelectorFromSet(labels.Set(map[string]string{"app": "nginx"}))
optionsModifer :=func(options *metav1.ListOptions) {
options.LabelSelector=setSelector.String()
}
podListWatcher :=cache.NewFilteredListWatchFromClient(clientset.CoreV1().RESTClient(), "pods", metav1.NamespaceDefault, optionsModifer)
// 创建ListWatch对象,指定要监控的资源类型是pod,namespace是default
// podListWatcher :=cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), "pods", v1.NamespaceDefault, fields.Everything())
// 创建工作队列
queue :=workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
// 创建informer,并将返回的存储对象保存在变量indexer中
indexer, informer :=cache.NewIndexerInformer(podListWatcher, &v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{
// 响应新增资源事件的方法,可以按照业务需求来定制,
// 这里的做法比较常见:写入工作队列
AddFunc: func(obj interface{}) {
key, err :=cache.MetaNamespaceKeyFunc(obj)
if err==nil {
queue.Add(key)
}
},
// 响应修改资源事件的方法,可以按照业务需求来定制,
// 这里的做法比较常见:写入工作队列
UpdateFunc: func(old interface{}, new interface{}) {
key, err :=cache.MetaNamespaceKeyFunc(new)
if err==nil {
queue.Add(key)
}
},
// 响应修改资源事件的方法,可以按照业务需求来定制,
// 这里的做法比较常见:写入工作队列,注意删除的时候生成key的方法和新增修改不一样
DeleteFunc: func(obj interface{}) {
// IndexerInformer uses a delta queue, therefore for deletes we have to use this
// key function.
key, err :=cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
if err==nil {
queue.Add(key)
}
},
}, cache.Indexers{})
// 创建Controller对象,将所需的三个变量对象传入
controller :=NewController(queue, indexer, informer)
// Now let's start the controller
stop :=make(chan struct{})
defer close(stop)
// 在协程中启动controller
go controller.Run(1, stop)
// Wait forever
select {}
return nil
}
*请认真填写需求信息,我们会在24小时内与您取得联系。