Java线程安全知识整理

一、线程安全

一个程序中有多个线程同时运行,并且这些线程运行某段相同的代码,程序运行的结果与单线程运行的结果相同,而其他的变量值也和预期的一样,这个程序就是线程安全的。

多个线程程序往往会操作同一个共享数据,就可能会出现安全问题。

比如就像我们在网上买东西,同一件商品可能会有很多的用户在同时购买,但商品的库存量是一定的,如果在某一时刻库存量变为了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){
        
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值