liuhao163.github.io

杂七杂八


  • Home

  • Categories

  • Tags

  • Archives

  • Sitemap

RUST学习-生命周期

Posted on 2023-01-07 | In rust , 基础

RUST的生命周期是指RUST的编译器用来处理悬垂引用的规则

什么是悬垂引用:指生命周期外的变量,引用了本生命周期外的数据,因为这时候被引用的数据在离开被引用的作用域会被回收掉,导致最终引用了一个空值,而RUST的编译器是不允许这种行为产生的

RUST对函数生命周期的检查是通过一个组件引用检查器实现的

几种引用的说明

见下面函数

1
2
3
4
5
6
7
fn largest(x: &str, y: &str) -> &str {
if x.len() > y.len() {
return x;
} else {
return y;
}
}

由于res离开函数largest就被回收掉了所以也无法编译通过会报错:cannot return reference to local variable res

1
2
3
4
fn largest(x: &str) -> &String {
let res=String::from("a");
return &res;
}

largest是无法编译通过的会报错:missing lifetime specifier,因为返回值的生命周期无法判断返回的是a还是b,导致编译错误,解决方案需要手动声明函数引用的生命周期,注意泛型里的’a

1
2
3
4
5
6
7
fn largest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
return x;
} else {
return y;
}
}

函数的泛型+声明周期

生命周期是特殊的泛型需要这么声明

1
2
3
4
5
6
7
8

fn largest<'a, T: PartialOrd>(x: &'a T, y: &'a T) -> &'a T {
if x > y {
return x;
} else {
return y;
}
}

RUST省略生命周期的方案

我们在编写函数一般不会手动指定生命周期那么,RUST是如何来推断出函数的生命周期呢,主要是遵循以下规则:

首先RUST的函数分为输入生命周期(引用参数),和输出生命周期(返回值),如果通过输入生命周期无法推断出输出生命周期就会报错,那么如何推导出输出生命周期呢需要遵循以下三个原则:

  1. 如果没有手动指定生命周期,且有多个引用参数,则每个引用参数有不同的生命周期
  2. 如果只有一个输入生命周期,则可以把当前生命周期赋予输出生命周期
  3. 如果是一个函数即第一个参数是&self或者&mut self,则可以把&self或者&mut self的输入生命周期赋予给输出生命周期

RUST学习-trait

Posted on 2023-01-05 | In rust , 基础

rust的trait很想是java的interface

如何定义、实现一个trait

通过关键字pub trait XXX{},trait里定义的方法如果没有默认实现,可以没有方法体,那么所有实现该trait的strut的必须实现该方法,如果提供默认方法。

1
2
3
4
5
6
7
8
9
10
11
//定义trait 并且定义了trait的函数,给出了默认实现
pub trait Summary {
//默认实现类似于Java的Abstact模板类
fn sumarize_auther(&self) -> String {
return "unknown".to_string();
}

fn sumarize(&self) -> String {
return format!("(Read mor form {}...)", &self.sumarize_auther());
}
}

实现该trait其中NewsArticle和Tweet都实现了Summary这个trait

  • 需要注意的是如果要实现trait,必须满足trait或者类型是在当前pakcage下定义的【为了安全防止你的代码别别人破坏或者别人破坏你的代码】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub auther: String,
pub content: String,
}

//实现trait
impl Summary for NewsArticle {
fn sumarize(&self) -> String {
return format!("{},by {} ({})", self.headline, self.auther, self.location);
}
}

pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}

impl Summary for Tweet {
fn sumarize(&self) -> String {
return format!("{}:{}", self.username, self.content);
}
}

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn main() {
let news_article = &NewsArticle {
headline: String::from("头条"),
location: String::from("北京"),
auther: String::from("ericliu"),
content: String::from("学习RUST的trait"),
};

println!("新闻摘要:{}", news_article.sumarize());
println!("新闻 break_info:{}", news_article.break_info());

let tweet = &Tweet {
username: "ericliu".to_string(),
content: String::from("学习RUST的trait"),
reply: false,
retweet: false,
};

println!("推文摘要:{}", tweet.sumarize());
println!("推文 break_info:{}", tweet.break_info());
}

