Synchronized Blocks
synchronized (System.out) {
System.out.print(input + ": ");
System.out.print(DatatypeConverter.printHexBinary(digest));
System.out.println();
}当多线程访问共享资源时,必须考虑同步!
同步只所以成为问题,只有在两个或多个线程同时拥有同一个对象的引用时!比如System.out,PrintStream对象,out是System的静态对象,所有线程用的是只能是同一个。即使不是静态对象,也会引起conflict。
假设LogFile代表日志文件,所有线程都需要访问同一个LogFile对象。
public class LogFile {
private Writer out;
public LogFile(File f)
FileWriter fw = new
this.out = new Buffe
}
public void writeEntry
Date d = new Date();
out.write(d.toString);
out.write('\t');
out.write(message);
out.write("\r\n");
}
public void close() th
out.flush();
out.close();
}
}所有线程都拥有 同一个LogFile对象 的引用,当一个线程调用writeEntry打印了日期后,第二个线程进来也调用该方法,发生冲突!
在这里有2种同步代码块的方式:
public void writeEntry(String message) throws IOException {
synchronized (out) {
Date d = new Date();
out.write(d.toString());
out.write('\t');
out.write(message);
out.write("\r\n");
}
}public void writeEntry(String message) throws IOException {
synchronized (this) {
Date d = new Date();
out.write(d.toString());
out.write('\t');
out.write(message);
out.write("\r\n");
}
}只要保证synchronized(lock),lock唯一就行。在这里LogFile对象是唯一的,就能保证out和this是唯一的。
Synchronized Method
public synchronized void writeEntry(String message) throws IOException {
Date d = new Date();
out.write(d.toString());
out.write('\t');
out.write(message);
out.write("\r\n");
}同步方法的lock其实是当前对象本身,即synchronized(this)。
同步方法并非是对所有同步问题通用的解决方案。首先,会导致服务器性能下降,其次,死锁的可能性会增大,第三,更重要的是,你要同步的并不总是对象本身,用同步方法同步的是对象本身,可能会使你真正该同步的对象没有被同步!比如上例中,要真正同步的是“out”,避免2个线程同时写入out,但是如果有和LogFile无关的类拥有了out的引用,那同步的目的就没有达到!(因为同步的是LogFile对象本身,而不是out)。然而在该例中是有效的原因是,out是一个私有的instance variable,从未对外暴漏过该对象的引用,对其他对象来说要想调用out的方法,只有通过LogFile类。因此,这里同步LogFile对象和同步out效果是一样的。
Alternatives to Synchronization
Synchronization并不总是解决因线程调度引起的不一致问题的最佳方案,有许多其他方案可以完全替换Synchronization。
1,只要可以就使用local variable,而不用field。局部变量不存在同步问题,因为进入方法时,vm创建局部变量,当方法调用结束,局部变量失效,在方法外部是无法访问局部变量的。因此,局部变量不可能被不同线程共享,每个线程都会有一套自己的局部变量。
2,方法的参数是基本类型的是线程安全的,因为对基础类型的参数,java传递的是值,而不是引用。这种方法,一般或者说应该实现成静态的。
方法参数是对象类型的比较“trickier”,因为传递的是对象的引用!
3,Immutable !
String类是线程安全的,因为它是不可变的,一般String对象被创建,任何线程都无法再修改它。
StringBuild 是线程不安全的!
构造方法一般不需要考虑线程安全问题,因为在构造方法返回前是不会有线程能拥有对象的引用的!
把一个类实现成不可变的,是最简单的实现线程安全的方法。只需要把它的所有field设置成private和final型,并且不提供修改它们的方法。java中有很多这样的类,比如java.lang.String,java.lang.Integer,java.lang.Double 等。
4,把一个线程不安全的类作为一个线程安全的类的私有的field。只要the containing class 访问那个不安全的类时使用的是安全的方式,并且不把那个不安全类的对象的引用泄露给其他类。
5,还可以使用可变的但是设计上是线程安全的类,从java.util.concurrent.atomic这个包。比如,不用int,而用AtomicInteger。
6,对Collection,比如Map,Set,可以用java.util.Collections包装成线程安全的。比如,对Set, Collections.synchronizedSet(foo),对List, Collections.synchronizedList(foo) 等。使用Collections.synchronizedSet/List/Map返回的Collection,不管是访问原来的,还是包装过的,都会是线程安全的。
注意,只有仅仅一个方法调用才是原子的。如果你想基于原子值做连续2次操作,而不想被interruption,那必须得同步!比如,一个list通过Collections.synchronizedList()是安全的了,要对该list迭代,要保证安全,就需要同步!因为迭代连续调用了多个原子操作。尽管每次方法调用是安全的,原子的,但是如果没有同步,一系列的原子操作连续操作的话就会不安全!
本文深入探讨Java中的同步机制,包括同步代码块与方法的使用,解释如何正确地使用synchronized关键字来确保线程安全。此外,还介绍了几种替代同步机制的方法,如利用不可变对象、原子变量等手段来提高并发性能。
844

被折叠的 条评论
为什么被折叠?



