线程安全:
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
- 操作共享数据的代码,即为需要被同步的代码
- 共享数据:多个线程共同操作的变量。比如: ticket就是共享数据。
- 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,可以考虑用this充当同步监视器。
public class RunnableImpl implements Runnable{
// 定义共享资源 线程不安全
private int ticket = 100;
//在成员位置创建一个锁对象
Object obj = new Object();
// 线程任务 卖票
@Override
public void run() {
while(true){
//建立锁对象
synchronized (obj){
//synchronized (this) 此时的this是唯一的当前类的对象
//当前类.class(用于继承的情况下),类也是对象
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
继承的情况下:
将Object obj = new Object();前用static修饰即可,就可以变成唯一的。
方式二:同步方法
1 创建一个方法 修饰符添加synchronized
2 把访问了 共享数据的代码放入到方法中
3 调用同步方法
public class RunnableImpl implements Runnable{
// 定义共享资源 线程不安全
private int ticket = 100;
// 线程任务 卖票
@Override
public void run() {
while(true){
payTicket();//调用下面synchronized修饰的方法
}
}
public synchronized void payTicket(){
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卖票操作
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
继承的情况下:
将 public synchronized void payTicket() 用static修饰即可。
静态同步方法的锁对象不是this, 其锁对象是本类的class文件对象
方式三:Lock 锁
class Window4 implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2.调用锁定方法:lock()
lock.lock();
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为" + ticket);
ticket--;
}else {
break;
}
}finally {
//3.调用解锁方法unlock()
lock.unlock();
}
}
}
}
由于synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否;而ReentrantLock是使用代码实现的,系统无法自动释放锁,需要在代码中的finally子句中显式释放锁lock.unlock()。另外,在并发量比较小的情况下,使用synchronized是个不错的选择;但是在并发量比较高的情况下,其性能下降会很严重,此时ReentrantLock是个不错的方案。
synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器;
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())。
wait 和 notify以及notifyAll:
1.wait、notify以及notifyAll都是Object对象的方法,他们必须在被 synchronized 同步的方法或代码块中调用,否则会报错。
- 调用wait方法会使该线程进入等待状态,并且会释放被同步对象的锁。
- notify操作可以唤醒一个因执行wait而处于阻塞状态的线程,使其进入就绪状态,被唤醒的线程会去尝试着获取对象锁,然后执行wait之后的代码。如果发出notify操作时,没有线程处于阻塞状态,那么该命令会忽略。注意执行notify并不会马上释放对象锁,会等到执行完该同步方法或同步代码块后才释放
- notifyAll方法可以唤醒等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的那个线程优先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。
wait和sleep的异同:
1.执行wait方法后悔立马释放对象锁
2.执行notify不会立马释放对象锁,需等该同步方法或同步块执行完。注意是同步的内容执行完,而不是该线程的run()方法执行完
最后说下 wait和sleep的区别,这也是面试经常面到的问题。
1.sleep是Thread类的方法,wait是Object类的方法
2.调用要求不同:sleep()可以在任何场景下调用,wait()必须在同步代码块或同步方法中使用
3.sleep不会立马释放对象锁,而wait会释放。