Postgresql中函数详解看一篇就够了——常用函数以及使用方法

Posted theskylife

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Postgresql中函数详解看一篇就够了——常用函数以及使用方法相关的知识,希望对你有一定的参考价值。

文章目录


写在前面,当我们在用postgresql数据库进行数据取数时,难免会遇到一些数据处理上的场景,本文会针对一些常用的内置函数进行总结。

-- 查看postgresql的版本(以下任一语句都可以实现)
select version();
show server_version;

1.格式转换

1.1 格式转换符显示转换

利用双冒号可以直接进行格式转换,语法如下:
字段名或数值::数据类型
例子如下:

-- 将文本'123'转为int8类型
SELECT
    '123' :: int8 num;
-- 将文本类型字段t1转为int8类型
SELECT
    t1 :: int8 
    from temp;

1.2 利用数据类型显示转换

利用数据类型进行转换,语法如下:
数据类型数值
例子:

-- 将文本'123'转为int8类型
SELECT
    int8'123' num;

1.3 格式转换函数显示转换

利用数据转换函数cast进行转换,语法如下:
cast(字段名或数值 as 数据类型)
例子:

-- 将文本'123'转为int8类型
SELECT
    cast('123' as int4) num;
-- 将文本字段t1转为int类型
SELECT CAST( t1 AS INT ) t1_c 
FROM
    TEMP;

1.4 转换案例

-- 文本转整数
SELECT CAST
    ( '123' AS int4 );
-- 文本转浮点数字
SELECT CAST
    ( '123.34' AS DECIMAL );
SELECT CAST
    ( '123.34' AS NUMERIC );
-- 数字转文本
SELECT CAST
    ( 123 AS VARCHAR );--可变字符串
SELECT CAST
    ( - 123 AS CHAR ( 2 ) );-- 固定字符串,进行截断,将-123转为'-1'
SELECT CAST
    ( - 123 AS CHAR ( 6 ) );-- 固定字符串,进行空格填充,将-123转为'-123  '
SELECT CAST
    ( 124.94 AS TEXT );--可变字符串,将124.94转为'124.94'
SELECT
    to_char( 124.94, '999D9' );--将124.94转为'124.9',遵循四舍五入
SELECT
    to_char( 124.94, 'FM999.99' );--将124.94转为'124.94'
SELECT
    to_char( - 124.94, 'FM9999999.99' );--将-124.94转为'-124.94'
SELECT
    to_char( - 124.94, 'FM9999999.990' );--将-124.94转为'-124.940'
SELECT
    to_char( 124, '00000' );--左端用零补齐凑够5位,将124转为'00124'
SELECT
    to_char( 124, '99999' );--左端用空格补齐凑够5位,将124转为'  124'
SELECT
    to_char( - 124.945, 'FM999' );--只显示整数部分,遵循四舍五入
-- 时间戳(timestamp)转日期(date)
SELECT CAST
    ( now( ) AS DATE );--普通日期模式
-- 时间戳(timestamp)转文本
SELECT CAST
    ( now( ) AS TEXT );--不指定输出格式
SELECT
    to_char( now( ), 'yyyy-mm-dd' );--指定输出格式;
-- 文本转日期(date)
SELECT
    to_date( '2012-01-01', 'yyyy-mm-dd' );
-- 文本转时间戳(TIMESTAMP)
SELECT
    to_timestamp( '2012-01-01 12:02:01', 'yyyy-mm-dd HH24:MI:SS' );

2.数学计算

2.1 数学运算操作符

操作符描述例子结果
+2 + 35
-2 - 3-1
*2 * 36
/除(整数除法截断结果)4 / 22
%模(取余)5 % 41
^指数(从左至右结合)2.0 ^ 3.08
|/平方根|/ 25.05
||/立方根||/ 27.03
!阶乘5 !120
!!阶乘(前缀操作符)!! 5120
@绝对值@ -5.05
&按位与91 & 1511
|按位或323
#按位异或17 # 520
~按位求反~1-2
<<按位左移1 << 416
>>按位右移8 >> 22

2.2 数学运算函数

函数返回类型描述例子结果
abs(x)和输入相同绝对值abs(-12.43)12.43
cbrt(dp)double立方根cbrt(27.0)3
ceil(dp or numeric)和输入相同不小于参数的最近的整数ceil(-42.8)-42
ceiling(dp or numeric)和输入相同不小于参数的最近的整数(ceil的别名)ceiling(-95.3)-95
degrees(dp)dp把弧度转为角度degrees(0.5)28.6478897565412
div(y numeric, x numeric)numericy/x的整数商div(9,4)2
exp(dp or numeric)和输入相同指数exp(1.0)2.71828182845905
floor(dp or numeric)和输入相同不大于参数的最近的整数floor(-42.8)-43
ln(dp or numeric)和输入相同自然对数ln(2.0)0.693147180559945
log(dp or numeric)和输入相同以10为底的对数log(100.0)2
log10(dp or numeric)和输入相同以10为底的对数log10(100.0)2
log(b numeric, x numeric)numeric以b为底的对数log(2.0, 64.0)6.0000000000
mod(y, x)和参数类型相同y/x的余数mod(9,4)1
pi()dp“π”常数pi()3.14159265358979
power(a dp, b dp)dp求a的b次幂power(9.0, 3.0)729
power(a numeric, b numeric)numeric求a的b次幂power(9.0, 3.0)729
radians(dp)dp把角度转为弧度radians(45.0)0.785398163397448
round(dp or numeric)和输入相同圆整为最接近的整数round(42.4)42
round(v numeric, s int)numeric圆整为s位小数数字round(42.4382, 2)42.44
scale(numeric)integer参数的精度(小数点后的位数)scale(8.41)2
sign(dp or numeric)和输入相同参数的符号(-1, 0, +1)sign(-8.4)-1
sqrt(dp or numeric)和输入相同平方根sqrt(2.0)1.4142135623731
trunc(dp or numeric)和输入相同截断(向零靠近)trunc(42.8)42
trunc(v numeric, s int)numeric截断为s位小数位置的数字trunc(42.4382, 2)42.43

3.逻辑计算

3.1 逻辑操作符

postgresql中的逻辑操作符,有以下三种:
AND
OR
NOT

3.2 比较操作符

操作符描述
<小于
>大于
<=小于等于
>=大于等于
=等于
<> or !=不等于

!=操作符在分析器阶段被转换成<>

3.3 比较谓词

谓词描述
a BETWEEN x AND y在x和y之间
a NOT BETWEEN x AND y不在x和y之间
a BETWEEN SYMMETRIC x AND y在对比较值排序后位于x和y之间
a NOT BETWEEN SYMMETRIC x AND y在对比较值排序后不位于x和y之间
a IS DISTINCT FROM b不等于,空值被当做一个普通值
a IS NOT DISTINCT FROM b等于,空值被当做一个普通值
expression IS NULL是空值
expression IS NOT NULL不是空值
expression ISNULL是空值(非标准语法)
expression NOTNULL不是空值(非标准语法)
boolean_expression IS TRUE为真
boolean_expression IS NOT TRUE为假或未知
boolean_expression IS FALSE为假
boolean_expression IS NOT FALSE为真或者未知
boolean_expression IS UNKNOWN值为未知
boolean_expression IS NOT UNKNOWN为真或者为假

3.4 比较函数

函数描述例子例子结果
num_nonnulls(VARIADIC “any”)返回非空参数的数量num_nonnulls(0, NULL, 1 ,2 ,3)4
num_nulls(VARIADIC “any”)返回空参数的数量num_nulls(0, NULL, 1 ,2 ,3)1

4.字符串及相关匹配函数