trait bound

我们在定义方法如果用到泛型的时候可以通过trait bound的概念对泛型进行一些定义,类似java中的这种用法,看例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fn main() {
println!("get_largest {}",get_largest(vec![1,-1,100,-100,200]));
println!("get_largest2 {}",get_largest2(vec!["hello".to_string(),"world".to_string(),"a".to_string()]));
}

//只有实现了PartialOrd的泛型所指的类型T
//T实现了Copy的Trait,一般指对象声明在Stack上否则let mut res = v[0];会报错Cannet move
fn get_largest<T:PartialOrd+Copy>(v: Vec<T>) -> T {
let mut res = v[0];
for &item in v.iter() {
if res < item {
res = item;
}
}
return res;
}

//只有实现了PartialOrd的泛型所指的类型T
//T实现了Copy的Trait,一般指对象声明在Stack上否则let mut res = v[0];会报错Cannet move
fn get_largest2<T:PartialOrd+Clone>(v: Vec<T>) -> T {
let mut res = v[0].clone();
for item in v.iter() {
if res < item.clone() {
res = item.clone();
}
}
return res;
}

trait可以对泛型的实现进行限制

该场景主要用于加强某些trait的行为,比如说让所有实现了traitA的类型具有B的功能,具体实现方法见下面,典型的例子是rust标准库中实现了Display的函数都实现了ToString这个trait

1
2
3
4
5
6
7
8
9
10
pub trait BreakNews {
fn break_info(&self) -> String;
}

//通过trait限制泛型的类型:给所有实现Summary的泛型实现break_info
impl<T: Summary> BreakNews for T {
fn break_info(&self) -> String {
return format!("break_info :{}", self.sumarize());
}
}

RUST学习-集合

Posted on 2023-01-04 | Edited on 2023-01-05 | In rust , 基础

vec

类似于java类型的List和golang的切片

创建方法

俩种方法一种是Vec::new,一种是vec!宏

1
2
let v: Vec<i32> = Vec::new();
let mut v = vec![1, 2, 3, 4];

更新

vec.push(v);

get

索引访问和match访问,索引访问会出现数组越界的painc,而match返回值是Option,因为有None的存在所以不会出现数组月觉的patinc

1
2
3
4
5
6
7
8
9
10
11
println!("out of index 100 {}",v[100]);//panicked at 'index out of bounds: the len is 4 but the index is 100'


match v.get(2) {
Some(v)=>{println!("v.get this element is {}",v)},
None=>{println!("v.get out of index")}
}
match v.get(100) {
Some(v)=>{println!("v.get this element is {}",v)},
None=>{println!("v.get out of index")}
}

遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
//遍历vector,第一次遍历用可变应用可以修改里面的值
let mut v1=vec![100,50,2];
for i in &mut v1{
*i=*i+40;
}

for i in &v1{
println!("{}",i)
}

for (index,i) in v1.iter().enumerate(){
println!("index {} v {}", index,i);
}

String

rust在处理字符串时候常用&str和String俩种类型,其中&str是字符串字面值,String是标准库提供的

常用函数/宏

  • String::from将str构造成String
  • format!通过占位符”{}”,格式化
  • String实际上是char类型的数组,所以可以采用切片方式访问即:&s4[0..4],
1
2
3
4
5
6
7
8
9
10
11
let s="hello";//这是&str类型
let s1=String::from("Hello");//这是string
let s2=String::from(" World!");
let s4=format!("{}{}",s1,s2);
println!("{}",s4);

println!("{}",&s4[0..4]);//

for c in s4.chars(){
println!("{}",c);
}

HashMap

创建方法

1
2
3
4
let mut hash_map = HashMap::new();

//拉链方法,需要显示制定类型
let hash_map: HashMap<_, _> = keys.iter().zip(values).collect();

更新

insert

1
2
3
4
hash_map.insert("blue", 1);

