1. Starten Sie einen Thread im Konstruktor
Ich habe dieses Problem in vielen Codes gesehen, ähnlich wie hier: #🎜 🎜#
public class A{ public A(){ this.x=1; this.y=2; this.thread=new MyThread(); this.thread.start(); } }
2. Unvollständige Synchronisierung
Wir alle wissen, dass der wirksame Weg, eine Variable zu synchronisieren, darin besteht, sie mit „Synchronized“ zu schützen Objektsperre oder eine Klassensperre, je nachdem, ob es sich um eine Klassenmethode oder eine Instanzmethode handelt. Wenn Sie jedoch eine Variable in Methode A synchronisieren, müssen Sie sie auch an anderer Stelle synchronisieren, wo die Variable erscheint, es sei denn, Sie lassen eine schwache Sichtbarkeit zu oder erzeugen sogar Fehlerwerte. Code wie dieser:class A{ int x; public int getX(){ return x; } public synchronized void setX(int x) { this.x=x; } }
3. Wenn ein Objekt als Sperre verwendet wird, wird die Referenz des Objekts geändert, was zu einem Synchronisierungsfehler führt.
synchronized(array[0]) { ...... array[0]=new A(); ...... }
4. Wait() wird nicht in der Schleife aufgerufen.
Wait und Notify werden zum Implementieren von Bedingungsvariablen verwendet. Möglicherweise wissen Sie, dass Wait und Notify in einem synchronisierten Block aufgerufen werden müssen, um sicherzustellen, dass Änderungen in den Bedingungen atomar und sichtbar sind. Ich sehe oft viel Code, der synchronisiert ist, aber nicht „wait“ in der Schleife aufruft, sondern „if“ oder gar keine bedingte Beurteilung verwendet:synchronized(lock) { if(isEmpty() lock.wait(); }
synchronized(lock) { while(isEmpty() lock.wait(); }
5. Der Synchronisationsbereich ist zu klein oder zu groß.
Wenn der Synchronisierungsumfang zu klein ist, wird der Zweck der Synchronisierung möglicherweise überhaupt nicht erreicht. Wenn der Synchronisierungsumfang zu groß ist, kann die Leistung beeinträchtigt werden. Ein häufiges Beispiel für einen zu kleinen Synchronisationsbereich ist die irrige Annahme, dass zwei synchronisierte Methoden synchronisiert werden, wenn sie gemeinsam aufgerufen werden. Man muss sich daran erinnern, dass Atomic+Atomic!=Atomic ist.Map map=Collections.synchronizedMap(new HashMap()); if(!map.containsKey("a")){ map.put("a", value); }
这是一个很典型的错误,map是线程安全的,containskey和put方法也是线程安全的,然而两个线程安全的方法被组合调用就不一定是线程安全的了。因为在containsKey和put之间,可能有其他线程抢先put进了a,那么就可能覆盖了其他线程设置的值,导致值的丢失。解决这一问题的方法就是扩大同步范围,因为对象锁是可重入的,因此在线程安全方法之上再同步相同的锁对象不会有问题。
Map map = Collections.synchronizedMap(new HashMap()); synchronized (map) { if (!map.containsKey("a")) { map.put("a", value); } }
注意,加大锁的范围,也要保证使用的是同一个锁,不然很可能造成死锁。 Collections.synchronizedMap(new HashMap())使用的锁是map本身,因此没有问题。当然,上面的情况现在更推荐使用ConcurrentHashMap,它有putIfAbsent方法来达到同样的目的并且满足线程安全性。
同步范围过大的例子也很多,比如在同步块中new大对象,或者调用费时的IO操作(操作数据库,webservice等)。不得不调用费时操作的时候,一定要指定超时时间,例如通过URLConnection去invoke某个URL时就要设置connect timeout和read timeout,防止锁被独占不释放。同步范围过大的情况下,要在保证线程安全的前提下,将不必要同步的操作从同步块中移出。
6、正确使用volatile
在jdk5修正了volatile的语义后,volatile作为一种轻量级的同步策略就得到了大量的使用。volatile的严格定义参考jvm spec,这里只从volatile能做什么,和不能用来做什么出发做个探讨。
volatile可以用来做什么?
1)状态标志,模拟控制机制。常见用途如控制线程是否停止:
private volatile boolean stopped; public void close(){ stopped=true; } public void run(){ while(!stopped){ //do something } }
前提是do something中不会有阻塞调用之类。volatile保证stopped变量的可见性,run方法中读取stopped变量总是main memory中的***值。
2)安全发布,如修复DLC问题。
private volatile IoBufferAllocator instance; public IoBufferAllocator getInsntace(){ if(instance==null){ synchronized (IoBufferAllocator.class) { if(instance==null) instance=new IoBufferAllocator(); } } return instance; }
3)开销较低的读写锁
public class CheesyCounter { private volatile int value; public int getValue() { return value; } public synchronized int increment() { return value++; } }
synchronized保证更新的原子性,volatile保证线程间的可见性。
volatile不能用于做什么?
1)不能用于做计数器
public class CheesyCounter { private volatile int value; public int getValue() { return value; } public int increment() { return value++; } }
因为value++其实是有三个操作组成的:读取、修改、写入,volatile不能保证这个序列是原子的。对value的修改操作依赖于value的***值。解决这个问题的方法可以将increment方法同步,或者使用AtomicInteger原子类。
2)与其他变量构成不变式
一个典型的例子是定义一个数据范围,需要保证约束lower< upper。
public class NumberRange { private volatile int lower, upper; public int getLower() { return lower; } public int getUpper() { return upper; } public void setLower(int value) { if (value > upper) throw new IllegalArgumentException(); lower = value; } public void setUpper(int value) { if (value < lower) throw new IllegalArgumentException(); upper = value; } }
尽管讲lower和upper声明为volatile,但是setLower和setUpper并不是线程安全方法。假设初始状态为(0,5),同时调用setLower(4)和setUpper(3),两个线程交叉进行,***结果可能是(4,3),违反了约束条件。修改这个问题的办法就是将setLower和setUpper同步:
public class NumberRange { private volatile int lower, upper; public int getLower() { return lower; } public int getUpper() { return upper; } public synchronized void setLower(int value) { if (value > upper) throw new IllegalArgumentException(); lower = value; } public synchronized void setUpper(int value) { if (value < lower) throw new IllegalArgumentException(); upper = value; } }Das obige ist der detaillierte Inhalt vonSo implementieren Sie Multithread-Programmierung in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!