函数返回类型描述例子结果
string || stringtext串接‘Hello’ || ‘Word’‘HelloWord’
string || non-string or non-string || stringtext使用一个非字符串输入的串接'Value: ’ || 42Value: 42
bit_length(string)int串中的位数bit_length(‘Hello’)40
char_length(string) or character_length(string)int串中字符数char_length(‘Hello’)4
lower(string)text将字符串转换为小写形式lower(‘Hello’)hello
overlay(string placing string from int [for int])text替换子串,for后面是指替换的位数overlay(‘Hexxx,word’ placing ‘llo’ from 3 for 4)Helloword
position(substring in string)int定位指定子串位置,可利用值是否大于0来判断是否包含子串position(‘lo’ in ‘hello’)4
substring(string [from int] [for int])text提取子串substring(‘hello’ from 1 for 3)hel
substring(string from pattern)text提取匹配POSIX正则表达式的子串substring(‘hello’ from ‘^…’)hel
substr(string, from [, count])text提取子串substr(‘Hello’, 1, 3)hel
trim([leading | trailing | both] [characters] from string)text从string的开头、结尾或者两端(both是默认值)移除只包含characters(默认是一个空格)中字符的最长字符串trim(both ‘Hes’ from ‘sHehelloeHs’)hello
trim([leading | trailing | both] [from] string [, characters] )texttrim()的非标准版本trim(both from ‘hhHellohh’, ‘h’) 或trim(‘hhHellohh’, ‘h’)Tom
upper(string)text将字符串转换成大写形式upper(‘hello’)HELLO
concat(str “any” [, str “any” [, …] ])text串接所有参数的文本表示。NULL 参数被忽略。concat(‘abcde’, 2, NULL, 22)abcde222
concat_ws(sep text, str “any” [, str “any” [, …] ])text将除了第一个参数外的其他参数用分隔符串接在一起。第一个参数被用作分隔符字符串。NULL 参数被忽略。concat_ws(‘,’, ‘abcde’, 2, NULL, 22)abcde,2,22
left(str text, n int)text返回字符串中的前n个字符。当n为负时,将返回除了最后|n|个字符之外的所有字符。left(‘abcde’, 2)ab
length(string)intstring中的字符数length(‘hello’)5
length(string bytea, encoding name )intstring在给定编码中的字符数。string必须在这个编码中有效。length(‘hello’, ‘UTF8’)5
lpad(string text, length int [, fill text])text将string通过前置字符fill(默认是一个空格)填充到长度length。如果string已经长于length,则它被(从右边)截断。lpad(‘hi’, 5, ‘ab’)abahi
ltrim(string text [, characters text])text从string的开头删除最长的只包含characters(默认是一个空格)的串ltrim(‘zzzytest’, ‘xyz’)test
regexp_match(string text, pattern text [, flags text])text[]返回一个POSIX正则表达式与string的第一个匹配得到的子串。regexp_match(‘foobarbequetarz’, ‘(foo)(bar)’)一行:foo,bar
regexp_matches(string text, pattern text [, flags text])setof text[]返回一个POSIX正则表达式与string匹配得到的子串regexp_matches(‘foobarbequetarz’, ‘.ar’, ‘g’)两行:bar tar
regexp_replace(string text, pattern text, replacement text [, flags text])text替换匹配一个POSIX正则表达式的子串。regexp_replace(‘Hello’, ‘l+.’, ‘r’)Her
regexp_split_to_array(string text, pattern text [, flags text ])text[]使用一个POSIX正则表达式作为分隔符划分string。regexp_split_to_array(‘hello world’, ‘\\s+’)一行:hello,world
regexp_split_to_table(string text, pattern text [, flags text]) setoftext使用一个POSIX正则表达式作为分隔符划分string。regexp_split_to_table(‘hello world’, ‘\\s+’)两行:hello world
repeat(string text, number int)text重复string指定的number次repeat(‘he’, 3)hehehe
replace(string text, from text, to text)text将string中出现的所有子串from替换为子串toreplace(‘hello’, ‘ello’, ‘is’)his
reverse(str)text返回反转的字符串reverse(‘abcde’)edcba
right(str text, n int)text返回字符串中的最后n个字符。如果n为负,返回除最前面的|n|个字符外的所有字符。right(‘abcde’, 2)de
rpad(string text, length int [, fill text])text将string通过增加字符fill(默认为一个空格)填充到长度length。如果string已经长于length则它会被截断。rpad(‘hi’, 5, ‘xy’)hixyx
rtrim(string text [, characters text])text从string的结尾删除最长的只包含characters(默认是一个空格)的串rtrim(‘testxxzx’, ‘xyz’)test
split_part(string text, delimiter text, field int)text按delimiter划分string并返回给定域(从1开始计算)split_part(‘you!hello!world!’, ‘!’, 2)hello
strpos(string, substring)int指定子串的位置(和position(substring in string)相同,但是注意相反的参数顺序)strpos(‘hello’, ‘o’)5
starts_with(string, prefix)bool如果string以prefix开始则返回真。starts_with(‘alphabet’, ‘alph’)t

5.时间与日期函数

5.1时间类操作符

操作符例子结果
+date ‘2001-09-28’ + integer ‘7’date ‘2001-10-05’
+date ‘2001-09-28’ + interval ‘1 hour’timestamp ‘2001-09-28 01:00:00’
+date ‘2001-09-28’ + time ‘03:00’timestamp ‘2001-09-28 03:00:00’
+interval ‘1 day’ + interval ‘1 hour’interval ‘1 day 01:00:00’
+timestamp ‘2001-09-28 01:00’ + interval ‘23 hours’timestamp ‘2001-09-29 00:00:00’
+time ‘01:00’ + interval ‘3 hours’time ‘04:00:00’
-- interval ‘23 hours’interval ‘-23:00:00’
-date ‘2001-10-01’ - date ‘2001-09-28’integer ‘3’ (days)
-date ‘2001-10-01’ - integer ‘7’date ‘2001-09-24’
-date ‘2001-09-28’ - interval ‘1 hour’timestamp ‘2001-09-27 23:00:00’
-time ‘05:00’ - time ‘03:00’interval ‘02:00:00’
-time ‘05:00’ - interval ‘2 hours’time ‘03:00:00’
-timestamp ‘2001-09-28 23:00’ - interval ‘23 hours’timestamp ‘2001-09-28 00:00:00’
-interval ‘1 day’ - interval ‘1 hour’interval ‘1 day -01:00:00’
-timestamp ‘2001-09-29 03:00’ - timestamp ‘2001-09-27 12:00’interval ‘1 day 15:00:00’
*900 * interval ‘1 second’interval ‘00:15:00’
*21 * interval ‘1 day’interval ‘21 days’
*double precision ‘3.5’ * interval ‘1 hour’interval ‘03:30:00’
/interval ‘1 hour’ / double precision ‘1.5’interval ‘00:40:00’

5.2 时间、日期类函数

