1.为什么会出现线程安全问题

计算机系统资源分配的单位为进程,同一个进程中允许多个线程并发执行,并且多个线程会共享进程范围内的资源:例如内存地址。当多个线程并发访问同一个内存地址并且内存地址保存的值是可变的时候可能会发生线程安全问题,因此需要内存数据共享机制来保证线程安全问题。

对应到java服务来说,在虚拟中的共享内存地址是java的堆内存,比如以下程序中线程安全问题:

public class ThreadUnsafeDemo {

    private static final ExecutorService EXECUTOR_SERVICE;

    static {
EXECUTOR_SERVICE = new ThreadPoolExecutor(100, 100, 1000 * 10,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(100), new ThreadFactory() { private AtomicLong atomicLong = new AtomicLong(1); @Override
public Thread newThread(Runnable r) {
return new Thread(r, "Thread-Safe-Thread-" + atomicLong.getAndIncrement());
}
});
} public static void main(String[] args) throws Exception {
Map<String, Integer> params = new HashMap<>(); List<Future> futureList = new ArrayList<>(100);
for (int i = 0; i < 100; i++) {
futureList.add(EXECUTOR_SERVICE.submit(new CacheOpTask(params)));
} for (Future future : futureList) {
System.out.println("Future result:" + future.get());
} System.out.println(params);
} private static class CacheOpTask implements Callable<Integer> { private Map<String, Integer> params; CacheOpTask(Map<String, Integer> params) {
this.params = params;
} @Override
public Integer call() {
for (int i = 0; i < 100; i++) {
int count = params.getOrDefault("count", 0);
params.put("count", ++count);
}
return params.get("count");
} }
}

创建100个task,每个task对map中的元素累加100此,程序执行结果为:

{count=9846}

而预期的正确结果为:

{count=10000}

至于出现这种问题的原因,下面会具体分析。

判断是否有线程安全性的一个原则是:

是否有多线程访问可变的共享变量

2.多线程的优势

发挥多处理器的强大能力,提高效率和程序吞吐量

3.并发带来的风险

使用并发程序带来的主要风险有以下三种:

3.1.安全性问题:

竞态条件:由于不恰当的执行时序而出现不正确的结果

对于1中的线程安全的例子就是由于竞态条件导致的最终结果与预期结果不一致。关键代码块如下:

int count = params.getOrDefault("count", 0);
params.put("count", ++count);

当多个线程同时取的count的值的时候,每个线程计算之后,在写入到count,这时候会出现多个线程值被覆盖的情况,最终导致结果不正确。

如下图所示:

3.2解决此类问题的几种方法

1.使用同步机制限制变量的访问:锁

比如:

synchronized (LOCK) {
int count = params.getOrDefault("count", 0);
params.put("count", ++count);
}

2.将变量设置为不可变

即将共享变量设置为final

3.不在线程之间共享此变量ThreadLocal

编程的原则:首先编写正确的代码,然后在实现性能的提升

无状态的类一定是线程安全的

3.3 内置锁

内置锁:同步代码块( synchronized (this) {})

进入代码块前需要获取锁,会有性能问题。内置锁是可重入锁,之所以每个对象都有一个内置锁,是为了避免显示的创建锁对象

常见的加锁约定:将所有的可变状态都封装在对象内部,并使用内置锁对所有访问可变状态的代码进行同步。例如:Vector等

同步的另一个功能:内存可见性,类似于volatile

非volatile的64位变量double、long:

JVM允许对64位的操作分解为两次32位的两次操作,可变64位变量必须用volatile或者锁来保护

加锁的含义不仅在于互斥行为,还包括内存可见性,为了所有线程都可以看到共享变量的最新值,所有线程应该使用同一个锁

原则:

除非需要跟高的可见性,否则应该将所有的域都声明为私有的,除非需要某个域是可变的,否则应该讲所有的域生命为final的

2.活跃性问题

线程活跃性问题主要是由于加锁不正确导致的线程一直处于等待获取锁的状态,比如以下程序:

public class DeadLock {
private static final Object[] LOCK_ARRAY; static {
LOCK_ARRAY = new Object[2];
LOCK_ARRAY[0] = new Object();
LOCK_ARRAY[1] = new Object();
} public static void main(String[] args) throws Exception {
TaskOne taskOne = new TaskOne();
taskOne.start(); TaskTwo taskTwo = new TaskTwo();
taskTwo.start();
System.out.println("finished"); } private static class TaskOne extends Thread { @Override
public void run(){
synchronized (LOCK_ARRAY[0]) {
try {
Thread.sleep(3000); } catch (Exception e) {
}
System.out.println("Get LOCK-0");
synchronized (LOCK_ARRAY[1]) {
System.out.println("Get LOCK-1");
} }
}
} private static class TaskTwo extends Thread { @Override
public void run() {
synchronized (LOCK_ARRAY[1]) {
try {
Thread.sleep(1000 * 3); } catch (Exception e) {
}
System.out.println("Get LOCK-1");
synchronized (LOCK_ARRAY[0]) {
System.out.println("Get LOCK-0");
}
}
}
}
}

在两个线程持有一个锁,并在在锁没有释放之前,互相等待对方持有的锁,这时候会造成两个线程会一直等待,从而产生死锁。在我们使用锁的时候应该考虑持有锁的时长,特别是在网络I/O的时候。

在使用锁的时候要尽量避免以上情况,从而避免产生死锁

3.性能问题

在使用多线程执行程序的时候,在线程间的切换以及线程的调度也会消耗CPU的性能。

最新文章

  1. PosePlus的第一次突破
  2. Win7、Ubuntu双系统正确卸载Ubuntu系统
  3. topcoder SRM 618 DIV2 LongWordsDiv2
  4. 【转】为什么我要用 Node.js? 案例逐一介绍
  5. sql在添加新列时同时指定default约束名称
  6. IEF could not decode Chinese character in IE history well
  7. JPA学习---第二节:JPA开发环境和思想介绍
  8. poj 3253 Fence Repair(优先队列+哈夫曼树)
  9. Visual Studio 2013新功能
  10. Html内容超出标记宽度后自动隐藏
  11. NLB+Application Request Route 网路负载均衡
  12. web工程中地址的写法
  13. 201521123083《Java程序设计》第13周学习总结
  14. bzoj 2733: [HNOI2012]永无乡
  15. Lambda表达式补充
  16. 机器学习之AdaBoost原理与代码实现
  17. Spark2.2 saveAsTable 函数使用 overWrite 设置 Partition 会造成全覆盖的问题
  18. login.html
  19. httping使用
  20. OkGo3.0 --真实项目使用和二次封装(转)

热门文章

  1. 微信小程序开发入门首选
  2. ansible 2.1.0 api 编程
  3. Echarts图表学习
  4. nginx 升级为最新版 nginx -1.12.0
  5. Metasploitable渗透测试实战——Windows漏洞 MS08-067复现
  6. 使用HANA Web-based Development Workbench创建最简单的Server Side JavaScript
  7. 因技术垃圾直接上手groovy的工作感悟
  8. 奇怪的Unrooted Tests错误
  9. POJ 3614 Sunscreen(贪心,区间单点匹配)
  10. Android(java)学习笔记152:采用get请求提交数据到服务器(qq登录案例)