//entry可以做到没有在插入
hash_map.entry(String::from("yellow")).or_insert(50);

get

get

1
2
3
4
match hash_map.get("yellow") {
Some(v) => println!("match Some {}", v),
None => println!("key empty"),
}

遍历

1
2
3
for (k,v) in hash_map{
println!("{},{}",k,v);
}

RUST学习-代码组成基本结构

Posted on 2023-01-04 | Edited on 2023-01-05 | In rust , 基础

rust的代码组成结构分为package->cratio->module

每个rust的程序都包含入口文件main.rs,模块导入文件lib.rs

rust的包导入use关键字

在rs文件顶端用use关键字可以导入依赖,另外rust的函数、strut、method默认都是私有的,如果在别的模块和文件需要使用要显示的在定义处加pub

pub use的用法:re-export重导出

  • 因为 lib.rs里有pub use front_of_hosting::hosting
  • 所以main.rs里能直接 my_project::hosting,省略掉路径front_of_hosting而直接导出了hosting
  • 也可以写相对路径my_project::front_of_hosting::hosting
  • 也可以re-export函数,即在lib.rs pub use front_of_hosting::serveing::take_order,这里可以直接调用my_project::take_order

lib.rs

1
2
pub use front_of_hosting::hosting;
pub use front_of_hosting::serving::take_order;

main.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use my_project::eat_at_restaurant;
use my_project::front_of_hosting::hosting;
use my_project::hosting as pub_hosting;
use my_project::take_order;

fn main() {
/**\
pub use的用法:re-export重导出
- 因为 lib.rs里有pub use front_of_hosting::hosting
- 所以main.rs里能直接 my_project::hosting,省略掉路径front_of_hosting而直接导出了hosting
- 也可以写相对路径my_project::front_of_hosting::hosting
- 也可以re-export函数,即在lib.rs pub use front_of_hosting::serveing::take_order,这里可以直接调用my_project::take_order
*/
pub_hosting::add_to_waitlist();
hosting::add_to_waitlist();
take_order();
}

定义mod

我们在开发过程中需要自己定义mod,rust对于mod的定义有特殊的犯法,如下:

  1. 在lib.rs里声明mod front_of_hosting;注意这里是;
  2. 按照crate树状结构创建同名的.rs文件这里是front_of_hosting.rs
  3. 如果mod存在嵌套子mod的情况,比如这里front_of_hosting嵌套了serving和hosting,则在父mod的rs文件同样声明子mod,这里是mod hosting;和mod serving;注意这里是;
  4. 创建父mod文件夹,这里是front_of_hosting文件夹,在父mod文件夹里创建子mod的同名.rs文件\
  5. 在同名的mod的rs文件中编写代码

RUST学习-代码组织形式package、crate、mod

Posted on 2022-12-27 | In rust , 基础

rust代码组织分为package、crate、mod

rust代码出于安全性考虑声明的函数、mod、默认都是私有的,如果想导出需要在他们前面加上关键字“pub”

在别的文件如果想引用mod或者函数需要使用use关键字

mod声明和内容独立成文件

mod独立成文件,需要按照crate树结构创建同名的mo文件

1.在lib.rs里声明mod front_of_hosting;注意这里是;
2.按照crate树状结构创建同名的.rs文件这里是front_of_hosting.rs
3.如果mod存在嵌套子mod的情况,比如这里front_of_hosting嵌套了serving和hosting,则在父mod的rs文件同样声明子mod,这里是mod hosting;和mod serving;注意这里是;
4.创建父mod文件夹,这里是front_of_hosting文件夹,在父mod文件夹里创建子mod的同名.rs文件\
5.在同名的mod的rs文件中编写代码

lib.rs 声明父类mod

1
pub mod front_of_hosting;

front_of_hosting.rs,这里声明了俩个子mod

1
2
3
pub mod hosting;

pub mod serving;

front_of_hosting\hosting.rs 实现mod hosting

1
2
3
4
5
6
7
pub fn add_to_waitlist() {
println!("add_to_waitlist");
}