函数返回类型描述例子结果
age(timestamp, timestamp)interval减去参数,生成一个使用年、月(而不是只用日)的“符号化”的结果age(timestamp ‘2001-04-10’, timestamp ‘1957-06-13’)43 years 9 mons 27 days
age(timestamp)interval从current_date(在午夜)减去age(timestamp ‘1957-06-13’),假如今日为2022-06-1465 years 1 day
clock_timestamp()timestamp with time zone当前日期和时间(在语句执行期间变化)clock_timestamp()2022-06-14 19:06:54.034672+08
current_datedate当前日期current_date2022-06-14
current_timetime with time zone当前时间(一天中的时间),带时区current_time19:11:04.336139+08
current_timestamptimestamp with time zone当前日期和时间(当前事务开始时),带时区current_timestamp2022-06-14 19:11:57.83455+08
date_part(text, timestamp)double precision获得子域(等价于extract)date_part(‘hour’, timestamp ‘2022-06-14 20:38:40’)20
date_part(text, interval)double precision获得子域(等价于extract)date_part(‘month’, interval ‘2 years 3 months’)3
date_trunc(text, timestamp)timestamp截断到指定精度date_trunc(‘hour’, timestamp ‘2022-06-14 20:38:40’)2022-06-14 20:00:00
date_trunc(text, timestamp with time zone, text)timestamp with time zone在指定的时区截断到指定的精度ddate_trunc(‘day’, timestamptz ‘2022-06-14 20:38:40+00’, ‘Australia/Sydney’)2022-06-14 22:00:00+08
date_trunc(text, interval)interval截断到指定精度date_trunc(‘hour’, interval ‘2 days 3 hours 40 minutes’)2 days 03:00:00
extract(field from timestamp)double precision获得子域extract(MINUTE from timestamp ‘2022-06-14 20:38:40’)38
extract(field from interval)double precision获得子域extract(month from interval ‘2 years 3 months’)3
isfinite(date)boolean测试有限日期(不是+/-无限)isfinite(date ‘2022-06-14’)true(实际缩写为t)
isfinite(timestamp)boolean测试有限时间戳(不是+/-无限)isfinite(timestamp ‘2022-06-14 21:28:30’)true(实际缩写为t)
isfinite(interval)boolean测试有限间隔isfinite(interval ‘2 minutes’)true(实际缩写为t)
justify_days(interval)interval调整间隔这样30天时间周期可以表示为月justify_days(interval ‘35 days’)1 mon 5 days
justify_hours(interval)interval调整间隔这样24小时时间周期可以表示为日justify_hours(interval ‘27 hours’)1 day 03:00:00
justify_interval(interval)interval使用justify_days和justify_hours调整间隔,使用额外的符号调整justify_interval(interval ‘1 mon -1 hour’)29 days 23:00:00
localtimetime当前时间(一天中的时间),不带时区localtime19:21:14.958286
localtimestamptimestamp当前日期和时间(当前事务的开始),不带时区LOCALTIMESTAMP2022-07-22 19:23:54.073462
make_date(year int, month int, day int)date从年、月、日域创建日期make_date(2022, 7, 15)2022-07-15
make_interval(years int DEFAULT 0, months int DEFAULT 0, weeks int DEFAULT 0, days int DEFAULT 0, hours int DEFAULT 0, mins int DEFAULT 0, secs double precision DEFAULT 0.0)interval从年、月、周、日、时、分、秒域创建intervalmake_interval(days => 10)10 days
make_time(hour int, min int, sec double precision)time从时、分、秒域创建时间make_time(8, 15, 23.5)08:15:23.5
make_timestamp(year int, month int, day int, hour int, min int, sec double precision)timestamp从年、月、日、时、分、秒域创建时间戳make_timestamp(2013, 7, 15, 8, 15, 23.5)2013-07-15 08:15:23.5
make_timestamptz(year int, month int, day int, hour int, min int, sec double precision, [ timezone text ])timestamp with time zone从年、月、日、时、分、秒域创建带时区的时间戳。如果没有指定timezone, 则使用当前时区。make_timestamptz(2022, 6, 14, 19, 30, 50.5)2022-06-14 19:30:50.5+08
now()timestamp with time zone当前日期和时间(当前事务的开始),带时区now()2022-07-22 19:28:15.804042+08
statement_timestamp()timestamp with time zone当前日期和时间(当前语句的开始),在一个事务的第一条命令期间返回值与CURRENT_TIMESTAMP相同statement_timestamp()2022-07-22 19:31:35.75589+08
timeofday()text当前日期和时间(像clock_timestamp,但是作为一个text字符串)timeofday()Fri Jul 22 19:35:19.000959 2022 CST
transaction_timestamp()timestamp with time zone当前日期和时间(当前事务的开始);等同于CURRENT_TIMESTAMPtransaction_timestamp()2022-07-22 19:34:02.369665+08
to_timestamp(double precision)timestamp with time zone把 Unix 时间(从 1970-01-01 00:00:00+00 开始的秒)转换成 timestampto_timestamp(1655211000)2022-06-14 20:50:00+08

6.数组函数

6.1 数组操作符

操作符描述例子结果
=等于ARRAY[1.1,2.1,3.1]::int[] = ARRAY[1,2,3]t
<>不等于ARRAY[1,2,3] <> ARRAY[1,2,4]t
<小于ARRAY[1,2,3] < ARRAY[1,2,4]t
>大于ARRAY[1,4,3] > ARRAY[1,2,4]t
<=小于等于ARRAY[1,2,3] <= ARRAY[1,2,3]t
>=大于等于ARRAY[1,4,3] >= ARRAY[1,4,3]t
@>包含ARRAY[1,4,3] @> ARRAY[3,1,3]t
<@被包含ARRAY[2,2,7] <@ ARRAY[1,7,4,2,6]t
&&重叠(具有公共元素)ARRAY[1,4,3] && ARRAY[2,1]t
||数组和数组串接ARRAY[1,2,3] || ARRAY[4,5,6]1,2,3,4,5,6
||数组和数组串接ARRAY[1,2,3] || ARRAY[[4,5,6],[7,8,9]]1,2,3,4,5,6,7,8,9
||元素到数组串接3 || ARRAY[4,5,6]3,4,5,6
||数组到元素串接ARRAY[4,5,6] || 74,5,6,7

6.2 数组函数

函数返回类型描述例子结果
array_append(anyarray, anyelement)anyarray向一个数组的末端追加一个元素array_append(ARRAY[1,2], 3)1,2,3
array_cat(anyarray, anyarray)anyarray连接两个数组array_cat(ARRAY[1,2,3], ARRAY[4,5])1,2,3,4,5
array_ndims(anyarray)int返回数组的维度数array_ndims(ARRAY[[1,2,3], [4,5,6]])2
array_dims(anyarray)text返回数组的维度的文本表示array_dims(ARRAY[[1,2,3], [4,5,6]])[1:2][1:3]
array_fill(anyelement, int[], [, int[]])anyarray返回一个用提供的值和维度初始化好的数组,可以选择下界不为1array_fill(6, ARRAY[3], ARRAY[5])[5:7]=6,6,6
array_length(anyarray, int)int返回被请求的数组维度的长度array_length(array[1,2,3], 1)3
array_lower(anyarray, int)int返回被请求的数组维度的下界array_lower(‘[0:2]=1,2,3’::int[], 1)0
array_position(anyarray, anyelement [, int])int返回在该数组中从第三个参数指定的元素开始或者第一个元素开始(数组必须是一维的)、第二个参数的第一次出现的下标array_position(ARRAY[‘sun’,‘mon’,‘tue’,‘wed’,‘thu’,‘fri’,‘sat’], ‘mon’)2
array_positions(anyarray, anyelement)int[]返回在第一个参数给定的数组(数组必须是一维的)中,第二个参数所有出现位置的下标组成的数组array_positions(ARRAY[‘A’,‘A’,‘B’,‘A’], ‘A’)1,2,4
array_prepend(anyelement, anyarray)anyarray向一个数组的首部追加一个元素array_prepend(1, ARRAY[2,3])1,2,3
array_remove(anyarray, anyelement)anyarray从数组中移除所有等于给定值的所有元素(数组必须是一维的)array_remove(ARRAY[1,2,3,2], 2)1,3
array_replace(anyarray, anyelement, anyelement)anyarray将每一个等于给定值的数组元素替换成一个新值array_replace(ARRAY[1,2,5,4], 5, 3)1,2,3,4
array_to_string(anyarray, text [, text])text使用提供的定界符和可选的空串连接数组元素array_to_string(ARRAY[1, 2, 3, NULL, 5], ‘,’, ‘*’)‘1,2,3,*,5’
array_upper(anyarray, int)int返回被请求的数组维度的上界array_upper(ARRAY[1,8,3,7], 1)4
cardinality(anyarray)int返回数组中元素的总数,如果数组为空则返回0cardinality(ARRAY[[1,2],[3,4]])4
string_to_array(text, text [, text])text[]使用提供的定界符和可选的空串将字符串划分成数组元素string_to_array(‘a-b-c-d-e-g-’, ‘-’, ‘’)a,b,c,d,e,g,NULL
unnest(anyarray)setof anyelement将一个数组扩展成一组行unnest(ARRAY[1,2])2行:1 2

7.范围函数

7.1 范围操作符

操作符描述例子结果
=等于int4range(1,5) = ‘[1,4]’::int4ranget
<>不等于numrange(1.1,2.2) <> numrange(1.1,2.3)t
<小于int4range(1,10) < int4range(2,3)t
>大于int4range(1,10) > int4range(1,5)t
<=小于等于numrange(1.1,2.2) <= numrange(1.1,2.2)t
>=大于等于numrange(1.1,2.2) >= numrange(1.1,2.0)t
@>包含范围int4range(2,4) @> int4range(2,3)t
@>包含元素‘[2011-01-01,2011-03-01)’::tsrange @> ‘2011-01-10’::timestampt
<@范围被包含int4range(2,4) <@ int4range(1,7)t
<@元素被包含42 <@ int4range(1,7)f
&&重叠(有公共点)int8range(3,7) && int8range(4,12)t
<<严格左部int8range(1,10) << int8range(100,110)t
>>严格右部int8range(50,60) >> int8range(20,30)t
&<不超过右部int8range(1,20) &< int8range(18,20) t
&>不超过左部int8range(7,20) &> int8range(5,10)t
-|-相邻numrange(1.1,2.2) -|- numrange(2.2,3.3)t
+numrange(5,15) + numrange(10,20)[5,20)
*int8range(5,15) * int8range(10,20)[10,15)
-int8range(5,15) - int8range(10,20)[5,10)

