一、线程安全
一个程序中有多个线程同时运行,并且这些线程运行某段相同的代码,程序运行的结果与单线程运行的结果相同,而其他的变量值也和预期的一样,这个程序就是线程安全的。
多个线程程序往往会操作同一个共享数据,就可能会出现安全问题。
比如就像我们在网上买东西,同一件商品可能会有很多的用户在同时购买,但商品的库存量是一定的,如果在某一时刻库存量变为了0,却有用户购买到了商品,这就是多线程程序可能出现的安全问题之一。
例:
public class Shopping implements Runnable{
//定义商品库存
int a = 50;
@Override
public void run() {
while (true){
if(a > 0){
try {
//手动模拟线程丢失cpu资源
Thread.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是线程"+Thread.currentThread()+",购买到了"+a--+"商品");
}
}
}
}
//测试类
class Test{
public static void main(String[] args){
Shopping s = new Shopping();
//创建三个Thread类对象,传Runnable接口实现类对象
Thread t0 = new Thread(s);
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t0.start();
t1.start();
t2.start();
}
}
以上程序某次运行的部分结果如下:
我是线程Thread[Thread-1,5,main],购买到了3商品
我是线程Thread[Thread-2,5,main],购买到了2商品
我是线程Thread[Thread-2,5,main],购买到了1商品
我是线程Thread[Thread-1,5,main],购买到了0商品
我是线程Thread[Thread-0,5,main],购买到了-1商品
可以看到,购买的商品出现了0,甚至出现了负数,这显然是不符合我们现实生活中的需求。
二、线程安全问题的解决
当一个线程运行某一代码段时,让其他线程无法运行这一代码段,直到前面的线程运行完毕之后,其他的线程才能运行这一代码段操作。保证数据的操作只能由一个线程来操作。在Java中使用同步技术来解决这一问题,使用synchronized关键字。(降低程序运行的效率,提高了安全性)
格式:(也可以直接修饰方法)
synchronized(对象监视器){
线程操作的共享数据
}
修改上例代码,如下:
public class Shopping implements Runnable{
//定义商品库存
int a = 50;
@Override
public void run() {
while (true){
//同步代码块
synchronized (this) {
if (a > 0) {
try {
//手动模拟线程丢失cpu资源
Thread.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是线程" + Thread.currentThread() + ",购买到了" + a-- + "商品");
}
}
}
}
}
直接修饰方法,例:
public class Shopping implements Runnable{
//定义商品库存
int a = 50;
@Override
public void run() {
//调用同步方法
buy();
}
//同步方法
public synchronized void buy(){
while (true){
if (a > 0) {
try {
//手动模拟线程丢失cpu资源
Thread.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是线程" + Thread.currentThread() + ",购买到了" + a-- + "商品");
}
}
}
}
三、Lock接口
JDK1.5 新特性之一,Lock接口。
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
修改以上例子的,使用Lock接口,如下:
public class Shopping implements Runnable{
//定义商品库存
int a = 50;
//创建lock接口实现类的对象
Lock l = new ReentrantLock();
@Override
public void run() {
while (true){
//调用lock方法,获取锁
l.lock();
if (a > 0) {
try {
//手动模拟线程丢失cpu资源
Thread.sleep(15);
System.out.println("我是线程" + Thread.currentThread() + ",购买到了" + a-- + "商品");
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
//调用unlock方法释放锁,无论是否有异常,都得释放锁
l.unlock();
}
}
}
}
}
四、死锁
多线程程序,出现了同步的嵌套,导致其它线程无线等待,此种现象称之为死锁。
可能出现示例:
synchronized (锁1){
synchronized (锁2){
}
}