java并发实践学习

来源:互联网 发布:js添加input隐藏属性 编辑:程序博客网 时间:2024/05/18 23:25

第二章

1,什么是对象的状态

对象的状态是指对象的实例域,或静态域中的数据。注意,对象的状态可能与依赖对象有关。例,HashMap的状态还与entry的对象相关。

2,什么是线程安全

线程安全:当多个线程同时访问一个对象(类)时(状态转变),不在需要任何的额外的同步策略,并且这个类能够变现出正确的行为,不能破坏不变形条件。

补充一点:不变形约束,当多个状态变量之间不是独立的,又相互的关系。当更新其中一个时,其他的也要变化。如果这个变化不是原子的,这可能会有线程安全的问题。

无状态对象一定线程安全。

3,什么是原子性

原子性是指在指令执行的不可分割。java的++不是原子性。非原子性是需要同步的原因。

4,竞争条件

不同的执行顺序可能会有不同的结果。正确的结果依赖于精确的时序。例:先检查后执行,是否为真,如果为执行,但是可能执行的时候,添加已经为假了。

5,复合操作

例,读取-修改-写回。若要线程安全,必须要让复合操作具有原子性,通过持有锁来实现,对同一个共享状态必须是同一个锁。

6,同时写一个原子变量会怎样?

依赖于总线仲裁。

7,java的内置锁

synchronized (lock){

}

两个功能,可见性和同步。

内置锁可以重入,注意,重入的粒度是线程,而不是调用(与posix线程不同).。????????????????????

可见性可以看一下java的内存模型。推荐http://tutorials.jenkov.com/java-concurrency/java-memory-model.html

8,每个对象都有一个内置锁,他本身。每个共享状态都应该只有同一个锁保护。不变性条件中的每一个锁都需要同一个锁保护。

如果只是将类的所有方法都添加sysnchronized,并不能保证同步。例:

if( !a.contain(o) )

a.insert(o)

虽然contain和insert都synchronized,但这是一个复合操作。

一些技巧:

1,可以通过线程安全的对象管理不安全的对象,达到线程安全的目的。

 

 

第三章

1,synchronized的作用: 可见性,原子性,临界区。

可见性:为了性能,每个线程在访问共享状态的时候,会对共享状态进行缓存,当修改变量的的时候也是在缓存中修改,一个线程的修改可能对其他线程不可见,从而使共享状态不一致。synchronized和volatile可以保证可见性。每次读会从内存中读取,修改会写回内存。

2,如下没有同步的代码有哪些问题?

package gkl;

public class T{
static boolean a;
static int b;

private static class A extends Thread{
public void run() {
while(!a)
Thread.yield();
System.out.println(b);
}
}
public static void main(String[] args) {
A thread=new A();
thread.start();
a=true;
b=10;
}

}

1),死循环,因为线程可能看不见a的修改。

2),输出为0,因为重排序,可能看见了a的修改,但是没有看见b的修改。

3,下面的代码有什么问题?

class Unsafe{
int value;
int get(){
return value;
}
synchronized void set(int a){
value=a;
}
}
只是同步set是不行的,因为get可能读取之后缓存,修改之后的值不可见。
4,读取费volatile的64位long和double,java内存模型把读操作和写操作分成两个32位,即long和double 费原子协定。但事实上,所有的商用虚拟机都将他们实现为原子操作,所以可以不用volatile。
5,volatile变量:保证可见性,不重排序该变量上的操作。但是访问volatile变量不会加锁么就是不会阻塞。费volatile变量上的原子操作不会刷新到内存中去,原子操作不保证可见性。volatile也不保证原子性,对volatile变量的++操作依然是非原子的。只是volatile相当于直接面向内存而非缓存。
volatile的典型用法:判断条件,是否退出循环。
 
6,对象的发布与逸出
发布:对象在他的作用域外有引用。常见的发布途径:传递参数,返回,作为其他对象的成员一起被发布(间接发布),传递内部类对象给某方法,外部对象被发布。
在构造函数中新建线程,或者调用可以改变实例状态的方法(参数对象的方法),会使this引用逸出。最好不要在构造器中让线程启动,可以以后再start。
逸出:不该发布的发布了。
 