7.2 范围函数

函数返回类型描述例子结果
lower(anyrange)范围的元素类型范围的下界lower(numrange(1.1,2.2))1.1
upper(anyrange)范围的元素类型范围的上界upper(numrange(1.1,2.2))2.2
isempty(anyrange)boolean范围为空?isempty(numrange(1.1,2.2))false
lower_inc(anyrange)boolean下界包含在内?lower_inc(numrange(1.1,2.2))true
upper_inc(anyrange)boolean上界包含在内?upper_inc(numrange(1.1,2.2))false
lower_inf(anyrange)boolean下界无限?lower_inf(‘(,)’::daterange)true
upper_inf(anyrange)boolean上界无限?upper_inf(‘(,)’::daterange)true
range_merge(anyrange, anyrange)anyrange包含两个给定范围的最小范围range_merge(‘[1,2)’::int4range, ‘[3,4)’::int4range)[1,4)

8.聚集函数

8.1 常用函数

函数参数类型返回类型部分模式描述
array_agg(expression)任何非数组类型参数类型的数组No输入值(包括空)被连接到一个数组
array_agg(expression)任意数组类型和参数数据类型相同No输入数组被串接到一个更高维度的数组中 (输入必须都具有相同的维度并且不能为空或者 NULL)
avg(expression)smallint, int, bigint、real、double precision、numeric或interval对于任何整数类型参数是numeric,对于一个浮点参数是double precision,否则和参数数据类型相同Yes所有非空输入值的平均值(算术平均)
bit_and(expression)smallint、int、bigint或bit与参数数据类型相同Yes所有非空输入值的按位与,如果没有非空值则结果是空值
bit_or(expression)smallint, int, bigint, or bit与参数数据类型相同Yes所有非空输入值的按位或,如果没有非空值则结果是空值
bool_and(expression)boolboolYes如果所有输入值为真则结果为真,否则为假
bool_or(expression)boolboolYes至少一个输入值为真时结果为真,否则为假
count(*)bigintYes输入的行数
count(expression)anybigintYesexpression值非空的输入行的数目
every(expression)boolboolYes等价于bool_and
json_agg(expression)anyjsonNo将值,包含空值,聚集成一个 JSON 数组
jsonb_agg(expression)anyjsonbNo把值,包含空值,聚合成一个 JSON 数组
json_object_agg(name, value)(any, any)jsonNo将名字/值对聚集成一个 JSON 对象,值可以为空,但不能是名字。
jsonb_object_agg(name, value)(any, any)jsonbNo把名字/值对聚合成一个 JSON 对象,值可以为空,但不能是名字。
max(expression)任意数组、数字、串、日期/时间、网络或者枚举类型,或者这些类型的数组与参数数据类型相同Yes所有非空输入值中expression的最大值
min(expression)任意数组、数字、串、日期/时间、网络或者枚举类型,或者这些类型的数组与参数数据类型相同Yes所有非空输入值中expression的最小值
string_agg(expression, delimiter)(text, text) 或 (bytea, bytea)与参数数据类型相同No非空输入值连接成一个串,用定界符分隔
sum(expression)smallint、int、 bigint、real、double precision、numeric、 interval或money对smallint或int参数是bigint,对bigint参数是numeric,否则和参数数据类型相同Yes所有非空输入值的expression的和
xmlagg(expression)xmlxmlNo连接非空XML值

8.2 统计类函数

函数参数类型返回类型部分模式描述
array_agg(expression)任何非数组类型参数类型的数组No输入值(包括空)被连接到一个数组
array_agg(expression)任意数组类型和参数数据类型相同No输入数组被串接到一个更高维度的数组中 (输入必须都具有相同的维度并且不能为空或者 NULL)
avg(expression)smallint, int, bigint、real、double precision、numeric或interval对于任何整数类型参数是numeric,对于一个浮点参数是double precision,否则和参数数据类型相同Yes所有非空输入值的平均值(算术平均)
bit_and(expression)smallint、int、bigint或bit与参数数据类型相同Yes所有非空输入值的按位与,如果没有非空值则结果是空值
bit_or(expression)smallint, int, bigint, or bit与参数数据类型相同Yes所有非空输入值的按位或,如果没有非空值则结果是空值
bool_and(expression)boolboolYes如果所有输入值为真则结果为真,否则为假
bool_or(expression)boolboolYes至少一个输入值为真时结果为真,否则为假
count(*)bigintYes输入的行数
count(expression)anybigintYesexpression值非空的输入行的数目
every(expression)boolboolYes等价于bool_and
json_agg(expression)anyjsonNo将值,包含空值,聚集成一个 JSON 数组
jsonb_agg(expression)anyjsonbNo把值,包含空值,聚合成一个 JSON 数组
json_object_agg(name, value)(any, any)jsonNo将名字/值对聚集成一个 JSON 对象,值可以为空,但不能是名字。
jsonb_object_agg(name, value)(any, any)jsonbNo把名字/值对聚合成一个 JSON 对象,值可以为空,但不能是名字。
max(expression)任意数组、数字、串、日期/时间、网络或者枚举类型,或者这些类型的数组与参数数据类型相同Yes所有非空输入值中expression的最大值
min(expression)任意数组、数字、串、日期/时间、网络或者枚举类型,或者这些类型的数组与参数数据类型相同Yes所有非空输入值中expression的最小值
string_agg(expression, delimiter)(text, text) 或 (bytea, bytea)与参数数据类型相同No非空输入值连接成一个串,用定界符分隔
sum(expression)smallint、int、 bigint、real、double precision、numeric、 interval或money对smallint或int参数是bigint,对bigint参数是numeric,否则和参数数据类型相同Yes所有非空输入值的expression的和
xmlagg(expression)xmlxmlNo连接非空XML值

8.3 有序集聚集函数

函数参数类型返回类型部分模式描述
corr(Y, X)double precisiondouble precisionYes相关系数
covar_pop(Y, X)double precisiondouble precisionYes总体协方差
covar_samp(Y, X)double precisiondouble precisionYes样本协方差
regr_avgx(Y, X)double precisiondouble precisionYes自变量的平均值 (sum(X)/N)
regr_avgy(Y, X)double precisiondouble precisionYes因变量的平均值 (sum(Y)/N)
regr_count(Y, X)double precisionbigintYes两个表达式都不为空的输入行的数目
regr_intercept(Y, X)double precisiondouble precisionYes由(X, Y)对决定的最小二乘拟合的线性方程的 y截距
regr_r2(Y, X)double precisiondouble precisionYes相关系数的平方
regr_slope(Y, X)double precisiondouble precisionYes由(X, Y)对决定的最小二乘拟合的线性方程的斜率
regr_sxx(Y, X)double precisiondouble precisionYessum(X^2) - sum(X)^2/N(自变量的“平方和”)
regr_sxy(Y, X)double precisiondouble precisionYessum(X*Y) - sum(X) * sum(Y)/N(自变量乘以因变量的“积之合”)
regr_syy(Y, X)double precisiondouble precisionYessum(Y^2) - sum(Y)^2/N(因变量的“平方和”)
stddev(expression)smallint、int、 bigint、real、double precision或numeric浮点参数为double precision,否则为numericYesstddev_samp的历史别名
stddev_pop(expression)smallint、int、 bigint、real、double precision或numeric浮点参数为double precision,否则为numericYes输入值的总体标准偏差
stddev_samp(expression)smallint、int、 bigint、real、double precision或numeric浮点参数为double precision,否则为numericYes输入值的样本标准偏差
variance(expression)smallint、int、 bigint、real、double precision或numeric浮点参数为double precision,否则为numericYesvar_samp的历史别名
var_pop(expression)smallint、int、 bigint、real、double precision或numeric浮点参数为double precision,否则为numericYes输入值的总体方差(总体标准偏差的平方)
var_samp(expression)smallint、int、 bigint、real、double precision或numeric浮点参数为double precision,否则为numericYes输入值的样本方差(样本标准偏差的平方)

