Java多线程安全问题——同步机制
Java的多线程会有什么安全问题呢?
我们来看下面一个案例:
有一个景点总共有100张门票,有三个窗口同时在卖票。
我们用程序来模拟一下。
class Window implements Runnable{
private int ticket = 100; //总票数100张,三个窗口共用,称为共享数据
@Override
public void run() {
while (true){
if(ticket > 0){
//Thread.currentThread().getName() 获取当前线程的名字
System.out.println(Thread.currentThread().getName()+ ":卖票,票号为:" + ticket);
ticket--;
}else {
break;
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window window = new Window();
Thread window1 = new Thread(window);
Thread window2 = new Thread(window);
Thread window3 = new Thread(window);
window1.setName("窗口1"); //setName()设置线程的名字
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
看起来好像就是这样,创建三个多线程模拟三个窗口,然后卖票,票是共享数据,三个窗口共用,没有什么问题。
可是实际上呢?我们一运行就会发现问题了。
如图上所看到的,出现了重票!
三个窗口都卖了张票号 为100 的票。
这是怎么回事呢?
我们用张图来说明:
这就是线程的安全问题。
也就是在一个线程执行操作还未完成时,另一个线程就进来了。
相信大家都能想到解决的办法,那就是 在一个线程执行操作还未完成时,阻止其他线程的去执行操作。
在Java中,有三种方法实现这个办法。
方式一:同步代码块
synchronized(同步监视器/锁){
//需要被同步的代码
//不能包含多了 或 少了
}
1.同步监视器,也称为 锁 :任何一个类的的对象,都可以充当锁。
2.需要被同步的代码:指 操作共享数据的代码 (共享数据,多个线程共同操作的变量,比如 多个窗口卖的 票)
看起来似乎很简单,我们来试试
没有重票了!也没有错票了!
就这么简单,你学会了吗?
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,可以直接将此方法声明为同步的
例如:
注意:1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
2. 非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
方式三:Lock(锁) ——JDK5.0新增
class Window implements Runnable{
private int ticket = 100;
//1.实例化
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2.调用锁定方法lock()
lock.lock();
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁的方法
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
注意点:
- 使用 同步代码块 和 同步方法 时,一定确保,多个线程共用一把锁!
- 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器
同步方式的利弊:利,解决了线程的安全问题。
弊,降低了效率。(操作同步代码时,只能一个线程操作,其他线程等待)
synchronized 与 Lock的异同
相同:都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock(),同时结束同步也需要手动的实现(unlock())
Java多线程的其他内容,将在后续文章中更新。