你的线程安全吗??


最近在看面经,关于Java的面试,老生常谈的一个问提就是多线程的安全问题和线程的同步,在此我不免要从它的概念开始讲述。

一、什么是非线程安全

我相信,浏览这篇博客的读者都知道什么是进程,什么是线程,进程与线程的区别。(如果你还不知道,那么可以去查阅一下。)其中重要的一点区别就是:进程拥有独立的地址空间,拥有属于自己的在资源,而线程不拥有资源,它共享着进程的资源。所以当一个进程中的多线程并发运行时,就容易造成数据的不一致和数据污染问题。比如:
小王在某银行存了100元,然后它要取50元,这时进程A查询信息剩余金钱100元,可以取钱,此时进程A修改金钱100-50,然后小王又要取100,如果进程B优先进程A获取剩余金钱100,那么它可以继续取钱,人们设想的是进程B应该在数据更改之后才能执行,但是线程是并发执行的,我们若不规定它的执行顺序,那么银行就会亏损。
在这里插入图片描述

二、判断线程是否安全的标准

下面这个函数,是线程安全的吗?
显然,是安全的,不管在什么情况下,最后的执行结果都是count=1。我们看到这段代码没有任何状态,它没有任何作用域,也没有任何引用,执行的作用范围只存在它这条现成的局部变量中,不对其他线程产生影响,也就是说多线程间没有共享数据,彼此间的行为相对独立互不影响,那么线程就是安全的

public void test() {
		int count = 0;
		count = count+1;
		System.out.println("count = "+count);
	}

相反如果我们给这段代码添加状态,我们将count设为全局变量,如果单线程运行结果是唯一的,如果多线程运行结果就不唯一了。

static int count = 0;//全局变量
	public void test() {
		count = count++;
		System.out.println("count = "+count);
	}

多线程运行结果显示如下,可以发现于我们设想的结果完全不一样。
在这里插入图片描述
综上,我们可以发现,判断一个程序是否存在线程安全问题的标准在于
1.是否是多线程环境
2.是否存在共享数据
3.是否有多条语句操作共享数据

三、实现线程安全的方式

针对线程不安全的存在条件,在Java中有两种解决思路
1.让共享资源至多被一个线程占用——同步
2.让线程拥有独立资源,不去共享资源

四、同步的实现方式

1.synchronized(互斥锁)

synchronized关键字用来控制线程同步,保证在多线程环境下,保证共享数据的一致性,它既可以修饰方法也可以修饰代码块,但修饰方法更加常用。

1.修饰方法:
实现原理:synchronized相当于一把锁,用来锁住对象,其他线程只有等当前线程执行完数据释放锁对象才能使用,否则一直处于等待状态。
注:

  • 非静态同步方法:锁对象默认是this
  • 静态同步方法:锁对象默认是:类名.class(每个类都有一个class对象,而且是唯一的)

实现方法

修饰符 synchronized 返回值类型 方法名(参数列表){
}

举例说明:

   public class MyTest implements Runnable{
    	static int count=0;
    	public synchronized void test() {
    		count++;
    		System.out.println("count="+count);
    	}
    	public void run() {
    		test();
    	}
    	public static void main(String[] args) {
    		MyTest mt = new MyTest();
    		for(int i=0;i<10;i++) {
    			Thread t = new Thread(mt);
    			t.start();	
    		}
    	}
    }

运行结果显示线程安全了!
在这里插入图片描述
synchronized在使用时有一个经常忽略的问题:它锁的是括号内的对象而不是代码,如果多线程处理的不是一个对象,那么synchronized锁就不起作用了,我们将上面的代码main函数修改成如下所示,每次循环产生一个新的MyTest对象,这样运行结果出现了错误。

for(int i=0;i<10;i++) {
		MyTest mt = new MyTest();
		Thread t = new Thread(mt);
		t.start();	
	}

在这里插入图片描述
2.修饰代码块

实现原理:
保证同一时间只有一个线程执行代码块中的代码。
实现方法:
synchronized(锁){
// 操作共享资源的代码
}
注:括号中的锁可以是任何对象
举例说明:修改上述代码

 //创建锁对象
    	private static Object lock = new Object();
    	public void test() {
    		synchronized(lock){
    			count++;
    			System.out.println("count="+count);
    		}
    	}

运行结果如下:
在这里插入图片描述

2.Lock(可重入锁)

synchronized虽然实现简单,但是容易浪费资源,假设一个线程获取了对应的锁并执行,那么其他线程需要用到该资源只能无穷等待,只有当该线程释放锁。释放锁有两种情况:
1.获取锁的线程执行结束,自动释放锁
2.获取锁的线程执行发生异常,JVM让线程放锁
因此在JDK1.5中java为我们提供了Lock,实现synchronized相同的功能,并且让锁有了可操作性,可以手动的获取和释放锁,甚至还可以中断获取以及超时获取的同步特性,但是从使用上说Lock明显没有synchronized使用起来方便快捷。
Lock和synchronized的区别:
1.Lock是一个接口,基于JDK层面实现,而synchronized是Java中的关键字,synchronized是Java内置的语言,基于JVM实现。
2.synchronized不需要手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
Lock用于获取锁,但它不会主动释放锁所以需要与unlock()配合使用。一般在使用Lock时必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
在这里我推荐大家去看博客可以说写的很详细了!https://blog.csdn.net/csdnnews/article/details/82321777#commentBox

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章