8.4 有序数据集

函数直接参数类型聚集参数类型返回类型部分模式描述
mode() WITHIN GROUP (ORDER BY sort_expression)任何可排序类型与排序表达式相同No返回最频繁的输入值(如果有多个频度相同的值就选第一个)
percentile_cont(fraction) WITHIN GROUP (ORDER BY sort_expression)double precisiondouble precision或者interval与排序表达式相同No连续百分率:返回一个对应于排序中指定分数的值,如有必要就在相邻的输入项之间插值
percentile_cont(fractions) WITHIN GROUP (ORDER BY sort_expression)double precision[]double precision或者interval排序表达式的类型的数组No多重连续百分率:返回一个匹配fractions参数形状的结果数组, 其中每一个非空元素都用对应于那个百分率的值替换
percentile_disc(fraction) WITHIN GROUP (ORDER BY sort_expression)double precision一种可排序类型与排序表达式相同No离散百分率:返回第一个在排序中位置等于或者超过指定分数的输入值
percentile_disc(fractions) WITHIN GROUP (ORDER BY sort_expression)double precision[]任何可排序类型排序表达式的类型的数组No多重离散百分率:返回一个匹配fractions参数形状的结果数组, 其中每一个非空元素都用对应于那个百分率的输入值替换

8.5 假想集聚集函数(排序)

函数直接参数类型聚集参数类型返回类型部分模式描述
rank(args) WITHIN GROUP (ORDER BY sorted_args)VARIADIC “any”VARIADIC “any”bigintNo假想行的排名,为重复的行留下间隔
dense_rank(args) WITHIN GROUP (ORDER BY sorted_args)VARIADIC “any”VARIADIC “any”bigintNo假想行的排名,不留间隔
percent_rank(args) WITHIN GROUP (ORDER BY sorted_args)VARIADIC “any”VARIADIC “any”double precisionNo假想行的相对排名,范围从 0 到 1
cume_dist(args) WITHIN GROUP (ORDER BY sorted_args) VARIADIC “any”VARIADIC “any”double precisionNo假想行的相对排名,范围从 1/N 到 1

8.6 分组操作

函数返回类型描述
GROUPING(args…)integer整数位掩码指示哪些参数不被包括在当前分组集合中

使用方法举例

WITH test_table AS (
	SELECT UNNEST( ARRAY [ '财务', '行政', '销售', '财务', '行政', '行政' ] ) AS depart,
		UNNEST ( ARRAY [ 'A', 'B', 'A', 'C', 'D', 'C' ] ) AS NAME,
		UNNEST ( ARRAY [ 200, 100, 50, 30, 200, 100 ] ) AS donate 
	) SELECT
	depart,
	NAME,
	GROUPING ( depart, NAME ),
	SUM ( donate ),
	COUNT ( donate ) 
FROM
	test_table 
GROUP BY
	ROLLUP ( depart, NAME );

9.条件类函数

函数语法使用说明使用例子
caseCASE WHEN condition THEN result [WHEN …] [ELSE result] ENDCASE子句可以用于任何表达式可以出现的地方。每一个condition是一个返回boolean结果的表达式。如果结果为真,那么CASE表达式的结果就是符合条件的result,并且剩下的CASE表达式不会被处理。如果条件的结果不为真,那么以相同方式搜寻任何随后的WHEN子句。如果没有WHEN condition为真,那么CASE表达式的值就是在ELSE子句里的result。如果省略了ELSE子句而且没有条件为真,结果为空。CASE WHEN a=1 THEN ‘one’ WHEN a=2 THEN ‘two’ ELSE 'other’END
coalesceCOALESCE(value [, …])返回它的第一个非空参数的值。当且仅当所有参数都为空时才会返回空。它常用于在为显示目的检索数据时用缺省值替换空值。COALESCE(description, short_description, ‘(none)’)
nullifNULLIF(value1, value2)当value1和value2相等时,NULLIF返回一个空值。 否则它返回value1。NULLIF(value, ‘(none)’)
greatestGREATEST(value [, …])从一个任意的数字表达式列表里选取最大的数值。列表中的 NULL 数值将被忽略。只有所有表达式的结果都是 NULL 的时候,结果才会是 NULL。greatest(2,5,1)
leastGREATEST(value [, …])从一个任意的数字表达式列表里选取最小的数值。列表中的 NULL 数值将被忽略。只有所有表达式的结果都是 NULL 的时候,结果才会是 NULL。least(2,6,5)

10.窗口函数

函数返回类型描述
row_number()bigint当前行在其分区中的行号,从1计
rank()bigint带间隙的当前行排名; 与该行的第一个同等行的row_number相同
dense_rank()bigint不带间隙的当前行排名; 这个函数计数同等组
percent_rank()double precision当前行的相对排名: (rank- 1) / (总行数 - 1)
cume_dist()double precision累积分布:(在当前行之前或者平级的分区行数) / 分区行总数
ntile(num_buckets integer)integer从1到参数值的整数范围,尽可能等分分区
lag(value anyelement [, offset integer [, default anyelement ]])和value的类型相同返回value,它在分区内当前行的之前offset个位置的行上计算;如果没有这样的行,返回default替代(必须和value类型相同)。offset和default都是根

Java中Thread详解(一篇就够了)

前言

操作系统中,一个进程往往代表着一个应用程序实例,而线程是进程中轻量级的调度单元,也可以看作是轻量级的进程,可以共享进程资源。下面简单介绍在操作系统中线程通用实现方式。接下来内容主要对线程模型进行简单介绍,然后对Java线程实现Thread类进行了解。

线程模型

暂且抛开Java线程,先说明一下在操作系统中,线程通用的几种实现方式。实现线程主要有三种方式。

内核线程模型

使用内核线程实现的方式,通常也被成为1 : 1实现模型。内核线程(Kernel Level Thread,KLT)是直接由操作系统内核来支持的线程,这种线程由内核来控制切换,内核通过调度器(Scheduler)来对线程进行调度,并负责将线程任务映射到各个处理器,在多核操作系统中具有能力并行处理多个任务,这种支持多线程的内核被称为多线程内核(Mutil-Threads Kernel)。
程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口(轻量级进程Light Weight Process)LWP,也就是通常意义所描述的线程,每个轻量级进程都由一个内核支持,因此先支持内核线程,才能有轻量级进程。其中KLT、Schedule、LWP之间关系如下图所示:

 

在这个模型中,每个轻量级进程都是一个调度单元,单个KLT的阻塞不会影响其他单元的调度,当然也有其本身的局限性,由于是基于内核线程进行创建的,所以线程的各种操作,如创建、同步等都需要进行系统调用。应用程序运行在用户空间,内核处于用户态(User Mode),系统调用需要进行用户态和内核态(Kernel Mode)切换,频繁的进行用户态内核态切换,会严重消耗系统性能。此外,每个KLT都需要内核来支持,因此也会消耗内核资源如内核线程空间等,因此这种模型下所能创建的线程数也是很有限的。

用户线程模型

使用用户线程实现的方式被称为1 : N模型,非内核线程都可以被看作是用户线程(User Thread,UT)的一种。这里需要辩证的看待关于用户线程的定义,广义上来从非内核线程的角度看,轻量级进程也属于用户线程的范畴,但是由于其建立在内核基础之上,过于依赖系统调用,又不具备通常意义上用户线程的优点。其实现模型如图所示:

 

狭义上所说的用户线程指完全建立在用户空间的线程,线程的控制无需内核参与,内核也无法感知其实现模式,这种线程也不需要进行用户态和内核态的切换,因此用户线程对资源的使用率较小,支持大规模的线程数量。部分高性能数据库中的线程就是完全由这种类型的用户线程来实现。
用户线程的优势也是其劣势,由于没有内核的支持,所有线程都需要由用户程序处理。操作系统通常只会进行进程级别资源的分配,那么在用户空间,用户线程如何创建、销毁、切换、阻塞以及调度都要由用户来处理,因此用户线程实现的程序也提高了复杂度和故障机率,除非是大规模的线程需要场景下,否则一般不倾向于使用用户线程。
Java中的线程曾经使用用户线程实现,最终由于其复杂度被放弃。近年来,以高并发为特性的语言,如Golang、Erlang中使用用户线程的场景有所提升。

