多线程(2)

目录

4.线程安全

4.1修改共享数据

4.2原子性

4.3可见性

5.synchronized关键字-监视器锁monitor lock

5.1synchronized的特性

6. volatile关键字

6.1能保证内存可见性

7.wait和notify

7.1 wait()方法

wait 做的事情:

 7.2 notify()方法

7.3 notifyAll()方法

7.4 wait和sleep的对比 


书接上回,接下来我们继续介绍多线程

4.线程安全

4.1修改共享数据

static class Counter {
    public int count = 0 ;
    void increase () {
        count ++ ;
  }
}
public static void main ( String [] args ) throws InterruptedException {
    final Counter counter = new Counter ();
    Thread t1 = new Thread (() -> {
        for ( int i = 0 ; i < 50000 ; i ++ ) {
            counter . increase ();
      }
  });
    Thread t2 = new Thread (() -> {
        for ( int i = 0 ; i < 50000 ; i ++ ) {
            counter . increase ();
      }
  });
    t1 . start ();
    t2 . start ();
    t1 . join ();
    t2 . join ();
    System . out . println ( counter . count );
}

比如这个代码 ,有多个线程对counter.count变量进行修改,此时这个变量是一个多个线程都能访问到的“共享数据”

4.2原子性

什么是原子性呢?

 比如一个售票系统,客户端a检查还有一张票,将票卖掉,此时数据库中的数据还没来得及修改,客户端b也检查发现还有一张票,将票卖掉,此时同一张票就被卖了两次。这就不符合原子性

这种操作是相互排斥的。

不保证原子性会带来什么样的问题

如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。

4.3可见性

可见性是指,一个线程对共享变量的修改,能够及时被其他线程看到

  •  线程之间的共享变量存在 主内存 (Main Memory).
  • 每一个线程都有自己的 " 工作内存 " (Working Memory)
  • 当线程要读取一个共享变量的时候 , 会先把变量从主内存拷贝到工作内存 , 再从工作内存读取数据 .
  • 当线程要修改一个共享变量的时候 , 也会先修改工作内存中的副本 , 再同步回主内存 .
    下面举个例子
    初始情况下,两个线程的工作内存内容一致

但是一旦一个线程修改了a的值,此时主内存不一定能及时同步,所对应的另一个线程中a的值也不一定同步

所以,此时线程是不安全的

5.synchronized关键字-监视器锁monitor lock

5.1synchronized的特性

1.互斥

synchronized 会起到互斥效果 , 某个线程执行到某个对象的 synchronized 中时 , 其他线程如果也执行到
同一个对象 synchronized 就会 阻塞等待

 比如这种,进入synchronized 修饰的代码块就相当于加锁,推出代码块就相当于解锁

  synchronized void increase () {
        count ++ ;
  }

 

针对每一把锁 , 操作系统内部都维护了一个等待队列 . 当这个锁被某个线程占有的时候 , 其他线程尝
试进行加锁 , 就加不上了 , 就会阻塞等待 , 一直等到之前的线程解锁之后 , 由操作系统唤醒一个新的
线程 , 再来获取到这个锁
2.刷新内存
synchronized 的工作过程 :
1.获得互斥锁
2.从主内存拷贝变量的最新副本到工作的内存
3.执行代码
4.将更改后的共享变量的值刷新到主内存
5.释放互斥锁
3.可重入
synchronized同步块对同一条线程来说是可重入的,不会出现把自己锁死的问题。
如果一个线程没有释放锁又尝试继续加锁,就会出现把自己锁死的问题。
这种锁被称为不可重入锁,而synchronized是可重入锁。

6. volatile关键字

6.1能保证内存可见性

代码在写入 volatile 修饰的变量的时候
改变线程工作内存中 volatile 变量副本的值
将改变后的副本的值从工作内存刷新到主内存
代码在读取 volatile 修饰的变量的时候
从主内存中读取 volatile 变量的最新值到线程的工作内存中
从工作内存中读取 volatile 变量的副本

但是此关键字并不能保证原子性

7.wait和notify

 线程之间是抢占式执行的,所以线程之间的执行先后顺序不可知,但是实际开发的时候希望合理的协调多个线程之间的执行先后顺序,完成这个协调工作主要涉及三个方法

  • wait() / wait(long timeout): 让当前线程进入等待状态 .
  • notify() / notifyAll(): 唤醒在当前对象上等待的线程.

7.1 wait()方法

wait 做的事情:

使当前执行代码的线程进行等待. (把线程放到等待队列中)
释放当前的锁
满足一定条件时被唤醒, 重新尝试获取这个锁.
wait 结束等待的条件 :
其他线程调用该对象的 notify 方法.
wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

 7.2 notify()方法

notify 方法是唤醒等待的线程 .
方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的
其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 "先来后到")
notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行
完,也就是退出同步代码块之后才会释放对象锁。

7.3 notifyAll()方法

 notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.

7.4 wait和sleep的对比 

wait用于线程之间通信,sleep用于让线程阻塞一段时间,唯一的相同点就是可以让线程放弃执行一段时间。

总结就是:

1.wait需要搭配synchronied使用,而sleep不需要

2.wait是Objeck的方法,而sleep是Thread的静态方法

  • 36
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值