Northpark博客

Today does not walk, will have to run tomorrow.


  • 首页

  • 关于

  • 旅行

  • 微世界

  • 音乐

  • 读书

  • 归档

高并发、多线程探索-6-线程不安全类、同步容器

Posted on 2018-12-28 | In java

1、线程不安全的类

如果一个类的对象同时可以被多个线程访问,并且你不做特殊的同步或并发处理,那么它就很容易表现出线程不安全的现象。比如抛出异常、逻辑处理错误…

下面列举一下常见的线程不安全的类及对应的线程安全类:

(1)StringBuilder 与 StringBuffer

StringBuilder是线程不安全的,而StringBuffer是线程安全的。分析源码:StringBuffer的方法使用了synchronized关键字修饰。

1
2
3
4
5
6
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

(2)SimpleDateFormat 与 jodatime插件

SimpleDateFormat 类在处理时间的时候,如下写法是线程不安全的:

1
2
3
4
5
6
7
8
9
10
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");

//线程调用方法
private static void update() {
try {
simpleDateFormat.parse("20180208");
} catch (Exception e) {
log.error("parse exception", e);
}
}

但是我们可以变换其为线程安全的写法:在每次转换的时候使用线程封闭,新建变量

1
2
3
4
5
6
7
8
private static void update() {
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
simpleDateFormat.parse("20180208");
} catch (Exception e) {
log.error("parse exception", e);
}
}

另外我们也可以使用jodatime插件来转换时间:其可以保证线程安全性
Joda 类具有不可变性,因此它们的实例无法被修改。(不可变类的一个优点就是它们是线程安全的)

1
2
3
4
5
private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd");

private static void update(int i) {
log.info("{}, {}", i, DateTime.parse("20180208", dateTimeFormatter).toDate());
}
Read more »

高并发、多线程探索-3-安全发布对象与多种单例模式

Posted on 2018-12-27 | In 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
29
30
31
32
33
34
35
package cn.northpark.concurrency.publish;

import java.util.Arrays;

import cn.northpark.concurrency.annotaion.NotRecommand;
import cn.northpark.concurrency.annotaion.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

/**
* @author jeyy
* 这个代码通过public访问级别发布了类的域,在类的任何外部的线程都可以访问这些域
* 我们无法保证其他线程会不会修改这个域,从而使私有域内的值错误(上述代码中就对私有域进行了修改)
*/
@Slf4j
@NotThreadSafe
@NotRecommand
public class UnsafePublish {

//调用类的公共方法可以改变类的私有属性的值
private String[] status = {"1","2","3"};

//类的非私有方法,返回私有对象的引用
public String[] getStatus() {
return status;
}

public static void main(String[] args) {
UnsafePublish up = new UnsafePublish();
log.info("{}",Arrays.toString(up.getStatus()));

up.status[0]="999";
log.info("{}",Arrays.toString(up.getStatus()));

}
}
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
package cn.northpark.concurrency.publish;

import cn.northpark.concurrency.annotaion.NotRecommand;
import cn.northpark.concurrency.annotaion.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

/**
* @author jeyy
* <pre>
* 这个内部类的实例里面包含了对封装实例的私有域对象的引用,在对象没有被正确构造完成之前就会被发布,有可能有不安全的因素在里面,会导致this引用在构造期间溢出的错误。
* 上述代码在函数构造过程中启动了一个线程。无论是隐式的启动还是显式的启动,都会造成这个this引用的溢出。新线程总会在所属对象构造完毕之前就已经看到它了。
* 因此要在构造函数中创建线程,那么不要启动它,而是应该采用一个专有的start或者初始化的方法统一启动线程
* 这里其实我们可以采用工厂方法和私有构造函数来完成对象创建和监听器的注册等等,这样才可以避免错误
* ——————————————————————————————————————————————————-
* 如果不正确的发布对象会导致两种错误:
* (1)发布线程意外的任何线程都可以看到被发布对象的过期的值
* (2)线程看到的被发布线程的引用是最新的,然而被发布对象的状态却是过期的
* </pre>
*/
@Slf4j
@NotThreadSafe
@NotRecommand
public class Escape {

private int thisCanBeEscape = 0;

public Escape() {
new InnerClass();
}

public class InnerClass{
//内部类构造方法调用外部类的私有域
public InnerClass() {
log.info("{}",Escape.this.thisCanBeEscape);
}
}

public static void main(String[] args) {
new Escape();
}
}

