一、多线程并发问题
所谓的多线程并发问题,就是在多线程的情况下,一个任务对共享数据操作的中间状态暴露给其他任务。即在一个线程的任务中,会对一个多线程共享的数据进行操作,在操作完成之前,对数据的更改对其他线程可见,此时,如果线程挂起,并驱动其他线程任务,则其他线程任务会访问到这个数据的中间状态,即访问到错误的数据。
多线程问题的基础是在多线程下使用同一个对象操作这个对象的同一个成员属性。
二、解决共享资源问题
防止多个任务访问同一资源的方法是给资源加锁,当一个任务正在处理资源时,会给资源加锁,其他 任务就无法访问资源。这里的资源通常是内存中的一个对象。其实就是当一个任务处理资源时,这个任务就独享这个资源,任务对资源(这里的资源通常是对象的成员变量)的任何操作,在任务释放锁之前,对其他任务来说都是不可见的。
三、Java中两种锁的机制
1、内部默认的加锁机制
Java提供synchronized关键字来防止资源冲突。当程序要执行synchronized保护的代码片段时,程序将检查锁是否可用,然后获取锁,执行代码,释放锁。
public synchronized void f(){};
public synchronized void g(){};
同一对象的所有synchronized方法都共享同一个对象锁,即这个对象所包含的的锁。每一个类对象Class对象也都包含一个锁,可以用synchronized static 方法来同步静态方法,处理对静态成员的多线程访问问题。
public synchronized static void h(){}
JavaBean的线程安全考虑,JavaBean的所有成员属性都是私有的,所有对这些属性的操作只有通过唯一的setter和getter方法,可以很方便的把这些方法设为synchronized的。如果成员属性不是private的,则可以通过 ClassName.paramName方式访问对象属性,而这种访问方法是无法加锁同步的,这样就会造成冲突。
2、使用显示的Lock对象。
Lock对象必须显示的被创建、锁定和释放。
public class MutexEvenGenerator{
private Lock lock = new ReentranLock();
public int next(){
lock.lock();
try{
//some operations
}finally{
lock.unlock();
}
}
两种方式比较。synchronized比Lock简单,需要编写的代码量比较少。但synchronized机制比较固定,不灵活。主要表现是:如果一个线程执行synchronized方法或语句块,如果得不到对象锁,则线程阻塞,一直到获得对象锁才能重新可执行。不能再没有获得对象锁的情况下返回执行,也不能设定等待多长时间时间后如果没获得对象锁就返回。而LOCK机制支持这两种情况。即Lock可以在没获得对象锁的情况下返回,也可以在等待多长时间后返回。
boolean capture = lock.tryLock();
boolean capture = lock.tryLock(2,TimeUbit.SECONDS);
同步机制强制对数据的操作必须是可视的。
@@volatile:
1、禁止编译器优化代码,而严格执行每一个操作。如:volatile int a = 0; a = 3; a = 4; 如果a不是volatile的,则对这三条语句,编译器只生成一条语句,即编译a=4这条语句,但如果声明a为volatile的,则会生成三条语句。
2、对这个变量的所有操作都是可见的。即如果一个线程更改了这个变量的值,则这个修改会立刻反应到主存中去,即使你操作的是一个本地缓存。
多线程调优吗?
1、创建和驱动线程调优:不要自己手动的Thread.start(),应该使用Executor.execute(Runnable r)方式来驱动线程,以使用系统对线程的优化,通常使用
ExecutorService exe = Executors.newCacheThreadPool(ThreadFactory factory);让系统来维护线程池对象。
2、线程调度优化。即当一个线程的主要核心工作完成后,应该及时通知线程调度器,自己的主要工作已经做完,可以出让CPU时间,以便调度器将CPU占有时间分配给需要处理核心任务的线程。通常是在核心任务完成后调用Thread.yield();方法。
3、线程同步的优化,线程同步会导致多线程下多个线程的堵塞,所以会极大的影响系统的性能。在做线程同步时,应该尽量使用同步快,只同步必要的代码,如果性能要求及高的并发程序,可以考虑使用原子类,如AutomicInteger,AutomicLong等。使用Lock比系统默认的synchronized效率高些