混合模型

基于使用内核线程和用户线程的模型,还有一种就是整合两种模型特性的混合实现模型,也被成为M : N模型。这种模型中即使用了内核线程,也使用了用户线程,通过轻量级进程作为用户线程和内核线程之间的桥梁,既可以使用内核提供的线程进行调度及处理器映射,同时也兼具了大规模用户线程并发。这种模型下用户线程和轻量级进程的数量比不固定,如下图所示:

 

在许多UNIX类操作系统,如Solaris、HP-UX等都提供了这种M : N的线程模型实现。

Java线程实现

实现模型

Java虚拟机规范中并没有定义关于线程如何实现,因此不同虚拟机厂商实现方式也没有统一标准。在JDK1.2前的线程,早期的Classic虚拟机中,基于一种被称为绿色线程的用户线程来实现。但在JDK1.3之后,商用Java虚拟机普遍开始使用内核线程模型,即1 : 1模型来作为Java线程的实现。
在HotSpot虚拟机中,每个Java线程都是直接映射到操作系统的原生线程来实现,虚拟机本身并不会干预线程的创建、调度,但是可以设置线程的优先级给予操作系统建议。关于线程的冻结或者唤醒、线程由那颗CPU核心执行以及可使用的CPU执行时间片都由操作系统来完成。
当然也有特殊场景,用于JavaME的CLDC HotSpot Implementation同时支持1 : N以及特殊的混合模型。Solaris平台的HotSpot,由于操作系统的特性,因此同时支持1 : 1和N : M的两种线程模型,通过虚拟机参数设置。
总体来说,Java线程模型的实现通常依赖于所运行的操作系统内核提供的支持,在不同平台并不能达成一致,因此Java虚拟机规范也未明确的做出定义。另外,至于使用何种线程模型,其所影响的只有线程规模(线程数量)和操作成本。

调度策略

线程无论基于何种模型创建,都有其调度策略,线程的调度指的是操作系统为线程分配使用权的过程。通常调度方式包含两种,分别是协同式(Cooperative Threads Scheduling)和抢占式(Preemptive Threads Scheduling):

  1. 协同式调度

使用协同式调度方式的线程调度由其本身来控制,线程在自身工作执行完成后,主动通知系统切换到另一个线程执行,这种方式实现简单,便于控制。但是过于依赖线程本身来控制调度,如果某个线程执行任务的程序存在问题就会导致一直阻塞。

     2. 抢占式调度

使用抢占式调度方式的多线程系统,线程的调度由系统分配执行时间,线程的切换由系统决定。这种调度方式下,线程的执行时间可控,不会因单个线程问题导致应用程序堵塞。Java中所使用的线程调度策略就是抢占式,虽然整个调度基于系统来确定,但是可以通过设置优先级的方式给予操作系统一定的建议,总共包含1~10优先级,优先级越高,线程越容易被选择执行。

Thread类

Java语言是支持多线程的,一个正在运行的Java程序可以称之为一个进程(process),在每个进程里面包含多个线程,线程是进程中单一的顺序控制流,CPU在执行计算机指令的时候都是按顺序执行,但是由于其执行速度很快,可以把时间分成很细小的时间片,交替执行,线程和进程的区别在于:

  • 创建进程的开销大于创建线程的开销,进程之间的通信比线程间要难
  • 线程不能独立存在,依托于进程而存在,线程也可以看作轻量级的进程
  • 多进程的稳定性高于多线程,一个进程的运行不会影响其他进程,但线程崩溃往往会引起程序的崩溃

Thread类位于java.lang包,JDK1.0引入。在HotSpot虚拟机中,线程使用的是基于操作系统的1 : 1的内核实现模型来创建线程,线程的创建、调度、执行、销毁等由内核进行控制,调度过程通过抢占式策略进行调度。

生命周期

Java语言定了线程生命周期的6种状态,在任意时刻线程只能处于一种状态,Java中提供了特定的API,在某些场景下可以实现线程间状态的转换。这6种状态使用枚举类java.lang.Thread.State来表示,每种状态如下所示:

  • NEW(新建状态):新创建后尚未启动的线程处于这种状态
  • RUNNABLE(运行状态):Java虚拟机中处于该状态的线程,对应操作系统状态中的Running和Ready,表示该线程正在执行或者等待操作系统分配执行时间
  • BLOCKED(阻塞状态):当多个线程进行资源争抢,等待获取一个排他锁的线程会处于该状态,当线程获取到锁之后状态会变为RUNNABLE状态,其他等待锁的线程继续处于BLOCKED状态,一般被synchronized关键字修饰或者ReentrantLock包裹的代码快会触发不可到到达线程处于该状态。
  • WAITING(无限期等待状态):处于该状态的线程不会被分配执行时间,需要等待被其他线程显式唤醒,调用Thread中以下方法会触发线程处于该状态:
    • Object.wait() with no timeout
    • Thread.join() with no timeout
    • LockSupport.park()
  • TIMED_WAITING(限期等待状态):计时等待状态的线程,不会被操作系统分配执行时间,也不需要被其他线程显式唤醒,在计时结束后由操作系统自动唤醒,Thread中的以下方法会触发线程进入此状态:
    • Thread.sleep()
    • Object.wait() with timeout
    • Thread.join() with timeout
    • LockSupport.parkNanos()
    • LockSupport.parkUntil()
  • TERMINATED(终止状态):线程任务执行结束时处于这种状态

线程的生命周期以及状态转换如下图所示:

 

接下来对Thread对象的声明周期包含的六种状态进行进行简单模拟。

NEW

public class Test
    public static void main(String[] args) throws Exception
        System.out.println("Thread State is:"+new Thread().getState());
    

输出结果

Thread State is:NEW

RUNNABLE

public class Test implements Runnable
    @Override
    public void run() 
        System.out.println("Thread State is:"+Thread.currentThread().getState());
    
    public static void main(String[] args) throws Exception
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
    

输出结果

Thread State is:RUNNABLE

BLOCKED

  • 创建线程T1,T2调用start()方法,T1,T2状态均为RUNNABLE
  • 若T1获得锁,T1状态为RUNNABLE,T2状态变为BLOCKED
  • 等待T1执行完释放锁,T2获得锁,T2状态RUNNABLE
class BlockThread extends Thread 
    private String name;    //当前线程名称
    private Object lock;    //锁
    public BlockThread(Object lock,String name)
        this.lock = lock;
        this.name = name;
    
    @Override
    public void run() 
        System.out.println("Thread "+name+" State is "+Thread.currentThread().getState());
        synchronized (lock) 
            System.out.println("Thread "+name+" hold the lock");
            try 
                System.out.println("Thread "+name+" State is "+Thread.currentThread().getState());
                Thread.sleep(1000 * 10);    //抢到锁的线程执行逻辑,这里用睡眠模拟
             catch (InterruptedException e) 
                e.printStackTrace();
            
            System.out.println("Thread " + name + " release the lock");
        
    

public class Test
    public static void main(String[] args) throws Exception
        Object lock = new Object();//锁
        BlockThread t1 = new BlockThread(lock,"T1");
        BlockThread t2 = new BlockThread(lock,"T2");
        t1.start(); //线程 T1开始运行
        t2.start(); //线程 T2开始运行
        Thread.sleep(100);  //阻塞主线程,等待T1,T2抢锁
        System.out.println("Thread T1 State is " + t1.getState());  //获取T1线程状态
        System.out.println("Thread T2 State is " + t2.getState());  //获取T2线程状态
    

输出结果

Thread T1 State is RUNNABLE
Thread T1 hold the lock
Thread T1 State is RUNNABLE
Thread T2 State is RUNNABLE
Thread T1 State is TIMED_WAITING
Thread T2 State is BLOCKED
Thread T1 release the lock
Thread T2 hold the lock
Thread T2 State is RUNNABLE
Thread T2 release the lock

WAITING