7,线程封闭
如果一个对象是线程封闭的,不采取同步策略,也是线程安全的。其他的线程不能访问。局部变量,TreadLocal类。例,jdbc的连接池,直到一个连接返回连接池,其他的线程不能访获得该连接。
8,ThreadLocal类
该类的对象对每一个共享该对象的线程维护一个副本,也就是说,每个线程都是得到该线程上次修改的状态值。
例:一个全局的数据库连接对象。每个线程都有自己的连接。
public class G {
public ThreadLocal<Connection> getConnect() {
return new ThreadLocal<Connection> (){
public Connection get() {
return DriverManager.getConnetion (DB_URL);
}
}

}

}
当某个线程调用ThreadLocal.get()时,会用InitialValue来获取初始值,可以理解为map<thread,Connection>.,但实现并非如此。
 
9,不可变对象一定是线程安全的。final域能确保初始化过程的安全性。仍然可以更新不可变对象的状态,通过新建一个实例替换。例:String.
不可变对象不一定域为final,域为final也是可以改变的。
public final class ThreeStooges {
private final Set<String> stooges = new HashSet<String>();

public ThreeStooges() {
stooges.add("Moe");
stooges.add("Larry");
stooges.add("Curly");
}

public boolean isStooge(String name) {
return stooges.contains(name);
}
}

没有提供改变状态的方法。
 
10安全发布对象。一个例子,来源http://bbs.csdn.net/topics/390662638

public class Holder {
private int n;
public Holder() {
this.n = n;
outter = this; //这里this逸出(outter是一个外部使用的对象)
}
public void assertSanity() {
if(n != n)
throw new AssertionError("This statement is false.");
}
}

public class SuperHolder
{
public Holder holder;

public static void main(String... args)
{
final SuperHolder sh = new SuperHolder();

new Thread(new Runnable(){
public void run()
{
sh.holder = new Holder();
}
}).start();

new Thread(new Runnable(){
public void run()
{
if(sh.holder!=null)
{
sh.holder.assertSanity();
}
}
}).start();
}
}


如果是“不可变对象”,只要正确构造了,无论如何JVM都能保证它被“安全发布”。
如果不是“不可变对象”,即使正确构造,但是JVM并不保证它能被“安全发布”。
n != n也不是原子的!原因就是构造函数不是原子的,指令可能重排序。”
其编译后的java指令为
aload
getfield  //第一次读变量n
aload
getfield  //第二次读变量n
if_icmpeq
如果不同步的话,两次读变量n之间是可能会切换到其它线程执行的。
继续使用我给出的“不安全发布”的例子来说
第一个线程用来new实例,当第二个线程中 sh.holder!=null 时(也就是构造函数已经发布了),但因为重排序,n有可能尚未赋值,也就是int变量的默认值0。此时第一次读变量n记为n',实际值为0;此时再次切换到线程1完成了n赋值为42,此时第二次读变量n记为n'',实际值为42。
你所看到的n!=n实际是n'!=n'',最后是0!=42,结果为真
(表示感谢)
 
10,安全发布对象的常见模式。
这儿有必要说一下安全发布的含义。安全发布不意味着线程安全,指的是对象发布的那一刻,对所有的线程,他的引用和状态是可见的,不保证以后的状态对其他线程依然可见,而需要使用同步。上面的例子之所以是不安全发布,就是因为对象的状态对其他线程不可见,不知道有没有赋值。
  • 在静态初始化函数中初始化一个对象引用
  • 将对象的应用保存到volatile类型的域或者AtomicReferance对象中
  • 将对象的引用保存到某个正确构造对象的final类型域中 (保证初始化的安全性)
  • 将对象的引用保存到一个由锁保护的域中。(Hashtable、synchronizedMap或者ConcurrentMap,Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet,BlockingQueue或者ConcurrentLinkedQueue)

类库中的其他数据传递机制(例如Future和Exchanger)同样能实现安全发布。

 
 
0 0
原创粉丝点击