jstack发现多线程死锁

在多线程的使用中,有时会碰到死锁,死锁会造成应用程序阻塞,浪费系统资源。下面探讨如何发现和解决死锁:

产生死锁

首先先写一段能够产生死锁的代码:

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
private static Object lock1=new Object();
private static Object lock2=new Object();
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
synchronized (lock1){
System.out.println("线程1获得 lock1");
SleepUtils.Sleep(1);
synchronized (lock2){
System.out.println("线程1获得 lock2");
}
}
System.out.println("线程1结束 ");
}
}.start();
new Thread() {
@Override
public void run() {
synchronized (lock2){
System.out.println("线程2获得 lock2");
SleepUtils.Sleep(1);
synchronized (lock1){
System.out.println("线程2获得 lock1");
}
}
System.out.println("线程2结束 ");
}
}.start();
}

运行上面的结果,线程会各自阻塞在同步锁的那个地方,产生了死锁,运行结果如下:

1
2
线程1获得 lock1
线程2获得 lock2

我们使用jps命令, 查询Java的线程,如下:

jps

使用jstack 16071命令查看堆栈信息

jps

从上面可以看出来,两个线程都在等待一个内存地址,这个就是我们的死锁

newFixedThreadPool线程池导致线程泄漏

现象问题

最近看到线上的项目线程数过大的报警,开始还是不知道什么原因,因为很多项目都使用同样的线程池管理代码,认为那个项目是偶然的因素造成的,后来经过分析,发现线程数每天都在增加。其他的项目由于发布导致线程会从零开始计算,所以没有那么快达到报警值。 触发报警的代码大概如下:

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
boolean start=true;
public void doSomeThing(){
ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
Thread thread = new Thread(() -> {
while (start) {
try {
if(.//判断能否执行){
Thread.sleep(100);
return;
}
executorService.execute(() -> {
try {
//....your code
} catch (Exception e) {

} finally {

}
});
} catch (Exception e) {
}
}
});
thread.start();
}

public int getMaxThreadCount(){
return ....;
}
public void stop(){
this.start=false;
}