class WaitingThread extends Thread 
    private Object lock;
    public WaitingThread(String name, Object lock) 
        super(name);
        this.lock = lock;
    
    
    @Override
    public void run() 
        System.out.println("Thread " + Thread.currentThread().getName()+" try to wait");
        synchronized (lock) 
            try 
                lock.wait();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

public class Test
    public static void main(String[] args) throws Exception 
        Object lock = new Object();
        WaitingThread t = new WaitingThread("T", lock);
        t.start();
        Thread.sleep(1000);
        System.out.println("Thread T State is " + t.getState());
        System.out.println("Thread "+Thread.currentThread().getName()+" State is " + Thread.currentThread().getState());
    

输出结果

Thread T try to wait
Thread T State is WAITING
Thread main State is RUNNABLE

TIMED_WAITING

线程调用sleep()方法,进入TIMED_WAITING状态

class WaitingThread extends Thread 
    private Object lock;
    public WaitingThread(String name, Object lock) 
        super(name);
        this.lock = lock;
    
    @Override
    public void run() 
        synchronized (lock) 
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

public class Test
    public static void main(String[] args) throws Exception 
        Object lock = new Object();
        WaitingThread t1 = new WaitingThread("T1", lock);
        WaitingThread t2 = new WaitingThread("T2", lock);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        System.out.println("Thread T1 State is " + t1.getState());
        System.out.println("Thread T2 State is " + t2.getState());
    

输出结果

Thread T1 State is TERMINATED
Thread T2 State is TIMED_WAITING

TERMINATED

public class Test implements Runnable
    @Override
    public void run() 

    
    public static void main(String[] args) throws Exception
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
        System.out.println("Thread State is "+thread.getState());
    

输出结果

Thread State is TERMINATED

构造方法

Java中的线程相关操作由Thread类实现,Thread类位于java.lang包,于JDK 1.0版本引入。Thread类封装了多线程操作的上层API,可执行单元代码逻辑通过实现Runnable接口,重写run方法完成,Thread类本身也实现了该接口,接口定义如下所示:

@FunctionalInterface
public interface Runnable 
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();

在JDK1.8 引入函数式编程后,Runnable接口也被@FunctionalInterface注解标记为函数式接口,支持Lambda表达式。在JDK1.8中,Thread类提供了如下构造方法:

  • Thread() :创建一个默认设置的线程对象实例
  • Thread(Runnable target) :创建一个包含可执行对象的线程实例
  • Thread(Runnable target, String name) :创建一个包含可执行对象,指定名称的线程对象
  • Thread(String name):创建一个指定名称的线程对象
  • Thread(ThreadGroup group, Runnable target) :创建一个指定线程组,包含可执行对象的线程对象实例
  • Thread(ThreadGroup group, Runnable target, String name) :创建一个指定线程组,包含可执行对象,指定线程名称的线程对象实例
  • Thread(ThreadGroup group, Runnable target, String name, long stackSize) :创建一个指定线程组、包含可执行对象、指定名称以及堆栈大小的线程对象实例
  • Thread(ThreadGroup group, String name):创建一个指定线程组,线程名称的线程实例

说明:关于线程组(ThreadGroup类),一个线程组代表一组线程。此外,一个线程组还可以包括其他线程组。线程组形成一棵树,其中除了初始线程组之外的每个线程组都有一个父级。允许线程访问有关其自己的线程组的信息,但不能访问有关其线程组的父线程组或任何其他线程组的信息。

优先级

Java中创建的线程,每个线程都有一个优先级,具有较高优先级的线程优先于具有较低优先级的线程执行。当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级。Thread类中定义了以下三个默认优先级:

// 线程所能设置的最小优先级
public final static int MIN_PRIORITY = 1;

// 创建线程的默认优先级
public final static int NORM_PRIORITY = 5;

// 线程所能设置的最大优先级
public final static int MAX_PRIORITY = 10;

创建线程

Java中如何创建一个线程Thread,可以继承Thread类、实现Runnable或者Callable接口进行显式的创建线程。需要注意的是Runnable接口只是提供了自定义任务执行单元的方法逻辑,而真正关于线程的上层API操作实现还是位于Thread类。查看Thread中重写的run方法源码:

@Override
public void run() 
    if (target != null) 
        target.run();
    

虽然Thread类实现了run方法,但是并没有做任何的特殊处理,真正的任务执行单元,会交给构造方法传入的可执行对象完成逻辑处理,这里使用的是模板方法设计模式。

关于Callable接口下面会详细介绍。

继承Thread类

通过继承Thread类,并重写run方法。代码如下:

public class Test extends Thread
    @Override
    public void run() 
        System.out.println("Thread is Created");
    
    public static void main(String[] args) 
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
        System.out.println(Thread.currentThread().getName());
    

在主线程创建新的线程,二者的执行时机由系统调度确定,因此输出结果是随机的,如下所示:

/*新建线程先执行*/
Thread is Created
main
/*主线程先执行*/
main
Thread is Created

实现Runnable接口

通过实现Runnable接口并且重写run方法来创建一个线程。代码如下

public class Test implements Runnable
    @Override
    public void run() 
        System.out.println("Thread is Created");
    
    public static void main(String[] args) 
        Test test = new Test();
        Thread thread = new Thread(test);
        thread.start();
        System.out.println(Thread.currentThread().getName());
    

输出结果

/*新建线程先执行*/
Thread is Created
main
/*主线程先执行*/
main
Thread is Created

实现Callable接口

严格来讲,参考Oracle官方文档,创建一个Thread只有这两种方式,无论实现Runable接口,还是继承Thread类,都存在一些缺陷,我们无法获得线程的执行结果,无法处理执行过程的异常,这里提供另外一种创建线程的方式。
Callable是JDK 1.5新增的接口,位于java.util.concurrent 包下,Callable接口里面定义了call方法,call方法是run方法的增强版,可以通过实现Callable接口时传入泛型来指定call方法的返回值,并且可以声明抛出异常。

@FunctionalInterface
public interface Callable<V> 
    V call() throws Exception;

Thread中无论哪一种构造方法都没有Callable类型的target,只能传入Runnable类型的target,那如何把Thread和Callable联系起来,这里就要引入Future接口和FutureTask实现类。

Future接口定义如下方法

/**尝试取消执行此任务。*/
boolean cancel(boolean mayInterruptIfRunning) 
/**等待计算完成,然后检索其结果。*/  
V get() 
/**如果需要等待最多在给定的时间计算完成,然后检索其结果(如果可用)。*/  
V get(long timeout, TimeUnit unit) 
/**如果此任务在正常完成之前被取消,则返回 true 。*/  
boolean isCancelled() 
/**返回 true如果任务已完成。*/  
boolean isDone()

查看源码,RunnableFuture作为一个过渡同时继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

public interface RunnableFuture<V> extends Runnable, Future<V> 
    void run();

查看FutureTask的类图关系

FutureTask类提供了两个构造方法

  • FutureTask(Callable callable):创建一个包含Callable可执行对象的FutureTask实例。
  • FutureTask(Runnable runnable, V result):创建一个包含Runnable可执行对象及执行结果的FutureTask实例,参数由RunnableAdapter进行适配包装。

再回到最初的问题如何将实现了Callable接口的线程类作为Thread实例的target,这里经过了以下过程

  • 创建线程类实现Callable接口,重写call方法
  • 创建FutureTask实例,将实现Callable接口的线程类实例化对象作为FutureTask的target
  • 创建Thread类,将FutureTask实例化对象作为Thread的target

Future接口和FutureTask类在中间做了一层包装,代码展示如下

public class Test implements Callable<String> 
    @Override
    public String call() throws Exception 
        System.out.println("Thread is Created");
        return "OK";
    
    public static void main(String[] args) throws Exception 
        Test test = new Test();
        FutureTask futureTask = new FutureTask(test);
        Thread thread = new Thread(futureTask);
        thread.start();
        String str = (String) futureTask.get(5,TimeUnit.SECONDS);
        System.out.println(str);
        System.out.println(Thread.currentThread().getName());
    

输出结果

Thread is Created
OK
main

native方法

Java中线程采用内核线程模型来实现用户程序中的线程,因此一些常用方法依托于虚拟机原生实现,下面介绍这些native方法的基本使用。

yield