pub fn seat_at_table() {
println!("seat_at_table");
}

front_of_hosting\serving.rs 实现mod rust

1
2
3
4
5
pub fn take_order() {}

fn server_order() {}

fn take_payment() {}

pub use

默认的use是私有的,在别的文件是无法使用这个use的,如果想使用需要在use前面也加上关键字,

一般pub use多用于重导出的场景,即:re-export

比如我们又一个mod的路径是 crate::my_project::front_of_hosting::hosting,我们在使用他的地方都需要“use my_project::front_of_hosting::hosting”,有没有简单的导出方法呢,是有的见下面

  • 因为 lib.rs里有pub use front_of_hosting::hosting
  • 所以main.rs里能直接 my_project::hosting,省略掉路径front_of_hosting而直接导出了hosting
  • 也可以写相对路径my_project::front_of_hosting::hosting
  • 也可以re-export函数,即在lib.rs pub use front_of_hosting::serveing::take_order,这里可以直接调用my_project::take_order

lib.rs

1
2
3
pub mod front_of_hosting;

pub use front_of_hosting::hosting;

main.rs
见这里use my_project::front_of_hosting::hosting 和 use my_project::hosting as pub_hosting;【为了避免重名起了个别名】

1
2
3
4
5
6
7
8
9
use my_project::front_of_hosting::hosting;
use my_project::hosting as pub_hosting;


fn main() {

pub_hosting::add_to_waitlist();
hosting::add_to_waitlist();
}

RUST学习-枚举

Posted on 2022-12-26 | Edited on 2023-01-05 | In rust , 基础

枚举在rust语言里是一个很强大的数据结构

定义枚举

和strut很类似,关键字是enum,同时我们也能为枚举里每个值增加不同的成员,和方法,见下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum IpAddrKind {
V4(u8, u8, u8, u8),//ipv4是4个0-255的数字
V6(String),//ipv6是一个字符串
}

impl IpAddrKind {
fn call(&self) {} //枚举的方法
}

//调用方法见下面
fn main() {
let ip_v4 = IpAddrKind::V4(127, 0, 0, 1);
let ip_v6 = IpAddrKind::V6(String::from("::1"));
ip_v4.call();
ip_v6.call();
}

match和if let

rust类似于其他编程语言开关语句(switch–case)的语法结构是match,/match必须穷举所有可能性,如果值关注几个分支,则需要_通配符,即 _ => (),

如果只助理一个分支则可以用if let,见下面,这种方式写的代码更少,缩进更少,也使用更少末班,使用if let放弃了穷举的可能,和match相比是简洁和穷尽的取舍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
fn main() {
let v = 0u8;
match v {
0 => { println!(" zero"); }
1 => { println!(" one"); }
2 => { println!(" two"); }
_ => (),//忽略其他 类似switch
}

/*
在这个例子plus_one对Option枚举里的值+1并且返回,有俩个分支一个None一个是Some,这里我们输出非None,这里可以搭配else使用
*/
let result = plus_one(Some(5));
if let Some(v) = result {
println!("plus_one :{}", v);
}else{
println!("plus_one other");
}
/*
这里我们想输出None这种分支
*/
let result = plus_one(None);
if let None = result {
println!("plus_one NONE");
}
}

如何访问枚举的成员

可以用上面的if let和match

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//用Match访问成员变量
fn vlue_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("{}", 1);
return 1;
}
Coin::Nickel => {
println!("{}", 5);
return 5;
}
Coin::Dime => {
println!("{}", 10);
return 10;
}
Coin::Quarter(State) => {
println!("Quarter from State:{:?}", State);
return 25;
}
}
}

fn main() {
//if let访问成员变量
let result = plus_one(Some(5));
if let Some(v) = result {
println!("plus_one :{}", v);
}else{
println!("plus_one other");
}

//vlue_in_cents函数如何访问State成员
println!("Coin result {}", vlue_in_cents(Coin::Quarter(State::Alabama)));
}

Option枚举

