现在我们就以售票问题来演示线程安全的问题
1, 在不对多线程数据进行保护的情况下会引发的状况
publicclassThreadUnSecurity {
staticinttickets = 10;
classSellTicketsimplementsRunnable{
@Override
publicvoidrun() {
//未加同步时产生脏数据
while(tickets > 0) {
System.out.println(Thread.currentThread().getName()+"--->售出第:"+tickets+" 票");
tickets--;
try{
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
if(tickets
System.out.println(Thread.currentThread().getName()+"--->售票结束!");
}
}
}
publicstaticvoidmain(String[] args) {
SellTickets sell=newThreadUnSecurity().newSellTickets();
Thread thread1=newThread(sell, "1号窗口");
Thread thread2=newThread(sell, "2号窗口");
Thread thread3=newThread(sell, "3号窗口");
Thread thread4=newThread(sell, "4号窗口");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
上述代码运行的结果:
我们可以看出同一张票在不对票数进行保护时会出现同一张票会被出售多次!由于线程调度中的不确定性,读者在演示上述代码时,出现的运行结果会有不同。
第一种实现线程安全的方式
同步代码块
packagecom.bpan.spring.beans.thread;
importcom.sun.org.apache.regexp.internal.recompile;
publicclassThreadSynchronizedSecurity {
staticinttickets = 10;
classSellTicketsimplementsRunnable{
@Override
publicvoidrun() {
//同步代码块
while(tickets > 0) {
synchronized(this) {
//System.out.println(this.getClass().getName().toString());
if(tickets
return;
}
System.out.println(Thread.currentThread().getName()+"--->售出第:"+tickets+" 票");
tickets--;
try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
if(tickets
System.out.println(Thread.currentThread().getName()+"--->售票结束!");
}
}
}
}
publicstaticvoidmain(String[] args) {
SellTickets sell=newThreadSynchronizedSecurity().newSellTickets();
Thread thread1=newThread(sell, "1号窗口");
Thread thread2=newThread(sell, "2号窗口");
Thread thread3=newThread(sell, "3号窗口");
Thread thread4=newThread(sell, "4号窗口");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
输出结果读者可自行调试,不会出现同一张票被出售多次的情况。
第二种 方式
同步方法
packagecom.bpan.spring.beans.thread;
publicclassThreadSynchroniazedMethodSecurity {
staticinttickets = 10;
classSellTicketsimplementsRunnable{
@Override
publicvoidrun() {
//同步方法
while(tickets > 0) {
synMethod();
try{
Thread.sleep(100);
}catch(InterruptedException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
if(tickets
System.out.println(Thread.currentThread().getName()+"--->售票结束");
}
}
}
synchronizedvoidsynMethod() {
synchronized(this) {
if(tickets
return;
}
System.out.println(Thread.currentThread().getName()+"---->售出第 "+tickets+" 票 ");
tickets--;
}
}
}
publicstaticvoidmain(String[] args) {
SellTickets sell=newThreadSynchroniazedMethodSecurity().newSellTickets();
Thread thread1=newThread(sell, "1号窗口");
Thread thread2=newThread(sell, "2号窗口");
Thread thread3=newThread(sell, "3号窗口");
Thread thread4=newThread(sell, "4号窗口");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
读者可自行调试上述代码的运行结果
第三种 方式
Lock锁机制, 通过创建Lock对象,采用lock()加锁,unlock()解锁,来保护指定的代码块
packagecom.bpan.spring.beans.thread;
importjava.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReentrantLock;
publicclassThreadLockSecurity {
staticinttickets = 10;
classSellTicketsimplementsRunnable{
Lock lock=newReentrantLock();
@Override
publicvoidrun() {
//Lock锁机制
while(tickets > 0) {
try{
lock.lock();
if(tickets
return;
}
System.out.println(Thread.currentThread().getName()+"--->售出第:"+tickets+" 票");
tickets--;
}catch(Exception e1) {
//TODO Auto-generated catch block
e1.printStackTrace();
}finally{
lock.unlock();
try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
if(tickets
System.out.println(Thread.currentThread().getName()+"--->售票结束!");
}
}
}
publicstaticvoidmain(String[] args) {
SellTickets sell=newThreadLockSecurity().newSellTickets();
Thread thread1=newThread(sell, "1号窗口");
Thread thread2=newThread(sell, "2号窗口");
Thread thread3=newThread(sell, "3号窗口");
Thread thread4=newThread(sell, "4号窗口");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
最后总结:
由于synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否;而ReentrantLock是使用代码实现的,系统无法自动释放锁,需要在代码中的finally子句中显式释放锁lock.unlock()。
另外,在并发量比较小的情况下,使用synchronized是个不错的选择;但是在并发量比较高的情况下,其性能下降会很严重,此时ReentrantLock是个不错的方案。
补充:
在使用synchronized 代码块时,可以与wait()、notify()、nitifyAll()一起使用,从而进一步实现线程的通信。
其中,wait()方法会释放占有的对象锁,当前线程进入等待池,释放cpu,而其他正在等待的线程即可抢占此锁,获得锁的线程即可运行程序;线程的sleep()方法则表示,当前线程会休眠一段时间,休眠期间,会暂时释放cpu,但并不释放对象锁,也就是说,在休眠期间,其他线程依然无法进入被同步保护的代码内部,当前线程休眠结束时,会重新获得cpu执行权,从而执行被同步保护的代码。
wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会释放对象锁。
notify()方法会唤醒因为调用对象的wait()而处于等待状态的线程,从而使得该线程有机会获取对象锁。调用notify()后,当前线程并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM会在等待的线程中调度一个线程去获得对象锁,执行代码。
需要注意的是,wait()和notify()必须在synchronized代码块中调用。
notifyAll()是唤醒所有等待的线程。
下面是示例代码,
packagecom.bpan.spring.beans.thread;
publicclassThreadDemo {
staticfinalObject obj =newObject();
//第一个子线程
staticclassThreadAimplementsRunnable{
@Override
publicvoidrun() {
intcount = 10;
while(count > 0) {
synchronized(ThreadDemo.obj) {
System.out.println("A-----"+count);
count--;
synchronized(ThreadDemo.obj) {
//notify()方法会唤醒因为调用对象的wait()而处于等待状态的线程,从而使得该线程有机会获取对象锁。
//调用notify()后,当前线程并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,
ThreadDemo.obj.notify();
try{
ThreadDemo.obj.wait();
}catch(InterruptedException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
staticclassThreadBimplementsRunnable{
@Override
publicvoidrun() {
intcount = 10;
while(count > 0) {
synchronized(ThreadDemo.obj) {
System.out.println("B-----"+count);
count--;
synchronized(ThreadDemo.obj) {
//notify()方法会唤醒因为调用对象的wait()而处于等待状态的线程,从而使得该线程有机会获取对象锁。
//调用notify()后,当前线程并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,
ThreadDemo.obj.notify();
try{
ThreadDemo.obj.wait();
}catch(InterruptedException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
publicstaticvoidmain(String[] args) {
newThread(newThreadA()).start();
newThread(newThreadB()).start();
}
}
领取专属 10元无门槛券
私享最新 技术干货