yield方法是一个native方法,由C++底层进行关于操作系统层面的逻辑处理。yield的字面意思是退让。调用该方法会向调度程序提示当前线程愿意放弃其当前对处理器的使用,调度程序可以随意忽略此提示。
yield是一种启发式尝试,使用它可以改善线程之间的相对进展,否则会过度使用 CPU。在使用yield方法时通常有下面两种使用场景:

  • yield的使用应与详细的分析和基准测试相结合,以确保实际上具有预期的效果,但很少使用这种方法。对于调试或测试目的可能很有用,它可能有助于重现由于竞争条件导致的错误
  • 在设计并发控制结构(例如 java.util.concurrent.locks 包中的结构)时,它也可能很有用

如下代码所示

public class TestYield 
    public static void main(String[] args) 
        MyThread thread1 = new MyThread("thread-1");
        MyThread thread2 = new MyThread("thread-2");
        thread1.start();
        thread2.start();
    

    private static class MyThread extends Thread 
        public MyThread(String name) 
            super(name);
        
        @Override
        public void run() 
            for (int i = 1; i <= 5; i++) 
                if (i % 2 == 0) 
                    Thread.yield();
                    System.out.println(getName() + ":" + i);
                
            
        
    

join

join方法让一个线程加入到另一个线程之前执行,在此线程执行期间,其他线程进入阻塞状态,当然也可以指定join入参(指定执行等待的超时时间),最多等待几毫秒让该线程终止,超时0意味着永远等待。
此实现使用以this.isAlive为条件的this.wait调用循环,当线程终止时,将调用this.notifyAll方法。建议应用程序不要在Thread实例上使用wait、notify或notifyAll。如果任何线程中断了当前线程,会抛出InterruptedException异常时清除当前线程的中断状态。

public class TestJoin 
    public static void main(String[] args) throws InterruptedException 
        MyThread thread1 = new MyThread("thread-1");
        MyThread thread2 = new MyThread("thread-2");

        thread1.start();
        thread1.join();
        thread2.start();
    
    private static class MyThread extends Thread 
        public MyThread(String name) 
            super(name);
        
        @Override
        public void run() 
            for (int i = 0; i < 20; i++) 
                System.out.println(getName());
            
        
    

sleep

当调用线程的sleep方法,使当前执行的线程休眠(暂时停止执行)指定的毫秒数,取决于系统计时器和调度程序的精度和准确性。如果任何线程中断了当前线程,会抛出InterruptedException异常时清除当前线程的中断状态。

interrupt

使用interrupt方法会中断这个线程,除非当前线程正在中断自己,否则会调用该线程的checkAccess方法,这可能会导致抛出SecurityException。主要有以下几种场景:

  • 如果一个线程被Object类的wait、或者Thread的join、sleep方法调用处于阻塞状态时,那么它的中断状态会被清除并且会收到一个InterruptedException。
  • 如果该线程在InterruptibleChannel上的IO操作中被阻塞,则通道将关闭,线程的中断状态将被设置,线程将抛出 java.nio.channels.ClosedByInterruptException。
  • 如果该线程在java.nio.channels.Selector中被阻塞,则该线程的中断状态将被设置,并且它将立即从选择操作返回,可能带有非零值,就像调用了选择器的唤醒方法一样。如果前面的条件都不成立,则将设置该线程的中断状态。
public class TestInterrupt 
    public static void main(String[] args) 

        Thread thread = new Thread(() -> 
            try 
                Thread.sleep(1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        , "thread");

        thread.start();
        thread.interrupt();
    

输出结果

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.starsray.test.api.TestInterrupt.lambda$main$0(TestInterrupt.java:8)
	at java.lang.Thread.run(Thread.java:748)

其他

这里就不一一演示,列举在Thread中提供的其他方法。

  • activeCount():返回当前线程的thread group及其子组中活动线程数的估计
  • checkAccess():确定当前正在运行的线程是否有权限修改此线程
  • clone():将CloneNotSupportedException作为线程抛出无法有意义地克隆
  • currentThread():返回对当前正在执行的线程对象的引用
  • dumpStack():将当前线程的堆栈跟踪打印到标准错误流
  • enumerate(Thread[] tarray):将当前线程的线程组及其子组中的每个活动线程复制到指定的数组中
  • getAllStackTraces():返回所有活动线程的堆栈跟踪图
  • getContextClassLoader():返回此Thread的上下文ClassLoader
  • getDefaultUncaughtExceptionHandler():返回线程由于未捕获异常突然终止而调用的默认方法
  • getId():返回此线程的标识符
  • getName():返回此线程的名称
  • getPriority():返回此线程的优先级
  • getStackTrace():返回表示此线程的堆栈转储的堆栈跟踪元素数组。
  • getState():返回此线程的状态
  • getThreadGroup():返回此线程所属的线程组
  • getUncaughtExceptionHandler():返回由于未捕获的异常,此线程突然终止时调用的处理程序
  • holdsLock(Object obj):返回 true当且仅当当前线程在指定的对象上保持监视器锁
  • interrupt():中断这个线程
  • interrupted():返回当前线程是否中断
  • isAlive():返回这个线程是否活着
  • isDaemon():返回这个线程是否是守护线程
  • isInterrupted(boolean ClearInterrupted):测试某个线程是否已被中断
  • setContextClassLoader(ClassLoader cl):设置此线程的上下文ClassLoader。
  • setDaemon(boolean on):将此线程标记为 daemon线程或用户线程。
  • ...

守护线程

Java中线程除了用户工作线程外还有一种特殊的线程被称为守护线程。通过setDaemon(true)方法将此线程标记为守护线程或用户线程。当虚拟机中唯一运行的线程都是守护线程时,Java虚拟机退出。该方法必须在线程启动前调用。
守护线程最主要的作用就是服务于虚拟机中的非守护线程,比如虚拟机中的垃圾回收线程就是典型的守护线程。需要注意的是如果是在守护线程中产生的线程,那么这个线程依然是守护线程。如代码所示:

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class TestDaemon 
    private static volatile boolean restFlag = false;
    private static final int threshold = 5;

    public static void main(String[] args) throws InterruptedException, IOException 
        List<Integer> list = new ArrayList<>();
        // 工作线程
        Thread worker = new Thread(() -> 
            while (!restFlag) 
                try 
                    Thread.sleep(1000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                if (!restFlag) 
                    list.add(1);
                    System.out.println("worker is working");
                
            
            System.out.println("worker rest");
        , "work-thread");

        // 守护线程
        Thread employers = new Thread(() -> 
            while (!restFlag) 
                if (list.size() >= threshold) 
                    restFlag = true;
                    System.out.println("employers exit");
                
            
        , "employers-thread");

        employers.setDaemon(true);
        worker.start();
        employers.start();
    

总结

Java中线程使用的是与操作系统1 : 1的内核线程模型,因此线程的创建、运行等依赖于操作系统的调度。线程生命周期中包含六种状态,线程调用不同的方法可以在RUNNING、WATING、TIMED_WAITING、BLOCK之间相互转换。
这里总结一下三种创建线程的方法特点

  • 继承Thread类实现多线程:
    • 实现起来简单,而且要获取当前线程,无需调用Thread.currentThread()方法,直接使用this即可获取当前线程
    • 线程类已经继承Thread类了,就不能再继承其他类
    • 多个线程不能共享同一份资源
  • 通过实现Runnable接口或者Callable接口实现多线程:
    • 线程类只是实现了接口,还可以继承其他类
    • 多个线程可以使用同一个target对象,适合多个线程处理同一份资源的情况
    • 通过这种方式实现多线程,相较于第一类方式,编程较复杂
    • 要访问当前线程,必须调用Thread.currentThread()方法

一般推荐使用以实现接口的方式创建线程类。

以上是关于Postgresql中函数详解看一篇就够了——常用函数以及使用方法的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程?一篇就够了

iOS动画详解(学习动画看这一篇就够了)

想要弄懂GROUP BY看这一篇就够了

写了 30 多个 Go 常用文件操作的示例,收藏这一篇就够了

写了 30 多个 Go 常用文件操作的示例,收藏这一篇就够了

Java中Thread详解(一篇就够了)

(c)2006-2024 SYSTEM All Rights Reserved IT常识