获取中...

-

Just a minute...

多线程引发的安全问题

由于线程是共享进程中的所有资源的,那么每个线程都可以去操作进程中的某一个资源,当多个线程操作同一个资源的时候就可能出现线程安全问题。比如两个线程对同一个实例变量做加1的操作,然后打印该变量,如果变量的初始值为0,当两个线程执行完后,打印的结果依次可能不是1和2,而是可能为1和1或者2和2,因为当一个线程对变量进行加1操作之后,在打印变量之前,第二个线程也对该变量做了加1的操作,这样打印的结果就是2和2,或者在第一个线程正在对变量做加1的时候,第二个线程也进来了,它获取到的变量的初始值还是0,结果打印的是1和1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UnSafeThread implements Runnable {

private Integer value = 0;

@Override
public void run() {
value++;
System.out.println(Thread.currentThread().getName() + ": value is " + value);
}

public static void main(String[] args) {

UnSafeThread unSafeThread = new UnSafeThread();

Thread thread1 = new Thread(unSafeThread, "thread1");
Thread thread2 = new Thread(unSafeThread, "thread2");
Thread thread3 = new Thread(unSafeThread, "thread3");

thread1.start();
thread2.start();
thread3.start();
}

}

异常结果为:

1
2
3
thread1: value is 1
thread2: value is 1
thread3: value is 2

上面的结果只是一种情况,其余的就不贴出来了。可以看到,当多线程在对资源进行访问的时候,如果改变的资源的值,这样就会出现线程安全问题,因为出现的结果和我们预想的不一样。这里的资源不仅仅是实例变量,还包括数据库、文件、或者一个对象等等。

怎么解决线程安全问题,基本上所有的并发编程在解决线程安全问题的时候,都是采用序列化访问临界资源的方式,在同一个时刻,只能有一个线程访问该资源,其他的线程处于等待的状态。如何实现序列化访问资源,一般可以在资源上加一个锁,当一个线程获取到资源,就为资源加上锁,当线程访问完资源,则释放掉锁,让下一个线程继续访问该资源。

synchronized的作用

synchronized是Java中的一个关键字,用来标记一段代码或者一个方法,当多个线程访问被标记的代码块或者方法的时候,这个线程会获取到一个对象锁,而其他的线程由于没有持有锁,就没有办法访问被标记的代码块或者方法,只有当持有锁的线程执行完后,释放了对象锁,其他的线程才有机会访问。
synchronized在标记代码块或者方法的时候,需要有一个锁对象。在Java中,每一个对象都拥有一个锁标记,也被称为监视器,线程只有获取该锁,才有资格访问被他控制的代码。所以,在被synchronized标记的代码块中,必须显示的传入一个对象作为锁,而如果在一个方法上,则不需要,因为拥有这个方法的实例对象也可以作为一个锁。

1
2
3
4
5
6
7
8
9
private Object object = new Object();

@Override
public void run() {
synchronized (object) {
value++;
System.out.println(Thread.currentThread().getName() + ": value is " + value);
}
}

结果为

1
2
3
thread1: value is 1
thread3: value is 2
thread2: value is 3

将代码修改为上面这样,多个线程访问的时候,就不会出现线程安全问题(不管执行多少次)。当然,可以把run()方法中的逻辑抽为一个方法,再被synchronized标记,这便是一个同步方法。

关于synchronized需要注意的地方

  • synchronized是一个互斥锁,即当一个线程拥有锁之后,其他线程是不能访问资源的。
  • 只能有一个线程访问同一个实例的synchronized方法
  • 可以有多个线程访问同一个实例的非synchronized方法
  • synchronized是一个重入锁,意思就是线程可以进入被它拥有的锁标记的所有代码块或者方法,不需要再次去获取锁,因为都是同一个锁。
  • 每一个类都有一个锁,用来控制类方法。
  • 对于使用synchronized标记的方法或者代码块,一旦程序出现异常,虚拟机会自动释放锁,不会造成死锁。

如果通过反编译字节码可以看到,被synchronized标记的代码块会多出两条指令,monitorenter和monitorexit。monitorenter指令执行时会让对象的锁计数加1,而monitorexit指令执行时会让对象的锁计数减1,对于synchronized方法,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。

相关文章
评论
分享
  • Java多线程之同步容器

    同步容器的作用Java中的容器主要为List、set、Map、Queue,这些容器都有不同的实现类,比如ArrayLIst、HashSet、HashMap、PriorityQueue等等,但是大部分都是非线程安全的,意味着在多线程访问...

    Java多线程之同步容器
  • Java多线程之ThreadLocal

    ThreadLocal的作用ThreadLocal的作用是提供线程内的局部变量,这个变量在同一个线程的生命周期内起作用,可以减少同一个线程内多个方法之前传入公共资源的复杂性,并且隔离其他线程。下面是一个使用ThreadLocal的例子...

    Java多线程之ThreadLocal
  • Java多线程之Lock

    引入Lock的原因Java提供了一个关键字synchronized用来保证同步访问,那为什么还需要使用Lock?最根本的原因就是使用synchronized会造成线程的阻塞,资源同时只能被一个线程访问,程序效率低。比如,多个线程查询读...

    Java多线程之Lock
  • Java中的线程的创建

    Java创建线程的三种方式 继承Thread 实现Runnable 实现Callable 继承Thread创建线程通过继承Thread的方式,可以很快的创建一个线程,Thread中有一个run()方法,只需要重写该方法,将需要另外开...

    Java中的线程的创建
  • 线程和进程

    什么是进程在百度百科中,进程有两种定义,一种是狭义定义:进程是正在运行的程序实例,比如微信、Tomcat、MySQL,这些就是一个个进程。另一种就是广义的定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,它是操作...

    线程和进程
  • Redis过期策略和内存淘汰策略

    Redis处理过期的keyRedis会将每个设置了过期时间的key保存在一个独立的字典中,以后会定期扫描这个字典来删除过期了的key。除了定期扫描删除策略之外,Redis同时采用惰性删除策略,在客户端访问key的时候,Redis会对这...

    Redis过期策略和内存淘汰策略
  • Redis持久化:AOF

    什么是AOF持久化Redis有两种持久化方式,RDB和AOF。RDB是将Redis中的数据保存一份到RDB文件中,而AOF不是保存键值对数据,而是保存服务器执行的写命令来记录数据库的状态。 AOF持久化的实现原理AOF持久化的实现可...

    Redis持久化:AOF
  • Redis持久化:RDB

    什么是RDB持久化RDB持久化是Redis的持久化方式之一,也被称为快照持久化,是将某一个时间节点的Redis内存中数据保存到磁盘上,生成一个RDB文件。之后,用户可以将这份文件进行备份,复制到其他的服务器上创建具有相同状态的Redi...

    Redis持久化:RDB
  • jps命令详解

    jps命令的作用jps命令是JDK中自带的命令,位于bin目录下,其作用是显示当前用户下系统中所有的Java进程的相关信息,比如pid。有了这个命令,可以很方便的显示Java进程,不过显示的是当前用户的所有Java进程,不能根据项目名...

    jps命令详解
Please check the parameter of comment in config.yml of hexo-theme-Annie!