多线程出现安全问题
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式:同步机制
方式一:同步代码块
synchronized(同步监视器){//需要被同步的代码
}
说明:
1.操作共享数据的代码,即为需要被同步的代码。
2.共享数据:多个线程共同操作的变量,比如: 车票就是共享数据。
3.同步监视器,俗称:锁,任何一个类的对象,都可以充当锁。(但是要求:多个线程必须共用同一把锁,即同一个对象)(可以考虑用this充当)(可以考虑类.class
充当)
示例:
①Runnable接口实现类
public class MThread implements Runnable{private int ticket = 100;Object obj = new Object();@Overridepublic void run() {while (true) {synchronized (obj) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + ":票号为" + ticket);ticket--;} else break;}}}
}
②创建并执行多线程
public class TestThread {public static void main(String[] args) {MThread thread = new MThread();Thread curThread1 = new Thread(thread);Thread curThread2 = new Thread(thread);Thread curThread3 = new Thread(thread);curThread1.setName("窗口一");curThread2.setName("窗口二");curThread3.setName("窗口三");curThread1.start();curThread2.start();curThread3.start();}
}
同步的方式,解决了线程的安全问题。
操作同步代码时,只能有一 个线程参与, 其他线程等待,相当于是一个单线程的过程,效率低。
注意:在继承Thread类这种情况下,需要将同步监视器声明为静态的,这样才能保证同一把锁。
方式二:同步方法
synchronized还可以放在方法声明中,表示整个方法为同步方法。
public synchronized void show (){ //其实是有一个同步监视器(锁)this//需要被同步的代码
}
与同步代码块使用类似,只是在run()方法中调用该同步方法。
注意:和使用同步代码块一样,在继承Thread类这种情况下,需要将该同步方法声明为静态的,即public static synchronized void show ()
,此时的同步监视器相当于是当前类.class
。
同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
非静态的同步方法,同步监视器是: this
静态的同步方法,同步监视器是:当前类本身
方式三:Lock锁方式
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
class A{private final ReentrantLock lock = new ReenTrantLock();public void run(){lock.lock();try{//保证线程安全的代码;}finally{lock.unlock(); }}
}
synchronized 与 Lock 的对比:
①Lock是显式锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放。
②Lock只有代码块锁,synchronized有代码块锁和方法锁。
③使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)。