Option枚举是很特殊的一个枚举类型,他包含Some和None俩个值,其中None是用来处理null这种情况的,因为RUST里没有NULL。

见下面的plus_one函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
fn plus_one(input: Option<i32>) -> Option<i32> {
match input {
None => None,
Some(v) => Some(v + 1),
}
}

fn main() {
//如果只助理一个分支则可以用if let,见下面,这种方式写的代码更少,缩进更少,也使用更少末班,使用if let放弃了穷举的可能,和match相比是简洁和穷尽的取舍
/*
在这个例子plus_one对Option枚举里的值+1并且返回,有俩个分支一个None一个是Some,这里我们输出非None,这里可以搭配else使用
*/
let result = plus_one(Some(5));
if let Some(v) = result {
println!("plus_one :{}", v);
}else{
println!("plus_one other");
}
/*
这里我们想输出None这种分支
*/
let result = plus_one(None);
if let None = result {
println!("plus_one NONE");
}
}

RUST学习-strut

Posted on 2022-12-25 | Edited on 2023-01-05 | In rust , 基础

rust的类型(结构体)定义是通过strut定义

strut定义

见下面,在使用的时候,需要给所有的成员赋值,有一个简写的更新语法。

在声明struts时候,如果strut可变需要声明为mut,如果strut声明为mut,它所有的成员都是mut的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    //声明一个strut,
let imt_user1=User{
user_name:String::from("lh"),
email:String::from("lh@heyi.com"),
sign_in_account:false,
active:true
};

//struts的更新语法 注意..imt_user1
let mut user2=User{
user_name:String::from("lh2"),
email:String::from("lh2@heyi.com"),
..imt_user1
};

struct User {
user_name:String,
email:String,
sign_in_account:bool,
active:bool,
}

tuple strut

元祖结构体,可以对元祖声明称一个结构体,见下面,使用方法如下,可以通过元祖方式访问下标

1
2
3
4
5
6
7
    let black=Color(0,0,0);
let origin=Point(0,0,0);
println!("black.0 {}",black.0);
println!("origin.2 {}",origin.2);

struct Point(i32,i32,i32);
struct Color(i32,i32,i32);

strut的输出

1
2
3
4
5
6
7
8
#[derive(Debug)] //这里表示该数组可以通过{:?} {:#?}打印
struct Rectangle {
width: u32,
length: u32,
}

println!("{:?}",rect);//要加#[derive(Debug)]
println!("{:#?}",rect);//{:#?}比{:?}更易读

strut的方法和函数

函数的定义,和实现在impl块内,一个strut可以有多个impl块

strut的method:第一个参数为&self,调用方式<strut实例名>.<method的Name>

strut的函数:第一个参数非&self,调用方式<strut的名称>::<func的name>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#[derive(Debug)]
struct Rectangle {
width: u32,
length: u32,
}

impl Rectangle {
//关联函数
fn from(width:u32,length:u32)->Rectangle{
return Rectangle{
width,length
}
}

fn square(size:u32)->Rectangle{
return Rectangle{
width: size,
length: size,
}
}
}

impl Rectangle {
fn area(&self)->u32{
return self.width*self.length;
}

fn mut_area(&mut self)->u32{
return self.width*self.length;
}

fn area1(self)->u32{
return self.width*self.length;
}

fn can_hold(&self,other:&Rectangle)->bool{
return self.width>other.width && self.length>other.length;
}
}

注意:strut的方法调用,第一个参数可以是self,&self,&mut self,name在调用的时候自动帮你加上&,&mut 注意如果是rect这时候会触发实例的所有权move

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let mut rect = Rectangle {
width: 50,
length: 30,
};

println!("method : {}", rect.area());//rect自动加上了 &
println!("method : {}", rect.mut_area());//因为是&mut self,所以rect声明为mut,调用时候自动加上了&mut

impl Rectangle {
fn area(&self)->u32{
return self.width*self.length;
}

fn mut_area(&mut self)->u32{
return self.width*self.length;
}

fn area1(self)->u32{
return self.width*self.length;
}

fn can_hold(&self,other:&Rectangle)->bool{
return self.width>other.width && self.length>other.length;
}
}

