目录
5.synchronized关键字-监视器锁monitor lock
书接上回,接下来我们继续介绍多线程
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 void increase () {count ++ ;}
6. volatile关键字
6.1能保证内存可见性
但是此关键字并不能保证原子性
7.wait和notify
线程之间是抢占式执行的,所以线程之间的执行先后顺序不可知,但是实际开发的时候希望合理的协调多个线程之间的执行先后顺序,完成这个协调工作主要涉及三个方法
-
wait() / wait(long timeout): 让当前线程进入等待状态 .
-
notify() / notifyAll(): 唤醒在当前对象上等待的线程.
7.1 wait()方法
wait 做的事情:
7.2 notify()方法
7.3 notifyAll()方法
notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.
7.4 wait和sleep的对比
wait用于线程之间通信,sleep用于让线程阻塞一段时间,唯一的相同点就是可以让线程放弃执行一段时间。
总结就是:
1.wait需要搭配synchronied使用,而sleep不需要
2.wait是Objeck的方法,而sleep是Thread的静态方法