安全发布对象示例(多种单例模式演示)

如何安全发布对象?共有四种方法

1、在静态初始化函数中初始化一个对象引用
2、将对象的引用保存到volatile类型域或者AtomicReference对象中
3、将对象的引用保存到某个正确构造对象的final类型域中
4、将对象的引用保存到一个由锁保护的域中

Read more »

高并发、多线程探索-5-线程封闭、threadLocal应用

Posted on 2018-12-27 | In java

1、什么是线程封闭?

它其实就是把对象封装到一个线程里,只有一个线程能看到这个对象,那么这个对象就算不是线程安全的,也不会出现任何线程安全方面的问题。

线程封闭技术有一个常见的应用:

数据库连接对应jdbc的Connection对象,Connection对象在实现的时候并没有对线程安全做太多的处理,jdbc的规范里也没有要求Connection对象必须是线程安全的。
实际在服务器应用程序中,线程从连接池获取了一个Connection对象,使用完再把Connection对象返回给连接池,由于大多数请求都是由单线程采用同步的方式来处理的,并且在Connection对象返回之前,连接池不会将它分配给其他线程。因此这种连接管理模式处理请求时隐含的将Connection对象封闭在线程里面,这样我们使用的connection对象虽然本身不是线程安全的,但是它通过线程封闭也做到了线程安全。

2、线程封闭的种类:

(1)Ad-hoc 线程封闭:

Ad-hoc线程封闭是指,维护线程封闭性的职责完全由程序实现来承担。Ad-hoc线程封闭是非常脆弱的,因为没有任何一种语言特性,例如可见性修饰符或局部变量,能将对象封闭到目标线程上。事实上,对线程封闭对象(例如,GUI应用程序中的可视化组件或数据模型等)的引用通常保存在公有变量中。

(2)堆栈封闭:

堆栈封闭其实就是方法中定义局部变量。不存在并发问题。多个线程访问一个方法的时候,方法中的局部变量都会被拷贝一份到线程的栈中(Java内存模型),所以局部变量是不会被多个线程所共享的。

(3)ThreadLocal线程封闭:

它是一个特别好的封闭方法,其实ThreadLocal内部维护了一个map,map的key是每个线程的名称,而map的value就是我们要封闭的对象。ThreadLocal提供了get、set、remove方法,每个操作都是基于当前线程的,所以它是线程安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//ThreadLocal的get方法源码
public T get() {
Thread t = Thread.currentThread();//当前线程对象
ThreadLocalMap map = getMap(t);//get操作基于当前线程
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
Read more »

高并发、多线程探索-4-不可变对象 -final -Immutable{google guava} -unmodifiable

Posted on 2018-12-27 | In java

概念

1、不可变对象

有一种对象只要它发布了就是安全的,它就是不可变对象。一个不可变对象需要满足的条件:

对象创建一个其状态不能修改
对象所有域都是final类型
对象是正确创建的(在对象创建期间,this引用没有逸出)

2、创建一个不可变对象的方法

(1)自己定义
这里可以采用的方式包括:
1、将类声明为final,这样它就不能被继承。
2、将所有的成员声明为私有的,这样就不允许直接访问这些成员。
3、对变量不提供set方法,将所有可变的成员声明为final,这样就只能赋值一次。通过构造器初始化所有成员进行深度拷贝。
4、在get方法中不直接返回对象的本身,而是克隆对象,返回对象的拷贝。

(2)使用Java中提供的Collection类中的各种unmodifiable开头的方法
(3)使用Guava中的Immutable开头的类

3、final关键字

final关键字可以修饰类、修饰方法、修饰变量

修饰类:类不能被集成。
基础类型的包装类都是final类型的类。final类中的成员变量可以根据需要设置为final,但是要注意的是,final类中的所有成员方法都会被隐式的指定为final方法
修饰方法:
(1)把方法锁定,以防任何继承类修改它的含义
(2)效率:在早期的java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不见效果。一个private方法会被隐式的指定为final方法
修饰变量:
基本数据类型变量,在初始化之后,它的值就不能被修改了。如果是引用类型变量,在它初始化之后便不能再指向另外的对象。

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
package cn.northpark.concurrency.immutable;

import java.util.Map;

import com.google.common.collect.Maps;

import cn.northpark.concurrency.annotaion.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@NotThreadSafe
public class ImmutableExample1 {

private final static Integer a = 1;
private final static String b = "2";
private final static Map<Integer, Integer> map = Maps.newHashMap();

static {
map.put(1, 2);
map.put(3, 4);
map.put(5, 6);
}

public static void main(String[] args) {
//a = 2;//报错
//b = "3";//报错
//map = Maps.newHashMap();//报错
map.put(1, 3);
log.info("{}", map.get(1));
}

private void test(final int a) {
// a = 1;
}
}

(1)对一个被final修饰的变量(Integer a、String b)被赋值时在编译过程中就出现了错误。
(2)(map)在重新被指向一个新的map对象的时候也出现了错误。
那么对被定义为final的map进行赋值呢?我们单独运行map.put(1,3)语句,结果是可以的。被final修饰的引用类型变量,虽然不能重新指向,但是可以修改,这一点尤为要注意。

18:05:19.163 [main] INFO cn.northpark.concurrency.immutable.ImmutableExample1 - 3

(3)当final修饰方法的参数时:同样也是不允许在方法内部对其修改的。

1
2
3
private void test(final int a) {
// a = 1; //报错
}
Read more »

高并发、多线程探索-2-线程安全性-原子性-Atomic-CAS

Posted on 2018-12-25 | In 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
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
package cn.northpark.concurrency.atomic;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

import cn.northpark.concurrency.annotaion.ThreadSafe;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@ThreadSafe
public class TestAtomicInteger {

// 请求总数
static int totalCount = 5000;

// 同时并发执行的线程数
static int threadTotal = 200;

// 计数器
public static AtomicInteger count = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
// 限制同时执行的线程数
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(totalCount);

for (int i = 0; i < totalCount; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
countDownLatch.countDown();

});
}

