并发编程2-安全的发布对象

简介: <p>前文只是介绍了volitale关键字能够关闭重排序,缓存寄存器等优化来防止可见性发生问题。</p> <p>但是并发编程更多的是对发布(放到可以供其他线程访问的区域,比如static 集合等)的对象进行多线程处理,这里讨论下如何能够安全的发布这些对象。</p> <p>通常有如下的手段:</p> <h2>栈限制 </h2> <p> 局部变量,也就是方法内的变量,因为线程私有栈,因

前文只是介绍了volitale关键字能够关闭重排序,缓存寄存器等优化来防止可见性发生问题。

但是并发编程更多的是对发布(放到可以供其他线程访问的区域,比如static 集合等)的对象进行多线程处理,这里讨论下如何能够安全的发布这些对象。

通常有如下的手段:

栈限制 

 局部变量,也就是方法内的变量,因为线程私有栈,因此这个变量也是私有的,不会存在并发问题。 当然这个不是解决线程共享对象的,实际上我们写的代码中大多数的对象都是这样使用的,因此没有故意去注意并发仍然运行起来没有问题。

线程限制

使用ThreadLocal来把一个对象变成线程内共享。 也不是解决线程间共享的,其很适用于保存线程内很多地方都会用到的大对象。

package com.prince.concurrent;  
  
import java.sql.Connection;  
import java.sql.DriverManager;  
import java.sql.SQLException;  
  
public class ConnectionManager {  
  
    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
        @Override  
        protected Connection initialValue() {  
            Connection conn = null;  
            try {  
                conn = DriverManager.getConnection(  
                        "jdbc:mysql://localhost:3306/test", "username",  
                        "password");  
            } catch (SQLException e) {  
                e.printStackTrace();  
            }  
            return conn;  
        }  
    };  
  
    public static Connection getConnection() {  
        return connectionHolder.get();  
    }  
  
    public static void setConnection(Connection conn) {  
        connectionHolder.set(conn);  
    }  
} 
原理上比较简单。 在虚拟机上维护了一个map,可以根据当前线程id得到另外一个map,然后根据当前ThreadLocal实例来得到hold的对象。


线程安全的对象

这种对象内部使用了锁或者其他的机制,保证了每个方法都是安全的。  JVM在性能和安全上的考虑对很多类都提供了两个版本的实现,安全的和不安全的。比如

AtomicInteger 和Integer

StringBuffer 和StringBuilder

Vector和ArrayList

HashTable和HashMap等等。

AtomicInteger 和Integer

class CounterNoSafe{
	private Integer count = 0;
	public void addOne(){
		count++;
	}
	
	public Integer getCount(){
		return count;
	}
}

class CounterSafe{
	private AtomicInteger count = new AtomicInteger(0);
	public void addOne(){
		count.getAndIncrement();
	}
	
	public Integer getCount(){
		return count.get();
	}
}
private void testAtomicInteger() throws InterruptedException{
		final CounterNoSafe counter1 = new CounterNoSafe();
		new Thread(){
			public void run() {
				for (int i = 0; i < 1000; i++) {
					counter1.addOne();
				}
			};
		}.start();
		
		for (int i = 0; i < 1000; i++) {
			counter1.addOne();
		}
		
		TimeUnit.SECONDS.sleep(2);
		
		System.out.println(counter1.getCount());
		
		
		final CounterSafe counter2 = new CounterSafe();
		new Thread(){
			public void run() {
				for (int i = 0; i < 1000; i++) {
					counter2.addOne();
				}
			};
		}.start();
		
		for (int i = 0; i < 1000; i++) {
			counter2.addOne();
		}
		
		TimeUnit.SECONDS.sleep(2);
		
		System.out.println(counter2.getCount());
	}
将会打印 1327 2000.

可见不安全的计数器是有问题的。

AtomicInteger并不是通过锁的机制来解决并发问题的,而是使用如下的代码:

    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }
这样可以获得更好的性能, 之后还会讨论