上面的代码存在两个问题:

  1. start是个主线程的变量,在主线程修改值,子线程的while循环不会停止

    上述代码能够停止,因为在内部调用`Thread.sleep方法,导致线程内的变量刷新

  2. newFixedThreadPool 线程池没有调用shutdown方法,导致线程不会被回收。

改正方法:

  1. start 设置成线程共享变量volatile类型

  2. 在最后调用停止的时候,让线程池进行回收

修改后代码如下:

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
44
45
46

volatile boolean start=true;
ExecutorService executorService;
Thread thread ;
public void doSomeThing(){
executorService = Executors.newFixedThreadPool(nThreads);
thread = new Thread(() -> {
while (start) {
try {
if(.//判断能否执行){
Thread.sleep(100);
return;
}
executorService.execute(() -> {
try {
//....your code
} catch (Exception e) {

} finally {

}
});
} catch (Exception e) {
}
}
});
thread.start();
}

public int getMaxThreadCount(){
return ....;
}
public void stop(){
this.start=false;
if (executorService != null) {
executorService.shutdown();
executorService.awaitTermination(MaxConcurrency() * 3, TimeUnit.SECONDS);
for (int i = 0; i < 10 && !executorService.isTerminated(); i++) {
Thread.sleep(1000);
}
}
if (thread != null && thread.isAlive()) {
thread.interrupt();
thread.join(2000);
}
}

最后的疑问

线程池在最后不使用后,为什么线程没有被释放?GC为什么没有把线程池对象回收?是怎么做到的?

目前还没有找到问题的答案,等找到后回来更新。

ES高级查询

高级查询

范围查询

1
2
3
4
5
6
7
8
9
10
{ 
"query": {
"range": {
"amount": {
"gte" :1,
"lte":100
}
}
}
}

相当于 amount>=1 and amount<=100

短语查询

1
2
3
4
5
6
7
{
"query": {
"match_phrase": {
"desc": "收入"
}
}
}

script查询

script查询 可以对查询出的字段进行再次计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GET order/_search
{
"query": {
"match_all": {}
},
"script_fields": {
"test1": {
"script": {
"lang": "painless",
"source": "doc['amount'].value * 2"
}
},
"test2": {
"script": {
"lang": "painless",
"params": {
"factor": 2
},
"source": "doc['amount'].value + params.factor"
}
}
}
}

过滤和查询


区别:

Filter:在查询过程中,Filter只判断该文档是否满足条件,只有YES或者NO。
ES会对它的结果进行缓存,所以相较于Query而言Filter的速度会更快一些。

Query: 除了问YES或NO,还会问匹配的程度。

过滤查询已被弃用,并在ES 5.0中删除。现在使用bool查询代替。

bool 查询是一个组合查询,返回一个bool值。 包含must,should,filter等查询

must:相当于and,必须满足

should:相当于or,代表或者意思  

filter:是一个bool表达式。    

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"query": {
"bool": {
"must": {
"match":{"desc": "收入"}
},
"should": {
"match":{"name": "user"}
},
"filter":{
"range":{
"amount":{
"gte" :10,
"lte":50
}
}
}
}
}
}

相当于mysql中的 1=1 and ((desc like ‘%收入%’ and amount>=10 and amount<=50>) or name =’user’)

聚合

在mysql中,聚合用group by,对于聚合后的计算用sum,avg等聚合函数计算,在es中,groupby 后面的字段称为桶,sum等聚合函数称为指标。
如:

1
select sex,sum(age) from user group by sex 

上面的sql中,sex和sum都是查询的指标,sex是桶。

聚合的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"size": 0, //不显示原来数据
"aggs": {
"buckets": {
"terms": {
"field": "orderId", //需要聚合的字段,桶
"order":{"sumAmount" : "asc" }//按查询结果排序
},
"aggs": { //聚合后需要怎么处理字段,成为指标
"sumAmount": { // 字段
"sum": {
"field": "amount"
}
}
}
}
}
}

对于聚合来说,es中的聚合函数有,sum,avg,stats,max,min等,聚合方式可以归为以下几类:

  1. 单字段单指标聚合
  2. 单字段多指标聚合
  3. 多字段单指标聚合
  4. 聚合后筛选

单字段单指标聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"size": 0,
"aggs": {
"buckets": {
"terms": {
"field": "orderId",
"order":{"sumAmount.avg" : "asc" }
},
"aggs": {
"sumAmount" : {
"stats" : { "field" : "amount" }
}
}
}
}
}

单字段多指标聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"size": 0,
"aggs": {
"bucket1": {
"terms": {
"field": "orderId",
"order":{"sumAmount" : "asc" }
},
"aggs": {
"sumAmount": {
"sum": {
"field": "amount"
}
},
"avgAmount":{
"avg": {
"field": "amount"
}
}
}
}
}
}

多字段单指标聚合

对索引中的两个字段一起聚合,相当于group by xxx,yyy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"size": 0,
"aggs": {
"bulket1": {
"terms": {
"field": "orderId"
},
"aggs": {
"bulket2": {
"terms": {
"field": "name"
},
"aggs": {
"sumAmount": {
"sum": {
"field": "amount"
}
}
}
}
}
}
}
}

聚合后的筛选:

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
{
"size": 0,
"aggs": {
"groupUserId": {
"terms": {
"field": "shortTime"
},
"aggs": {
"sumAmount": {
"sum": {
"field": "amount"
}
},
"having": {
"bucket_selector": {
"buckets_path": {
"orderCount": "_count",
"sumAmount": "sumAmount"
},
"script": {
"source": "params.sumAmount >= 100 && params.orderCount >=2"
}
}
}
}
}
}
}

ES中有一个区别于传统DB的聚合方式,对索引中的两个字段分别聚合,相当于mysql中group by 'xxx', group by 'yyy',统计后的结果分布在各个桶里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"size": 0,
"aggs": {
"bulket1": {
"terms": {
"field": "shortTime"
}
},
"bulket2": {
"terms": {
"field": "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
"aggregations" : {
"bulket2" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "user",
"doc_count" : 4
},
{
"key" : "user1",
"doc_count" : 2
}
]
},
"bulket1" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : 1546905600000,
"key_as_string" : "2019-01-08T00:00:00.000Z",
"doc_count" : 3
},
{
"key" : 1546646400000,
"key_as_string" : "2019-01-05T00:00:00.000Z",
"doc_count" : 2
},
{
"key" : 1546992000000,
"key_as_string" : "2019-01-09T00:00:00.000Z",
"doc_count" : 1
}
]
}
}

也可以多个字段各自统计

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
{
"size": 0,
"aggs": {
"bucket1": {
"terms": {
"field": "orderId",
"order":{"sumAmount" : "asc" }
},
"aggs": {
"sumAmount": {
"sum": {
"field": "amount"
}
}
}
},
"bucket2": {
"terms": {
"field": "name",
"order":{"avgAmount" : "asc" }
},
"aggs": {
"avgAmount": {
"sum": {
"field": "amount"
}
}
}
}
}
}

聚合后结果如下:

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
"aggregations" : {
"bucket2" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "user1",
"doc_count" : 2,
"avgAmount" : {
"value" : 137.07
}
},
{
"key" : "user",
"doc_count" : 4,
"avgAmount" : {
"value" : 246.18
}
}
]
},
"bucket1" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "10000",
"doc_count" : 3,
"sumAmount" : {
"value" : 51.16
}
},
{
"key" : "10001",
"doc_count" : 3,
"sumAmount" : {
"value" : 332.09000000000003
}
}
]
}
}

elasticsearch快速入门

ES是什么

ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。我们可以这么去理解:

  • 分布式的实时文件存储,每个字段都被索引并可被搜索
  • 分布式的实时分析搜索引擎
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据

索引,类型,文档的含义

在搜索引擎之前,使用的基本上都是关系型的数据库(mysql,mssql,oracle),在关系型数据库中有Database,Table和row.

对于es来说,有Index ,Type,Document来与之对应,关系如下:
















关系数据库 数据库
ES Index Type Document


注意: 每个 Index (即数据库)的名字必须是小写。
Elastic 6.x 版只允许每个 Index 包含一个 Type,7.x 版将会彻底移除 Type

Index管理


创建Index

1
2
3

put /fwdatabase

成功返回:

1
2
3
4
5
6
{
"acknowledged":true,
"shards_acknowledged":true,
"index":"fwdatabase"
}

查询系统Index

1
2
3

GET /_cat/indices?v

es入门

或者用_mget返回json信息

1
GET /_mget   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"docs": [
{
"_index": "fwdatabase",
"_type": "order",
"_id": "nCYUrGgBn1FEHZKMQWsk",
"found": false
},
{
"_index": "fwdatabase1",
"_type": "order",
"_id": "myYUrGgBn1FEHZKMQWsk",
"_version": 1,
"found": true,
"_source": {}
}
]
}

删除Index

1
2
3

delete /fwdatabase

Type 创建和删除


新建type

在关系型数据库中,我们需要指定表字段和字段的类型,在ES也可以松散的定义数据,也可以订制指定的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
put  /fwdatabase/_mapping/order
{
"properties": {
"orderId": {
"type": "keyword"
},
"shortTime": {
"type": "date"
},
"name": {
"type": "keyword"
},
"amount": {
"type": "double"
},
"desc": {
"type": "text"
}
}
}

curl -X PUT -H ‘Content-type: application/json’ -d ‘{“properties”:{“orderId”:{“type”:”keyword”},”shortTime”:{“type”:”keyword”},”amount”:{“type”:”double”},”desc”:{“type”:”text”}}}’ http://localhost:9200/fwdatabase/_mapping/order

查询type

  1. 查询Index所包含的Type

    1
    2
    3

    GET /_mapping?pretty=true

    es入门

  2. 其他一些查询

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /_search
    在所有索引的所有类型中搜索

    /gb/_search
    在索引gb的所有类型中搜索

    /gb,us/_search
    在索引gb和us的所有类型中搜索

    /g*,u*/_search
    在以g或u开头的索引的所有类型中搜索

    /gb/user/_search
    在索引gb的类型user中搜索

    /gb,us/user,tweet/_search
    在索引gb和us的类型为user和tweet中搜索

    /_all/order,bill/_search
    在所有索引中搜索order,bill类型

修改type和删除type

修改Type一般可以只对数据进行局部更新,或者删除当前Index后重新创建一个新的type,此处略去不去过多的讲述。

document的管理


document相当于mysql中的行数据,我们可以通过接口对es的数据进行管理。

插入数据

  1. 单条插入数据

    1
    2
    3
    4
    5
    6
    7
    POST  /fwdatabase/order
    {
    "orderId":"10000",
    "name":"test",
    "amount":12.09,
    "desc":"测试"
    }

    插入成功返回信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    "_index": "fwdatabase", //index名称
    "_type": "order", //类型名称
    "_id": "aI9AvGgBpdtUL3hsAU2l", //生成的id
    "_version": 1,//版本号
    "result": "created",
    "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 2
    }

    插入的数据可以通过_search接口查询出来:

    1
    2
    3

    GET /fwdatabase/order/_search

    查询结果和相关注释如下:

    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
    {
    "took": 4, //耗时
    "timed_out": false,//是否超时
    "_shards": { //分片信息
    "total": 5, //分片数量
    "successful": 5,//
    "skipped": 0,
    "failed": 0
    },
    "hits": {
    "total": 1,//查询命中的记录数
    "max_score": 1,
    "hits": [
    {
    "_index": "fwdatabase",//索引名称
    "_type": "order",
    "_id": "RH0AvWgBNfbilfsxyX-V",//自增ID
    "_score": 1,//相关得分
    "_source": {//返回数据
    "orderId": "10000",
    "name": "test",
    "amount": 12.09,
    "desc": "测试"
    }
    }
    ]
    }
    }

其中 _index 和 _type是对应的索引和类型的名称,_id是系统自动指定的唯一标识的主键。也可以自己之指定:

1
2
3
4
5
6
7
8
POST  /fwdatabase/order/1 //这里指定_id的值
{
"_id":1,
"orderId":"10000",
"name":"test",
"amount":12.09,
"desc":"测试"
}

批量增加数据

批量插入数据可以减少与es的网络请求,从而加快插入的效率,调用的是_bulk的接口,写法如下:(为了方便后面的演示,在此处插入点数据):

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

{"index":{}}
{"shortTime":"2019-01-05","orderId":"10000","name":"user1","amount":12.09,"desc":"水果收入"}
{"index":{}}
{"shortTime":"2019-01-08","orderId":"10001","name":"user2","amount":110.00,"desc":"苹果收入"}
{"index":{}}
{"shortTime":"2019-01-09","orderId":"10002","name":"user3","amount":53.98,"desc":"香蕉收入"}
{"index":{}}
{"shortTime":"2019-01-08","orderId":"10003","name":"user4","amount":-53.09,"desc":"支出"}
{"index":{}}
{"shortTime":"2019-01-18","orderId":"10004","name":"user5","amount":-66.00,"desc":"转账"}
{"index":{}}
{"shortTime":"2019-01-28","orderId":"10005","name":"user6","amount":-102.50,"desc":"转账"}
{"index":{}}
{"shortTime":"2019-01-08","orderId":"10006","name":"user7","amount":32.59,"desc":"收入"}
{"index":{}}
{"shortTime":"2019-01-08","orderId":"10007","name":"user8","amount":-10.09,"desc":"支出"}
{"index":{}}
{"shortTime":"2019-01-06","orderId":"10008","name":"user9","amount":-11.50,"desc":"种子支出"}
{"index":{}}
{"shortTime":"2019-01-08","orderId":"10009","name":"user10","amount":305.49,"desc":"其他入账"}
{"index":{}}
{"shortTime":"2019-01-04","orderId":"10010","name":"user11","amount":1112.09,"desc":"分红"}
{"index":{}}
{"shortTime":"2019-01-08","orderId":"10011","name":"user12","amount":-48.45,"desc":"支出"}
{"index":{}}
{"shortTime":"2019-01-06","orderId":"10012","name":"user13","amount":-59.20,"desc":"税收支出"}

插入完成后的数据

es入门

更新数据

  1. 按id更新数据

    1
    2
    3
    4
    5
    6
    7
    POST fwdatabase/order/1 
    {
    "orderId":"10001",
    "name":"userTest",
    "amount":122.76,
    "desc":"测试更新"
    }
  2. 批量更新

    批量更新与批量插入是同一个接口,在批量插入的时候指定_id,es会自动选择插入和更新。

    1
    2
    3
    4
    5
    POST /fwdatabase/order/_bulk
    { "index": { "_id": "RH0AvWgBNfbilfsxyX-V" }}
    {"orderId":"111111", "name":"user", "amount":12.09, "desc":"测试批量更新"}
    { "index": { "_id": "Pn0AvWgBNfbilfsxyX-V" }}
    {"orderId":"222222", "name":"user", "amount":112.09, "desc":"测试批量更新"}

    es入门

  3. 局部更新

    局部更新可以增加一个document的字段,其他数据保持不变:

    1
    2
    3
    4
    5
    6
    7
    8

    POST /fwdatabase/order/PX0AvWgBNfbilfsxyX-V/_update
    {
    "doc" : {
    "tags" : "testing"
    }
    }

    id=PX0AvWgBNfbilfsxyX-V的数据增加一个tags列。

删除数据

  1. 按id删除

    1
    DELETE fwdatabase/order/1

    删除Id为1的数据。
    或者用query的查询删除也可以:

    1
    2
    3
    4
    5
    6
    7
    8
    POST  fwdatabase/order/_delete_by_query
    {
    "query": {
    "term": {
    "_id": "O30AvWgBNfbilfsxyX-V"
    }
    }
    }

    term是精确查询,后面查询会讲到

  2. 按查询结果删除

    1
    2
    3
    4
    5
    6
    POST  fwdatabase/order/_delete_by_query
    {
    "query": {
    "match_all": {}
    }
    }

查询数据


es查询分为两种,一种是参数存在与url中,形式/index/type/_search?q=_id:1
如:

1
GET /fwdatabase/order/_search?q=_id:P30AvWgBNfbilfsxyX-V

称为: Search Lite API

一种是利用POST方法提交json数据,实现查询(上面已经演示),称为:DSL查询

dsl更具有可读性,推荐使用。

  1. Search Lite API查询

    由于这种查询方式,不太场采用,只是简单介绍下,不做深入探究。

    1.1. 按ID查询

    1
    GET /index/type/_search?q=_id:1

    1.2. 查询指定的字段

    只查询orderId,amount字段。

    1
    GET  /fwdatabase/order/_search?q=_id:P30AvWgBNfbilfsxyX-V&_source=orderId,amount   

    1.3. 排序和分页

    排序:

    1
    GET  /fwdatabase/order/_search?sort=amount:desc  

    分页:

    1
    GET  /fwdatabase/order/_search?sort=amount:desc&from=0&size=2 

  1. (DSL)查询
    2.1 精确查询

    精确查询相当于mysql中的等于(=),在es中使用的term关键字

    • 查询user用户的收入和支出情况:

      1
      2
      3
      4
      5
      6
      7
      POST  /fwdatabase/order/_search
      {
      "query": {
      "term": {"name":"user"}
      }
      }

    • 查询user和user1的收支情况:

      1
      2
      3
      4
      5
      6
      7
      POST  /fwdatabase/order/_search
      {
      "query": {
      "terms": {"name":["user","user1"]}
      }
      }

    • 多个精确条件查询

      这个是需要其他高级的表达式,此处先不着急,留个问题在这。

    2.2 模糊查询

    • match

      1
      2
      3
      4
      5
      6
      7
      POST  /fwdatabase/order/_search
      {
      "query": {
      "match": {"desc":"收入"}
      }
      }

      查询出所有desc为包含收入的数据。

      如果用match下指定了一个确切值,在遇到数字,日期,布尔值或者not_analyzed 的字符串时,将是精确搜索. 

      1
      2
      3
      4
      5
      6
      POST  /fwdatabase/order/_search
      {
      "query": {
      "match": {"name":"user"}
      }
      }

    2.3 分页

    1
    2
    3
    4
    5
    6
    POST  /fwdatabase/order/_search
    {

    "from":0,
    "size":3
    }

    2.4 排序

  • 默认正序

    1
    2
    3
    4
    5
    6
    {
    "from":0,
    "size":10,
    "sort":"amount"

    }
  • 倒序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "from": 0,
    "size": 10,
    "sort": {
    "amount": {
    "order": "desc"
    }
    }
    }
  • 多条件排序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {
    "sort": [
    {
    "amount": {
    "order": "desc"
    }
    },
    {
    "orderId": {
    "order": "desc"
    }
    }
    ]
    }

Java中Integer的详解

在Java中有int和integer两种类型,简单的说Integer是int的引用类型,但是这个引用的类型比较特殊,下面看几个demo:

Integer a1 = 140;
Integer a2 = 140;
System.out.println(a1 == a2);

Integer b1 = 120;
Integer b2 = 120;
System.out.println(b1 == b2);

运行结果是:

false
true

原因: Java对于-128到127之间的数,会进行缓存,Integer i = 127时,会将127进行缓存,下次再写Integer j = 127时,就会直节从缓存中拿取,不会再从新创建对象

所以120的时候地址是一样的,运行是true,大于127的时候重新开辟吗新的地址空间,地址不一致,为false

当然我们也可以强制重新开辟一个新的变量:


Integer c1 = new Integer(120);
Integer c2 = new Integer(120);
System.out.println(c1 == c2);


Integer d1 = new Integer(120);
Integer d2 =  120;
System.out.println(d1 == d2);

运行结果是:

false
false

这样就更好的验证了我们上面的想法。

如果用Integer和int的值相比,会怎么样呢?

Integer e1 = new Integer(120);
int e2 =  120;
System.out.println(e1 == e2);



Integer f1 = new Integer(140);
int f2 =  140;
System.out.println(f1 == f2);

Integer g1 = 120;
int g2 =  120;
System.out.println(g1 == g2);

运行结果是:

true
true
true

原因: 当Integer和int对比的时候,Integer会自动拆箱成为int进行对比

从上面的结论我们得出:

  1. Integer在小于-128到127可以看似是值类型,超过这个范围后,会变成引用类型

  2. Integer与int对比的时候会自动拆箱成int,再进行对比

综合上面,我开始有个一个疑问:


Integer i1 = 140;
Integer i2 = i1;
i1++;
System.out.println(i2);
System.out.println(i2 == i1);

运行结果是:

140
false

为什么i2没有随之i1的值改变而改变?

原因: Integer在进行运算的时候回自动调用内部函数intValue().

上面的代码可以拆解成:


Integer i1 = 140;
Integer i2 = i1;
i1=Integer.valueOf(i1.intValue()+1);
System.out.println(i2);
System.out.println(i2 == i1);

根据上面的所有情况,我们可以写一个终极的值类型和引用类型的转换:


Integer i1 = 120;
Integer i2 = i1;
Integer i3=i1+1;
Integer i4=i3-1;
System.out.println(i2);
System.out.println(i2 == i4);

运行结果:

120
true

而当i1=140的时候,i2 == i4就会变成false。

位运算的整理

在计算机的世界,都是0和1 ,利用这个0和1组成了计算机的基础,数字是如何在计算机中表示的?二进制到底是什么?

数字怎么表示

在刚开始学编程的时候,有几种常见的数据基础的数据类型占用几个字节,如int 占16个字节(不同的语言体系不同,这里以C语言为例)。

一个字节可以表示两个数字0和1 ,占16个字节就可以用2^16个数字。

为了能够表示负数,单独保留一个字节作为符号位, 所以int的整型的范围是从-32768到32767 .

二进制的换算

十进制的数字是逢十进一,二进制很简单是逢二进一,比如十进制:3+9=12. 在二进制中:1+1=10.

计算二进制的方法与十进制也相同,比如在十进制中想取个十百的数字,可以分别除10的倍数。

1
2
3
4
5
6
7
8
9
10
比如取456的各位的数:       

456/10=45余6 ,

45/10=4 余5

4/10= 0余4

最终倒序返回时456

换成二进制的方法与十进制相同,唯一的区别是负数的表示方法不同。

1
2
3
4
5
6
7
8
9
10
11
12
比如获得13的二进制数据:   

13/2=6余1

6/2=3余0

3/2=1余1

1/2 余1

所以13 就是倒序 1101

负数的表示方法,取反码,然后加1

1
2
3

13的原码 1101,对应的反码是:0010,然后加1 ,0011(完整的数据:‭1111 1111 1111 1111 1111 1111 1111 0011‬)

常用的二进制的使用

二进制的运算效率高于其他计算方法,在日常的代码中,如果理解没有障碍的话,个人推荐优先使用位运算。

  1. 奇数和偶数的判断

    1
    2
    3
    4

    a&1 = 0 偶数
    a&1 = 1 奇数

  2. 取模运算

    1
    2
    3
    4
    5

    a % (2^n) 等价于 a & (2^n - 1)

    a%16 ==> a&15

  3. 求绝对值

    1
    2
    3
    4
    5
    6
    7
    8

    int abs( int x )
    {
    int y ;
    y = x >> 31 ;
    return (x^y)-y ; //or: (x+y)^y
    }

  4. 乘以2的次幂

1
2
3
4
5
6
7

10>>1=20

10>>2=40

10>>3=80

Java发送邮件(含附件)

前几天写了一个Java发送邮件的帮助类i,可以发送QQ和163的邮箱,也可以发送附件,写个一个主要的方法,其他的可以自己封装。代码如下:

引入pom:

1
2
3
4
5
6
<dependency>
<groupId>Javax.mail</groupId>
<artifactId>Javax.mail-api</artifactId>
<version>1.6.2</version>
</dependency>

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package com.pay.utils;

import com.pay.utils.enums.MailType;
import org.springframework.util.StringUtils;

import Javax.activation.*;
import Javax.mail.*;
import Javax.mail.internet.*;
import Javax.mail.util.ByteArrayDataSource;
import Java.io.*;
import Java.util.HashMap;
import Java.util.Map;
import Java.util.Properties;

public class MailSender {
private MailType mailType;
private String userName;
private String passWord;
private Properties properties;


public MailSender(MailType mailType, String userName, String passWord) {
this.mailType = mailType;
this.userName = userName;
this.passWord = passWord;
this.properties = getProperties();
}


public boolean sender(String recivers, String cc, String mailTitle, String mailContent, boolean isHtml, Map<String, byte[]> mapFile) throws MessagingException, IOException {
Session session = Session.getInstance(properties);
//2.通过session获取Transport对象(发送邮件的核心API)
Transport ts = session.getTransport();
//3.通过邮件用户名密码链接
ts.connect(properties.getProperty("mail.host"), userName, this.passWord);
//4.创建邮件
MimeMessage mm = new MimeMessage(session);
//设置发件人
mm.setFrom(new InternetAddress(userName));
Address[] address = new InternetAddress().parse(recivers);
mm.setRecipients(Message.RecipientType.TO, address);

//设置抄送人
if (!StringUtils.isEmpty(cc)) {
mm.setRecipient(Message.RecipientType.CC, new InternetAddress(cc));
}
mm.setSubject(mailTitle);
if (!isHtml) {
mailContent = String.format("<pre>%s</pre>", mailContent);
}
// mm.setContent(mailContent, "text/html;charset=utf-8");
// 创建多重消息
Multipart multipart = new MimeMultipart();

BodyPart bodyPart = new MimeBodyPart();
bodyPart.setContent(mailContent, "text/html;charset=utf-8");
multipart.addBodyPart(bodyPart);
if (mapFile != null) {
MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
mc.addMailcap("text/html;; x-Java-content-handler=com.sun.mail.handlers.text_html");
mc.addMailcap("text/xml;; x-Java-content-handler=com.sun.mail.handlers.text_xml");
mc.addMailcap("text/plain;; x-Java-content-handler=com.sun.mail.handlers.text_plain");
mc.addMailcap("multipart/*;; x-Java-content-handler=com.sun.mail.handlers.multipart_mixed");
mc.addMailcap("message/rfc822;; x-Java-content-handler=com.sun.mail.handlers.message_rfc822");
CommandMap.setDefaultCommandMap(mc);

for (Map.Entry<String, byte[]> map : mapFile.entrySet()) {
BodyPart messageBodyPart = new MimeBodyPart();
InputStream inputStream = new ByteArrayInputStream(map.getValue());
DataSource source = new ByteArrayDataSource(inputStream, "application/txt");
messageBodyPart.setDataHandler(new DataHandler(source));
messageBodyPart.setFileName(MimeUtility.encodeText(map.getKey()));
multipart.addBodyPart(messageBodyPart);
}

mm.setContent(multipart);
}
//5.发送电子邮件
ts.sendMessage(mm, mm.getAllRecipients());
return true;
}

private Properties getProperties() {
if (this.mailType.equals(MailType.m163)) {
Properties prop = new Properties();
prop.put("mail.host", "smtp.163.com");
prop.put("mail.transport.protocol", "smtp");
prop.put("mail.smtp.auth", true);
return prop;
}
if (this.mailType.equals(MailType.qq)) {
Properties prop = new Properties();
prop.setProperty("mail.host", "smtp.qq.com");
prop.setProperty("mail.transport.protocol", "smtp");
prop.setProperty("mail.smtp.auth", "true");
prop.setProperty("mail.smtp.socketFactory.class", "Javax.net.ssl.SSLSocketFactory");
prop.setProperty("mail.smtp.port", "465");
prop.setProperty("mail.smtp.socketFactory.port", "465");
return prop;
}
return null;
}

}


```

