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小時內與您取得聯系。