当多条语句在操作同一个线程共享数据的时候,一个线程对多条语句只执行了一部分,还没有执行完的时候,另外一个线程参与进来执行。导致线程共享数据出现错误。例如在对同一个账户进行取款时,账户余额为1000元,A某个时刻正在取走1000元时,B也参与进来取走1000元,此时,账户余额就会出现错误,这就是线程安全的问题。
解决这类安全问题,我们只能让一个线程都执行完,执行过程中其他线程不得参与进来。完毕后,再让另一条线程执行进来。Java对于这种多线程的安全问题提供了专业的解决方式:同步机制。本文就是介绍几种解决方式。
1、同步锁
在讲到方法之前不得不先提到同步锁机制。
"对于并发工作,你需要某种方式来防止两个任务访问相同的资源。防止这种冲突的方法就是当资源被一个任务使用时,在其加上锁。第一个访问某项资源的任务时必须锁定这项资源,使其他任务在被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定使用它了"引用至《Thinking in Java》
说到底synchronized锁(同步锁)可以是任意对象。所有的对象都自动含有单一的锁。也可以叫做监视器。必须保证使用同一个资源的多个线程共用一把锁。否则无法保证共享资源的安全。
2、同步代码块 synchronized(对象){ 需要被同步的代码}
对于同步代码块而言。synchronized锁可以自己指定,一般在继承Thread时,变量都为静态,用.class
实现Runnable接口时用this。代码如下:
class Windows extends Thread{ //每一个对象都调用一次ticket变量 因此有静态变量只用一次 private static int ticket = 100; @Override public void run() { while (true){ synchronized (Windows.class) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "窗口:票号为" + ticket); ticket--; } else { break; } } } } } public class WindowsTest { public static void main(String[] args) { Windows w1 = new Windows(); Windows w2 = new Windows(); Windows w3 = new Windows(); //给窗口命名 w1.setName("窗口一"); w2.setName("窗口二"); w3.setName("窗口三"); w1.start(); w2.start(); w3.start(); } }
运行结果如下:
窗口一窗口:票号为10 窗口三窗口:票号为9 窗口三窗口:票号为8 窗口三窗口:票号为7 窗口二窗口:票号为6 窗口三窗口:票号为5 窗口一窗口:票号为4 窗口三窗口:票号为3 窗口三窗口:票号为2 窗口二窗口:票号为1
3、Lock锁
Lock是显示锁(需要手动开启和关闭锁),synchronized是隐式锁,出了自动域自动释放。Lock也只有代码块锁,没有方法锁。使用LockJVM将花费较少的时间来调整线程,性能更好。更好的扩展性,提供更多的子类。
代码如下:
class Windows2 implements Runnable { private int ticket = 100; //创建锁的对象 private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true) { try { //开启锁 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 { lock.unlock(); } } } } public class LockTest { public static void main(String[] args) { Windows2 w = new Windows2(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); } }
运行结果如下:
窗口一买票:票号为10 窗口二买票:票号为9 窗口三买票:票号为8 窗口三买票:票号为7 窗口一买票:票号为6 窗口二买票:票号为5 窗口二买票:票号为4 窗口三买票:票号为3 窗口一买票:票号为2 窗口二买票:票号为1