Day06-线程安全问题
例子:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式
-
问题:卖票过程中,出现了重票\错票 - - > 出现了线程的安全问题
-
问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
-
如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来.直到线程a操作完ticket时,线程才可以开始操作ticket.这种情况即使线程a出现了阻塞,也不能被改变
-
在Java中,我们通过同步机制,来解决线程的安全问题
-
同步的方式,解决了线程的安全问题. - ->好处
操作同步代码时,只能有一个线程参与,其他线程等待.相当于一个单线程的过程,效率低 - - >局限性
方式一:同步代码块
synchronized(同步监视器){
需要被同步的代码
}
说明:1. 操作共享数据的代码,即为需要被同步的代码 --> 不能包含代码多了,也不能包含代码少了
2. 共享数据:多个线程共同操作的变量.比如:ticket就是共享数据
3. 同步监视器,俗称:锁.任何一个类的对象,都可以充当锁.
要求:读几个线程必须要共用同一把锁
补充:在实现Runnable接口多线程的方式中,我们可以考虑使用this充当同步监视器
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的.
(static) synchronized + 同步方法{
}
关于同步方法的总结:
-
同步方法仍然涉及到同步监视器,只是不需要我们显式的说明
-
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
方式三: LOCK锁 - - - JDK5.0新增
-
实例化ReentrantLock
-
调用锁定方法lock()
-
调用解锁方法unlock()
补充:synchroized 与lock的异同?
同:二者都可以解决线程安全问题
异:synchroized机制在执行完相应的同步代码以后,自动的释放同步监视器
lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
优先使用LOCK->同步代码块(已经进入了方法体,分配了相应资源)->同步方法(在方法之外)
死锁
-
死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
-
说明:出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
我们使用同步时,要避免出现死锁.
线程通行的三个方法
线程通行的例子:使用两个线程打印1-100,线程1,线程2,交替打印
涉及到的三个方法:
wait():一旦执行此方法,当前线程就会进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会唤醒被wait的一个线程.如果有多个线程被wait,就唤醒优先级高的那个.
notifyall():一旦执行此方法,就会唤醒所有被wait的一个线程.
注意点:
-
这三个方法只能使用在同步代码块或者同步方法中,不能用LOCK的方法
-
这三个方法的调用者必须是同步代码块或同步方法中的同步监视器.否则没回出现IllegalMonitorStateException异常
-
这三个方法是定义在java.lang.Object类中
Slepp()和Wait()的异同
1.相同点:一旦执行方法.都可以使得当前的线程进入阻塞状态
2不同点:1) 两个方法声明的位置不同:Thread类中声明sleep() , Object中声明wait()
2) 调用的要求不同: sleep()可以在任何需要的场景下调用. wait()必须使用在同步代码中
3) 关于是否释放同步监视器:如果两个方法都使用在同步代码块或者同步方法中,sleep()不会释放锁,wait() 会释放锁