线程安全
例如,有一百张票要通过三个窗口卖出,由于CPU的执行权的分配是随机的,也就是三个窗口哪个窗口处于卖票执行流程是不确定的,假如一号窗口发现还有票,正要出票但还未出时,轮到了二号窗口执行,也要出票,就会发现两个窗口卖了同一个号码的票,也就是重票,或者还有一张票时,在一号窗口执行完正要出票时,轮到了二号三号窗口执行,就会发现卖出了已经不存在的票,也就是错票。
当多个线程执行过程中存在共享数据,也就是多个线程会对同一个数据进行操作时,就产生了线程安全问题。这三个窗口就是三个线程,而这100张票就是共享数据。
为了解决线程安全问题,显然,我们应该在其中一个线程操作共享数据时,禁止其它的线程对这个共享数据进行操作,就好像在一个线程操作这个共享数据时,就上一把锁,不允许其他线程进入这个共享数据的执行流程。
同步代码块
同步机制之一:
形式
- synchronized(同步监视器){
//需要被同步的代码 - }
需要被同步的代码也就是各个线程操作共享数据的代码。
同步监视器可以由任何一个类的对象充当,只需要这些线程用的是同一个对象。
在实现Runnable接口创建多线程的方式中,我们可以考虑使用this(当前对象)充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用类.class(当前类)充当同步监视器。
同步方法
同步机制之二:
如果操作共享数据的代码可以完整的放在一个方法中,那我们不妨将这个方法声明为同步方法,也就是在方法的返回值类型前加上synchronized。
同步方法中虽然没有显式的声明同步监视器,但仍然存在同步监视器。
对于非静态的同步方法,同步监视器的是this(当前对象),而对于静态方法,同步监视器的是类.class(当前类)。
Lock锁
同步机制之三:
形式:
private ReentrantLock lock=new ReentrantLock();
try{
lock.lock();
//需要被同步的代码
}finally{
lock.unlock();
}
对于synchronized机制,在执行完同步代码块后会自动的释放同步监视器,而Lock则需要手动的启动同步(lock),手动的结束(unlock),并且,Lock可以通过tryLock()判断锁的状态。
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
一个线程需要同步资源A和同步资源B,另一个线程需要同步资源B和同步资源A;顺利的情况是,第一个线程先执行完毕,释放了同步资源B 和A,这样另一个线程就可以拿到同步资源,顺利执行。
但是,如果第一个线程拿到了同步资源A,在等待同步资源B时,另一个线程刚好拿到同步资源B,在等待同步资源A,这时就形成了死锁,两个线程都无法继续执行。
死锁的状态是我们必须避免的,主要的方式有:
1)专门的算法、原则
2)尽量减少同步资源的定义
3)尽量避免嵌套同步