一、了解Synchronized关键字嘛?简单说说~
Synchronized关键字,表示对当前的普通方法/静态方法/代码块进行加锁,当代码运行至该方法时,要检查有没有其他线程正在用这个方法,有的话要等别d的线程用完之后再锁定调用,接着执行代码。synchronized有三个特性,分别是: 1.互斥 某个线程执行到某个对象的synchronized时,若其他线程也需执行同一个对象的synchronized,此时就会阻塞等待。 进入Synchronized修饰的代码块,即加锁;退出Synchronized修饰的代码块,即解锁; Synchronized的使用: ① 直接修饰普通方法(锁的是SynchronizedDemo 类的实例)
public class SynchronizedDemo {
public synchronized void methond() {
}
}
② 修饰静态方法(针对 类对象 进行加锁)
public class SynchronizedDemo {
public synchronized static void methond() {
}
}
③ 修饰代码块(明确指定锁哪个对象)
public class SynchronizedDemo {
public void methond() {
synchronized(this){}
synchronized(SynchronizedDemo.class){}
}
}
我们要理解的是:当两个线程竞争同一把锁时,才会产生阻塞等待。
举个栗子?:加锁的这个过程就类似于ATM取钱,一个人进去了之后其他人只能在门口等待,而且这个人出来之后,大家都有机会进去ATM取钱。线程之间是抢占式执行的,所有线程执行的先后顺序不确定。
理解阻塞等待:针对每一把锁,操作系统内部维护了一个等待队列,如果有线程使用了这把锁,其他线程只能阻塞等待,一直等待该锁释放后,由操作系统唤醒一个新的线程再来获取该锁。 注意:下一个线程并不是立刻就能获取到锁,要靠操作系统进行调度(唤醒)。后来的这些线程还要进行锁的竞争,并不存在“先来后到”的原则。
2.保证可见性(及时刷新内存) 编译器会优化类似于i++这样的代码,为了提高效率,会将一些中间的加载和保存过程省略掉,加上synchronized关键字之后,就会禁止上面的优化流程,保证每次进行修改删除等操作时都会把数据真正同步至内存。
可见性:线程在工作内存中修改变量的值能够及时的同步至主内存中。 synchronized 能够及时的刷新内存,保证了内存的可见性。 具体工作过程如下: ①获得互斥锁 ②从主内存拷贝变量的最新副本到工作内存 ③执行代码 ④将更改后的共享变量的值刷新到主内存 ⑤释放互斥锁
3.可重入锁 Synchronized允许一个线程针对同一把锁连续加锁多次(可重入),所以不会出现死锁这样的情况。 这是因为:可重入锁的内部维护了 “线程持有者” 和 “计数器” 这两个属性。 如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取到锁, 并让计数器自增。 解锁的时候计数器递减为 0 的时候, 才真正释放锁 (才能被别的线程获取到)。
二、Volatile关键字呢?它们两者有何区别?
① volatile能够保证内存的可见性,但是不能保证原子性。synchronized既能保证内存可见性,又能保证原子性。这是它们最本质的区别! ② volatile仅能使用在 变量 级别;synchronized则可以使用在变量、方法、和类级别的。 ③ volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。 ④ volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。 在JMM中,每个线程都有自己独立的工作内存,即一组寄存器/cache高速缓冲区;在CPU和内存交互的时候,会先把内存中内容拷贝到工作内存,操作完之后在写回内存,这个过程非常容易出现数据不一致的情况,在编译器开启优化时特别严重。
三、wait()和notify()方法介绍一下?具体如何使用?
线程之间是抢占式执行的,先后顺序无法确定,依赖于操作系统的调度。在实际开发中,我们希望合理的协调多个线程之间的先后执行顺序。就有wait()、notify()/notifyAll()方法,它们都是Object类的方法。
wait()方法:让线程进入等待 wait()方法要做的事情:(wait() 要和synchronized搭配使用,否则会抛出异常)
- 让当前执行的线程进入阻塞等待状态
- 释放当前的锁,让其它线程进行操作
- 满足一定条件时会被操作系统唤醒,再次尝试获取当前锁
wait()结束等待的条件:
- 其他线程调用该对象的notify()方法
- wait等待时间超时,重新尝试获取锁
- 其他线程调用该等待线程的interrupted()方法,导致wait抛出InterruptedException异常
notify()方法:唤醒线程
- 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
- 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
- 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
notifyAll():notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程,但是这些线程还需要竞争锁。
public class testDemo {
static class WaitTask implements Runnable{
private Object locker;
public WaitTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker){
try{
System.out.println("start");
locker.wait();
System.out.println("end");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
static class NotifyTask implements Runnable{
private Object locker;
public NotifyTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker){
System.out.println("start");
locker.notify();
System.out.println("end");
}
}
}
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(new WaitTask(locker));
Thread t2 = new Thread(new NotifyTask(locker));
t1.start();
Thread.sleep(1000);
t2.start();
}
}
四、wait()和sleep()方法对比?
wait()方法用于线程之间的通信,可以指定时间,也可以无限等待;sleep()可以让线程阻塞一段时间; wait()要搭配synchronized使用,sleep()不需要; wait()是Object的方法,sleep()是Thread的静态方法; wait()唤醒可以通过notify()/interrupt()/时间到来唤醒,sleep()可以通过时间到/interrupt(); wait()主要用于协调线程之间的先后顺序,sleep单纯让线程休眠,并不涉及到多个线程的配合。
如有错误,请批评指正!
|