RUST学习-所有权

Posted on 2022-12-24 | Edited on 2023-01-05 | In rust , 基础

本章会讲解rust的核心特性,所有权

程序运行时候都必须管理他们使用内存的方式
– java,go通过gc来管理内存
– c++ c通过显示的分配和释放内存来管理
–rust用一套所有权机制来管理,编译器咋编译时候检查的规则。因为在编译时检查所以在运行时候没有任何回收内存的开销

所有权规则

  1. 每个值都有一个变量,这个变量是这个值的所有者
  2. 每个值同时只能有一个所有者
  3. 当所有者超出作用域时候该值会被删除

    代码实例

1
2
3
//s在离开main作用域时候会通过drop()函数使其失效变量会立即交回给系统
let mut s=String::from("hello") ;
s.push_str(",world");

通过string说明所有权

s1在Stack上保存了份数据ptr,len,capaciry,其中ptr是heap上保存的数据的地址
s2在Stack上复制了份s1的数据ptr,len,capaciry,其中ptr是heap上保存的数据的地址
当s1,s2都离开作用域时候会通过drop释放堆内存数据,这时候会引起bug double free,rust为了解决这个问题引入了move的概念

move的原理:
rust的所有权机制的解法:第一不会复制heap上的数据,第二由于s1赋值给了s1,那么s1堆内存的引用会失效,s1离开作用域不会释放任何数据,当然在作用域中s1不能再被使用。

1
2
3
  let s1=String::from("hello");
// let s2=s1;
// println!("{}",s1); //这里会报错value borrowed here after move

clone的概念:相比于move连heap的值都copy过来这时候 s1是可以用的

1
let s3=s1.clone();

Stack上的所有权问题

Stack上的赋值:copy.比如整数,
Rust提供了Copy trait,当一个类型实现了Copy trait,那么旧的变量在赋值后依然可用;Drop trait 如果类型实现了该trait,就不能在实现Copy trait了

1
2
3
let x=5;
let y=x;
print!("{}",x); //这时候x是可用的

'安全基本知识-加密'

Posted on 2022-09-21 | In 经验积累 , 安全

加密算法

  1. 对称加密:首选AES-GCM-256
  2. 非对称加密:首选RSA 2048如果选择ECC首选ECC 多用于对称加密秘钥的传输
  3. 单向散列:首选HMAC-SHA256,无论使用那种算法都需要加盐
  4. 消息认证:首选HMAC-SHA256,因为它能实现消息认证,和完整性保证

传输协议

  1. 首选TLS1.2,新业务推荐直接使用TLS1.3及以上版本

口令标准

  1. 能够与SSO集成的建议继承SSO,并且配合SSO实现双因子认证或者动态口令
  2. 使用静态口令场景应该符合
    1. 不使用默认口令
    2. 不使用通用口令
    3. 口令长度不小于14位
    4. 应包含大写、消息、符号、字母、数字
    5. 用户口令
      1. 不小于8位
      2. 应包含大写、消息、符号、字母、数字
      3. 建议不上传用户口令,用户侧用慢速散列发给服务端,服务端通过加盐配合HMAS-SHA256存储

其他

  1. 使用运维提供的组件清单中的服务
  2. 禁止使用FTP、Telnet等不安全的服务q

JAVA的并发包:Lock和Conidtion

Posted on 2022-09-21 | In java , 并发

再造管程的意义

java的管程的关键字是synchronized,在1.6后对管程做了升级,引入了偏向锁,轻量级锁的概念,性能几乎和Lock相近。然而在某些场景还是没法取到Lock。
主要集中synchronized如下的几个痛点

  1. 无法主动响应中断:在synchronzed代码块中,如果已经获取了资源a,在获取b失败后,就直接进入了阻塞,无法主动响应中断,这时候遇到死锁我们是无法处理的;
  2. 无法设置过期时间:synchronzed无法设置过期时间;
  3. 无法在非阻塞情况下竞争锁资源:
