线程安全
线程安全问题:多个线程去访问同一个资源会产生安全问题,如模拟四个窗口同时出售车票,车票数量是共享资源,四个窗口是四个线程,会出现线程安全问题。要想解决线程安全问题,需要保证处理共享资源的代码在任意时刻只能有一个线程访问。
1. 同步代码块
将处理共享资源的代码放置在一个使用synchronized关键字修饰的代码块中。语法格式如下:
synchronized(lock){
//操作共享资源的代码
}
上述代码中lock是一个锁对象,线程执行同步代码块时首先检测锁对象的标志位,锁对象标志位为1线程执行代码块并把锁对象的标志位置为0.当一个新的线程执行到这段同步代码块时锁对象标志位为0新线程会阻塞,等待锁标志变为1。同步代码块的锁对象可以是任意类型的对象,但是多个线程共享的锁对象必须是相同的。
2.同步方法
在方法先前加synchronized关键字修饰,被修饰的方法为同步方法,它能实现和同步代码块同样的功能,语法格式如下:
[修饰符] synchronized 返回值类型 方法名(参数....){}
被synchronized修饰的方法某一时刻只能被一个线程访问,访问该方法的其他线程会阻塞,直到当前线程访问完毕,其他线程才有机会执行。同步方法的锁对象就是当前调用该方法的对象,也就是this指向的对象。同步静态方法的锁对象是该方法所在类的class对象,该对象可以直接用类名.class方式获取。
同步代码块和同步方法解决了多线程同时访问共享资源时线程安全问题,但是每次都会检查锁状态,非常消耗资源,效率较低。
3.同步锁
synchronized同步代码块和同步方法是一种封闭式的锁机制,使用简单也能解决线程安全问题,但也有一下限制,例如它无法中断一个正在等候获得锁的线程,也无法通过轮询得到锁,如果不想等下去就没法得到锁。Lock锁可以在某个线程在持续获得同步锁失败后返回,不在继续等待,另外Lock锁使用时也更加灵活。
第一步:定义一个锁对象
private final Lock lock = new ReentrantLock();
第二步:在需要锁的代码前调用
lock.lock() //对代码进行加锁。
第三步:在代码结束处释放锁
lock.unlock(); //执行完代码后释放锁
ReentrantLock是Lock锁接口的实现类,也是常用的同步锁常用接口
lock();unlock()
tryLock()方法判断某个线程锁是否可用。
4.死锁问题
两个线程运行的时候都在等待对方的锁,就造成了程序的停滞,这种现象就叫死锁。
线程通信
多个线程之间需要协同完成工作就需要线程间通信。以生产者消费者场景为例。假设有生产者和消费者两个线程,同时去操作同一个商品,生产者负责生产商品,消费者负责消费商品。需要保证有商品的情况下消费者才能消费商品,需要让线程间进行通信,协调进行。线程间通信常用的方法是Object类提供的wait方法,notify方法和notifyAll方法。
wait方法:是当前线程放弃同步锁进入等待状态,直到其他线程进入同步锁并调用notify或notifyAll方法唤醒该线程为止。
notify方法:唤醒此同步锁上等待的第一个调用wait方法的线程
notifyAll方法:唤醒此同步锁上调用wait方法的所有线程。
需要注意的是wait,notify和notifyAll方法这三个方法的调用者都应该是同步锁对象,如果这三个方法的调用者不是同步锁对象,Java虚拟机会抛出IllegalMonitorStateException异常。
如:
Object lock = new Object();、
第一个线程
public void run(){
...
synchronized(lock){
lock.wait()
}
}
第二个线程
public void run(){
...
synchronized(lock){
lock.notify()
}
}