synchronized 可以保证在同一时刻只有一个线程可以自毁执行某个方法或者代码块,这是一种互斥概念,可以保证不同线程看到对象永远都是处于一致的状态中。
除了long或double外的变量读写都是原子的。
拓展:java long和double类型的写操作是非原子操作,原因如下:long和double占8字节,64位,在32位的操作系统对64位的数据读写要分成两步,每一步取32位数据。这样一个线程写高32位,另一个线程写低32位,会导致数据失效。因此可以使用volatile关键字来防止此类现象发生。
正确停止一个线程,不应该使用Thread.stop来结束线程,这是不安全的,而应该让第一个线程轮询一个boolean域,当这个域值改变时即停止线程。此时应该对该boolean域进行同步访问或使用volatile修饰符。因为尽管boolean类型的读写都是原子的,但是由于没有同步,就不能保证后台线程何时看到此域值的更改,JVM为了优化只会判断一次。
volatile关键字不执行互斥访问,但它可以保证任一线程读取该域时都是看到它刚刚被写入的值。
使用volatile并不是万能的,如果涉及对对象的操作,如加法运算,有可能导致丢失更新。
private static voilatile int num = 0;
public static int getNum(){
return num++; // 这里的++运算并不是原子的
}
解决方法是加同步,或者使用java.uitl.concurrent.atomic包中的AtomicLong,自增方法是getAndIncrement();
拓展:AtomicLong的底层实现是使用了直接内存,即sun.misc.Unsafe类
过度同步会导致性能降低、死锁等问题。
List在迭代器遍历时,修改集合的数量会抛异常,使用java.util.concurrent.CopyOnWriteArrayList可以避免这个问题
同步应当尽量使用外部同步而非内部同步,因为外部同步是可选的,而内部同步会导致所有调用都执行同步。StringBuffer是内部同步的,因此应该尽可能使用StringBuilder
使用java.util.concurrent.Executors或者ThreadPoolExecutor产生Executor代替直接使用Thread.
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(runnable);
executor.shutdown(); // 优雅地终止任务
使用ExecutorService+Callable
Future<String> fu = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "finish";
}
});
- notify只能唤醒一个线程,notifyAll可以唤醒所有等待中的线程。为了保持活性,减少死锁发生的概率,应当尽量使用notifyAll()