countDownLatch.await();
executorService.shutdown();
log.info("count{}", count);
}

public static void add() {
count.incrementAndGet();
// count.getAndIncrement();
}
}
Read more »

高并发、多线程探索-1-相关基础知识

Posted on 2018-12-24 | In java

Java并发编程入门,适合没有并发编程经验的同学,本章首先从课程重点、特点、适合人群及学习收获几个方面对课程进行整体的介绍,然后会从一个实际的计数场景实现开始,给大家展示多线程并发时的线程不安全问题,让大家能够初体验到并发编程,之后会讲解并发和高并发的概念,并通过对比让大家明白到底什么是并发和…

高并发、多线程探索-1-相关基础知识

0、工具

Apache Bench(AB) :Apache附带的工具,测试网站性能
Jmeter : Apache组织开发的压力测试工具(比AB更强大)
代码测试方法 :Semaphore、CountDownLatch类

  • Semaphore类:信号量

信号量,在我们测试的过程中充当监控并发数的角色。能够维持在同一时间的请求的并发量,达到并发量上线,会阻塞进程。

  • CountDownLatch类:计数器向下减的闭锁
    1

说明:假设计数器的值为3,线程A执行了await()方法之后,进入了awaiting等待状态。在其他线程的方法中执行了countDown()方法之后,计数器的值都会减一,直到计数器的值减为0,线程A的方法才继续执行。所以说,countDownLatch类可以阻塞线程执行,并且当满足指定条件后让线程继续执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.northpark.concurrency.annotaion;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface NoteThreadSafe {
String value() default "";
}
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
package cn.northpark.concurrency.controller;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

import cn.northpark.concurrency.annotaion.NoteThreadSafe;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@NoteThreadSafe
public class TestCurrency {

// 请求总数
static int totalCount = 5000;

// 同时并发执行的线程数
static int threadTotal = 200;

// 计数器
public static int count = 0;

public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
// 限制同时执行的线程数
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(totalCount);

for (int i = 0; i < totalCount; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
countDownLatch.countDown();

});
}

countDownLatch.await();
executorService.shutdown();
log.info("count{}", count);
}

public static void add() {
count++;
}
}
Read more »
123…16
Bruce

Bruce

Extreme ways

92 posts
45 categories
83 tags
RSS
GitHub Twitter Facebook
Links
  • NorthPark
  • 挖粪の男孩
  • 小白博客
© 2015 - 2019 Bruce
Powered by Hexo
NorthPark中文网
Mac破解软件
院线大片
情商提升
  本站访客数 人次   本站总访问量 次