1
2
3
4
void lockInterruptibly() throws InterruptedException;
void lock(long time, TimeUnit unit)
boolean tryLock(long time, TimeUnit unit)
boolean tryLock();

lock的原理

我们以ReentrantLock为例子,类似管程,每个资源都一个等待队列。它是用一个双向链表实现的等待队列,同时每个链表的节点包含一个volatile的变量state作为锁标记位:0-release,1-lock。在调用lock/unlock方法中会通过cas修改volatile来实现state的变化。他是如何来保证在上锁解锁时候的可见性的呢?我们用下面的代码说明
rtl.lock上锁–>之后value+=1–>解锁,他们在并发场景下根据HB原则.

  1. 顺序原则:线程1中:rtl.lock (HB于) value+=1 ;value+=1(HB于)rtl.unlock()
  2. volatile的原则:此情况是针对线程1的rtl.unlock已经修改state成功但是由于可见性线程2的rtl.lock也申请锁的时候是否会失败?答案是不会,因为但当线程1的rtl.unlock修改state()和线程2的lock修改sate()发生冲突时候,由于rlt.lock需要先读取state,根据volatile的原则,写HB于读,所以unlock一定HB于lock,这时候lock一定读到的是最新的值
  3. 传递性原则:由于线程1的value+=1(HB于)rtl.unlock(),所以线程1的value+=1一定(HB于)于线程2的rtl.lock()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class X {
private final Lock rtl = new ReentrantLock();
int value;
public void addOne() {
// 获取锁
rtl.lock();
try {
value+=1;
} finally {
// 保证锁能释放
rtl.unlock();
}
}
}

lock的特点

可重入锁

lock和synchronized一样都是可重入锁:即同一个线程在获取锁之后,可以重复获取锁资源。(不可重入锁:即在此尝试获取锁资源会进入阻塞)

公平锁和非公平数锁

lock可以实现贡公平锁,如:ReentrantLock的构造方法,见下面代码,具体可以看Sync的代码,如果是非公平锁,有可能最后获取资源的反而最先释放。详细可以去看
ReentrantLock内部类Sync的tryAcquire俩种实现。其中FairSync里有一个判断hasQueuedPredecessors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}

/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

Lock中的等待通知机制-Condition

管程中最主要的一个功能就是wait的等待通知机制。那么lock的等待通知机制则是Condition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ReentrantLock lock=new ReentrantLock();
Condition condition=lock.newCondition();

public void test(){

//阻塞等同于 object.wait()
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}

//阻塞等同于 object.notify() object.notifyAll();
condition.signal();
condition.signalAll();
}

操作异步转同步

有些操作本身是异步的,但是我们在使用中需要同步的场景,比如dubbo中我们的客户端通过tcp发送数据给服务端后,我们需要接收到返回值才能继续处理请求。而tcp本身就是异步的。这时候就需要同步转异步操作是如何实现的呢,见DefaultFeature,伪代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Request {

private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

private Object ret;

public Request get() {
lock.lock();
try {
//等待结果转同步
if (!isDone()) {
condition.await(1000L,TimeUnit.MILLISECONDS);
}

if (!isDone()) {
throw new RuntimeException("timeout");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}

return this;
}

private boolean isDone() {
return true;
}


public void doReceived(Object response) {
lock.lock();
try {
this.ret = response;
//在别的线程获取结果后异步通知
condition.signalAll();
} finally {
lock.unlock();
}
}
}

lock的最佳实践

永远只用于锁要修改的成员表变量
永远只在访问可变的成员标量加锁
永远不要在调用其他对象的方法加锁—因为你不确定他内部是如何实现的

推荐阅读:

Doug Lea《Java 并发编程:设计原则与模式》

12…23

Liu hao

励志当好厨子的程序员

229 posts
54 categories
81 tags
RSS
GitHub E-Mail
© 2018 – 2023 Liu hao
Powered by Hexo v3.9.0
|
Theme – NexT.Pisces v7.0.0