并发访问:当使用多线程来访问同一个数据时,很容易出现线程安全的问题(并发访问)
线程安全:并发线程在任一时刻只有一个线程可以进入修改共享资源的代码区(临界区),所以同一时刻最多只有一个线程处于临界区,从而保证线程的安全性
线程同步主要由四种方法:1.互斥区,就是锁了;2.条件变量;3.信号量;4.事件。基本上我们写程序用的就是锁了。为了保证线程同步,一般用如下两种方式:
1. synchronized同步监视器
1)步代码块
使用同步监视器的通用方法,就是同步代码块。
语法格式:synchronized(obj){ …… } //obj为同步监视器
说明:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。
任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完后,该线程会释放对同步监视器的锁定。Java程序允许使用任何对象作为同步监视器
同步监视器的目的:阻止两个线程对同一个共享资源进行并发访问(推荐使用可能被并发访问的共享资源充当同步监视器)
2)同步方法
同步方法:使用synchronized关键字来修饰的方法。对于synchronized修饰的实例方法(非static方法),无需显式指定同步监视器,同步方法的同步监视器是this
可变类与不可变类:不可变类总是线程安全的,因为它的对象状态不可变;可变对象需要额外的方法来保证其线程安全。
可变类的线程安全是以降低程序的效率为代价的,为了减少线程安全所带来的负面影响,程序可采用如下方法:
1)不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步
2)如果可变类有两种运行环境:单线程环境、多线程环境,则应为该类提供两种版本(线程安全与不安全版本)
单线程环境下用线程不安全版本以保证性能,多线程环境下用线程安全版本以保证安全
(synchronized可以修饰方法、代码块,但是不能修饰构造器、成员变量)
3)释放同步监视器的锁定
程序无法显式释放对同步监视器的锁定,线程会在如下几种情况下释放对同步监视器的锁定
执行结束
遇到break、return
出现未处理的Error、Exception
程序中执行了同步监视器对象的wait()方法
无法释放同步监视器的情况
程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器
线程执行同步代码块时,其它线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器(避免使用suspend()、resume())
2. 同步锁
同步锁:通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当(更强大的线程同步机制)