StringBuffer和StringBuilder

	private void testStringBufferAndBuilder() throws InterruptedException {
		final StringBuffer sbf = new StringBuffer();
		new Thread(){
			public void run() {
				for (int i = 0; i < 10000; i++) {
					sbf.append(1);
				}
			};
		}.start();
		
		for (int i = 0; i < 10000; i++) {
			sbf.append(1);
		}
		TimeUnit.SECONDS.sleep(2);
		
		System.out.println(sbf.length());
		
		final StringBuilder sbd = new StringBuilder();
		new Thread(){
			public void run() {
				for (int i = 0; i < 10000; i++) {
					sbd.append(1);
				}
			};
		}.start();
		
		
		for (int i = 0; i < 10000; i++) {
			sbd.append(1);
		}
		
		TimeUnit.SECONDS.sleep(2);
		
		System.out.println(sbd.length());
	}

将会打印2000和17836

这个是因为StringBuilder是线程不安全的,因此在并发写入的时候,内部状态出现了错误。   但是它相对快一点,单线程的时候可以使用。


集合的并发后面再说



相关文章
|
4月前
|
安全 Java 编译器
Java并发编程学习3-可见性和对象发布
本篇介绍对象的共享之可见性和对象发布
61 2
Java并发编程学习3-可见性和对象发布
|
8月前
|
存储 安全 Java
JUC并发编程(JUC核心类、TimeUnit类、原子操作类、CASAQS)附带相关面试题
1.JUC并发编程的核心类,2.TimeUnit(时间单元),3.原子操作类,4.CAS 、AQS机制
41 0
|
11月前
|
安全
并发编程-08安全发布对象之发布与逸出
并发编程-08安全发布对象之发布与逸出
25 0
|
11月前
|
安全 Java
并发编程-09安全发布对象+单例模式详解
并发编程-09安全发布对象+单例模式详解
49 0
并发编程-09安全发布对象+单例模式详解
|
Java 编译器 调度
基本的线程机制—Java编程思想
并发编程使我们可以将程序分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立人物(也被称为子任务)中的每一个都将由执行线程来驱动。 一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务。 在使用线程时,CPU将轮流给每个任务分配其占用时间。 线程的一大好处是可以使你从这个层次抽身出来,即diamante不必知道它是运行在具有一个还是多个CPU的机器上。
|
安全 Java
《JUC并发编程 - 高级篇》03 - 共享对象之管程 上篇(共享带来的问题 | synchronized | 线程八锁 | 线程安全类)(三)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 上篇(共享带来的问题 | synchronized | 线程八锁 | 线程安全类)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 上篇(共享带来的问题 | synchronized | 线程八锁 | 线程安全类)(三)
|
存储 安全 Java
《JUC并发编程 - 高级篇》03 - 共享对象之管程 上篇(共享带来的问题 | synchronized | 线程八锁 | 线程安全类)(一)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 上篇(共享带来的问题 | synchronized | 线程八锁 | 线程安全类)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 上篇(共享带来的问题 | synchronized | 线程八锁 | 线程安全类)(一)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 上篇(共享带来的问题 | synchronized | 线程八锁 | 线程安全类)(二)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 上篇(共享带来的问题 | synchronized | 线程八锁 | 线程安全类)
《JUC并发编程 - 高级篇》03 - 共享对象之管程 上篇(共享带来的问题 | synchronized | 线程八锁 | 线程安全类)(二)
|
存储 缓存 监控
Java并发编程系列之二线程基础
上篇文章对并发的理论基础进行了回顾,主要是为什么使用多线程、多线程会引发什么问题及引发的原因,和怎么使用Java中的多线程去解决这些问题。
Java并发编程系列之二线程基础
|
安全 Java
「Java并发编程实战」之对象的共享
本系列博客是对《Java并发编程实战》的一点总结,本篇主要讲解以下几个内容,内容会比较枯燥。可能大家看标题不能能直观的感受出到底什么意思,这就是专业术语,哈哈,解释下,术语(terminology)是在特定学科领域用来表示概念的称谓的集合,在我国又称为名词或科技名词(不同于语法学中的名词)。术语是通过语音或文字来表达或限定科学概念的约定性语言符号,是思想和认识交流的工具。我就用白话文来给大家解释下这些术语。
125 0