Java中确保线程安全最常用的两种方式

什么是线程安全,你真的了解吗?我们今天先来看段代码:

public void threadMethod(int j) {int i = 1;j = j + i;
}
```大家觉得这段代码是线程安全的吗?毫无疑问,它绝对是线程安全的,我们来分析一下为什么它是线程安全的?我们可以看到这段代码是没有任何状态的,什么意思,就是说我们这段代码不包含任何的作用域,也没有去引用其他类中的域进行引用,它所执行的作用范围与执行结果只存在它这条线程的局部变量中,并且只能由正在执行的线程进行访问。当前线程的访问不会对另一个访问同一个方法的线程造成任何的影响。两个线程同时访问这个方法,因为没有共享的数据,所以他们之间的行为并不会影响其他线程的操作和结果,所以说**无状态的对象也是线程安全的。**

 **添加一个状态呢?**

如果我们给这段代码添加一个状态,添加一个count,来记录这个方法并命中的次数,每请求一次count+1,那么这个时候这个线程还是安全的吗?```
public class ThreadDemo { int count = 0; // 记录方法的命中次数 public void threadMethod(int j) {  count++ ; int i = 1; j = j + i; }
}
```很明显已经不是了,单线程运行起来确实是没有任何问题的,但是当出现多条线程并发访问这个方法的时候,问题就出现了,我们先来分析下count+1这个操作。进入这个方法之后首先要读取count的值,然后修改count的值,最后才把这把值赋值给count,**总共包含了三步过程:“读取”一>“修改”一>“赋值”**,既然这个过程是分步的,那么我们先来看下面这张图,看看你能不能看出问题:

<img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/9/2/16598670dc857ff6~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image" style="margin: auto" />

可以发现,count的值并不是正确的结果,当线程A读取到count的值,但是还没有进行修改的时候,线程B已经进来了,然后线程B读取到的还是count为1的值,正因为如此所以我们的count值已经出现了偏差,那么这样的程序放在我们的代码中是存在很多的隐患的。**2、如何确保线程安全?**

既然存在线程安全的问题,那么肯定得想办法解决这个问题,怎么解决?我们说说常见的几种方式。**2.1、synchronized**

synchronized关键字就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。```
public class ThreadDemo { int count = 0; // 记录方法的命中次数 public synchronized void threadMethod(int j) { count++ ; int i = 1; j = j + i; }
}
```这样就可以确保我们的线程同步了,同时这里需要注意一个大家平时忽略的问题,**首先synchronized锁的是括号里的对象,而不是代码,其次,对于非静态的synchronized方法,锁的是对象本身也就是this**。当synchronized锁住一个对象之后,别的线程如果想要获取锁对象,那么就必须等这个线程执行完释放锁对象之后才可以,否则一直处于等待状态。**注意点**:虽然加synchronized关键字可以让我们的线程变的安全,但是我们在用的时候也要注意缩小synchronized的使用范围,如果随意使用时很影响程序的性能,别的对象想拿到锁,结果你没用锁还一直把锁占用,这样就应了一句话:占着茅坑不拉屎,属实有点浪费资源。**2.2、Lock**

先来说说它跟synchronized有什么区别吧,Lock是在Java1.6被引入进来的,Lock的引入让锁有了可操作性,什么意思?就是我们在需要的时候去手动的获取锁和释放锁,甚至我们还可以中断获取以及超时获取的同步特性,但是从使用上说Lock明显没有synchronized使用起来方便快捷。我们先来看下一般是如何使用的:

private Lock lock = new ReentrantLock(); // ReentrantLock是Lock的子类 private void method(Thread thread){ lock.lock(); // 获取锁对象 try { System.out.println(“线程名:”+thread.getName() + “获得了锁”); // Thread.sleep(2000); }catch(Exception e){ e.printStackTrace(); } finally { System.out.println(“线程名:”+thread.getName() + “释放了锁”); lock.unlock(); // 释放锁对象 } }


public static void main(String[] args) { LockTest lockTest = new LockTest(); // 线程1 Thread t1 = new Thread(new Runnable() { @Override public void run() { // Thread.currentThread()返回当前线程的引用 lockTest.method(Thread.currentThread()); } }, “t1”); // 线程2 Thread t2 = new Thread(new Runnable() { @Override public void run() { lockTest.method(Thread.currentThread()); } }, “t2”); t1.start(); t2.start(); }


<img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/9/2/16598670dc353796~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image" style="margin: auto" />

可以看出我们的执行是没有任何问题的。其实在Lock还有几种获取锁的方式,我们这里再说一种就是**tryLock()**这个方法跟Lock()是有区别的,Lock在获取锁的时候如果拿不到锁就一直处于等待状态,直到拿到锁,但是tryLock()却不是这样的,tryLock是有一个Boolean的返回值的,如果没有拿到锁直接返回false,停止等待,它不会像Lock()那样去一直等待获取锁。我们来看下代码:

private void method(Thread thread){ // lock.lock(); // 获取锁对象 if (lock.tryLock()) { try { System.out.println(“线程名:”+thread.getName() + “获得了锁”); // Thread.sleep(2000); }catch(Exception e){ e.printStackTrace(); } finally { System.out.println(“线程名:”+thread.getName() + “释放了锁”); lock.unlock(); // 释放锁对象 } } }


private void method(Thread thread) throws InterruptedException { // lock.lock(); // 获取锁对象 // 如果2秒内获取不到锁对象,那就不再等待 if (lock.tryLock(2,TimeUnit.SECONDS)) { try { System.out.println(“线程名:”+thread.getName() + “获得了锁”); // 这里睡眠3秒 Thread.sleep(3000); }catch(Exception e){ e.printStackTrace(); } finally { System.out.println(“线程名:”+thread.getName() + “释放了锁”); lock.unlock(); // 释放锁对象 } } }


<img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/9/2/16598670dce8cc65~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image)我们再来改一下这个等待时间,改为5秒,再来看下结果:``" style="margin: auto" />
private void method(Thread thread) throws InterruptedException { // lock.lock(); // 获取锁对象 // 如果5秒内获取不到锁对象,那就不再等待 if (lock.tryLock(5,TimeUnit.SECONDS)) { try { System.out.println("线程名:"+thread.getName() + "获得了锁"); }catch(Exception e){ e.printStackTrace(); } finally { System.out.println("线程名:"+thread.getName() + "释放了锁"); lock.unlock(); // 释放锁对象 } } }
```**结果**:这个时候我们可以看到,线程t2等到5秒获取到了锁对象,执行了任务代码。

<img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/9/2/165986716a85c495~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image" style="margin: auto" />

这就是使用Lock来保证我们线程安全的方式,其实Lock还有好多的方法来操作我们的锁对象,这里我们就不多说了,大家有兴趣可以看一下API。

**PS**:现在你能做到如何确保一个方法是线程安全的吗?
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值