``` Java
public enum MailType {
m163, qq
}


CountDownLatch的原理

上次大概说了CountDownLatch的使用,今天说下实现的原理,CountDownLatch的使用效果和Join差不多,实现起来也比较简单。

大体的思路就是一个死循环阻塞,等到某个条件满足后就跳出循环,继续执行后面的代码。执行逻辑如下:

CountDownLatch的原理

源码分析


我们下面分析下CountDownLatch的源码:

创建CountDownLatch对象

  
  public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

创建CountDownLatch 对象很简单,就是创建一个Sync对象。 Sync的对应的代码


 Sync(int count) {
     setState(count);
   }   

protected final void setState(int newState) {
   state = newState;
}

这个可以确定给state设置了一个值。

await 方法 阻塞等待


 public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }  


 public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

 protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }


private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

doAcquireSharedInterruptibly 这个方法中有一个for(;;),这个是一个死循环, 直到tryAcquireShared 返回的r>=0.也就是state==0。

countDown 方法


   public void countDown() {
        sync.releaseShared(1);
    }

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

  

     protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }


private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

**compareAndSetWaitStatus(h, 0, Node.PROPAGATE)**这个是用CAS的方法改变state的值。

多线程中单例模式的优化

单例模式

在编程中,单例模式是我们常用的一种设计模式,功能是保证在整个系统只用一个该对象的对象,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class Singleton {
private static Singleton singleton;

private Singleton() {
}

public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
return singleton;
}
return singleton;
}
}

上面的代码我们知道并不是线程安全的,在多线程环境下,容易造成创建多个对象。 测试代码如下:

1
2
3
4
5
6
7
8
9
10
11

@Test
public void testSingleton() throws InterruptedException {
for (int i=0;i<10;i++){
new Thread(()->{
Singleton.getInstance();
}).start();
}
Thread.currentThread().join();
}

运行结果如下:

1
2
3
4
5
6
7
创建对象
创建对象
创建对象
创建对象
创建对象
创建对象
创建对象

解决方案


对于上面的问题解决的方法有很多,比如使用加锁的方式,double检测的方式,为了验证最有方案我们把代码修改下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

public class Singleton {
private static Singleton singleton;

private Singleton() {
try {
Thread.sleep(10);//增加创建对象的耗时
} catch (Exception e) {

}
}

public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
return singleton;
}
}
return singleton;
}
}

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

@Test
public void testSingleton() throws InterruptedException {
long start=System.currentTimeMillis();
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Thread thread = new Thread(() -> {
Singleton.getInstance();
});
threadList.add(thread);
thread.start();
}
for (Thread t : threadList) {
t.join();
}
long end=System.currentTimeMillis();
System.out.println("运行耗时:"+(end-start));
}


方案一:使用synchronized 关键字

1
2
3
4
5
6
7
8
9
10
public static Singleton getInstance() {
synchronized (Singleton.class){
if (singleton == null) {
System.out.println("创建对象");
singleton = new Singleton();
}
}
return singleton;
}

经过多次测试时间维持在410ms左右,下面是一次测试结果

1
2
3

运行耗时:410

这个虽然成功的保证了只有一个对象,但同样也会把其他的线程阻塞在创建的锁的前面,造成了性能上面的开销,如果创建一个对象的时间比较长,这个性能的开销是相当可观的。

方案二:double验证

1
2
3
4
5
6
7
8
9
10
11
12
13

public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
return singleton;
}
}
}
return singleton;
}

1
2
3

运行耗时:380

上面的代码虽然聪明的避开的过多线程等待的原因,但是彻底消除线程排队的现象,因为创建对象分需要耗时,这样就给其他线程提供了“可乘之机”

方案三:使用volatile共享变量 (最优方案)

共享变量是线程间同步的“轻量级锁”,彻底消除线程排队的现象,此处用于单例模式的设计,能够实现最小性能的开销:

1
2
private volatile static Singleton singleton;

1
2
3

运行耗时:280

Java多线程通信lock和wait

在Java多线程中有一对配合使用过的两个方法,来实现线程间通信的功能–lock和wait, 由于这个需要获得锁,所以必须结合synchronized一起使用。首先我们先看一个例子:


public class LockWait {
	
	static volatile List<String> itemContainer = new ArrayList<>();
	static Object obj = new Object();
	
	public static void main(String[] args) {
Thread th1 = new Thread(() -> {
	synchronized (obj) {
		for (int i = 0; i < 10; i++) {
			System.out.println("th1添加元素");
			itemContainer.add(String.valueOf(i));
			if (itemContainer.size() == 5) {
				System.out.println("th1线程发出通知");
				obj.notify();
			}
		}
	}
});

Thread th2 = new Thread(() -> {
	synchronized (obj) {
		System.out.println("进入th2线程");
		if (itemContainer.size() != 5) {
			try {
				System.out.println("th2线程开始等待");
				obj.wait();
				System.out.println("th2线程等待结束");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("th2线程结束");
		}
	}
	
});

th2.start();
th1.start();
	}
}
 

输出结果如下:

进入th2线程
th2线程开始等待
th1添加元素
th1添加元素
th1添加元素
th1添加元素
th1添加元素
th1线程发出通知
th1添加元素
th1添加元素
th1添加元素
th1添加元素
th1添加元素
th2线程等待结束
th2线程结束

具体运行逻辑如下:

Java多线程通信lock和wait

总结上面的运行结果,th2在wait的时候,th1可以持有锁。说明wait是释放锁,而notify不释放锁。

这样也就带来了一个弊端,无法实时的得到结果,就是说当List达到我们想要的结果的时候,th1线程一直还在持有锁,导致th2无法执行。

有没有更好办法呢?在Java中提供了一个CountDownLatch类:


public class CountDownLatchTest {
	
	public static void main(String[] args) {
final List<String> itemContainer = new ArrayList<>();
final CountDownLatch countDownLanch = new CountDownLatch(1);
Thread th1 = new Thread(() -> {
	for (int i = 0; i < 10; i++) {
		try {
			System.out.println("th1添加元素");
			itemContainer.add(String.valueOf(i));
			if (itemContainer.size() == 5) {
				System.out.println("th1线程发出通知");
				countDownLanch.countDown();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
});

Thread th2 = new Thread(() -> {
	System.out.println("进入th2线程");
	if (itemContainer.size() != 5) {
		try {
			System.out.println("th2线程开始等待");
			countDownLanch.await();
			System.out.println("th2线程等待结束");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("th2线程结束");
	}
	
});

th2.start();

th1.start();
	}
}


运行结果:

进入th2线程
th1添加元素
th2线程开始等待
th1添加元素
th1添加元素
th1添加元素
th1添加元素
th1线程发出通知
th1添加元素
th2线程等待结束
th1添加元素
th2线程结束
th1添加元素
th1添加元素
th1添加元素
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×