当多条语句在操作同一个线程共享数据的时候,一个线程对多条语句只执行了一部分,还没有执行完的时候,另外一个线程参与进来执行。导致线程共享数据出现错误。例如在对同一个账户进行取款时,账户余额为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