1. volatile和synchronized关键字
volatile
可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要
从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问
的可见性。
synchronized
可以修饰方法或者以同步块的形式来进行使用,它保证了线程对变量访问的可见性
和排他性。
我们对如下代码进行反编译
public class Synchronized {
public static void main(String[] args) {
// 对Synchronized Class对象进行加锁
synchronized (Synchronized.class) {
}
// 静态同步方法,对Synchronized Class对象进行加锁
m();
}
public static synchronized void m() {
}
}
反编译结果:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: ldc #2 // class chapter4/Synchronized
2: dup
3: astore_1
4: monitorenter
5: aload_1
6: monitorexit
7: goto 15
10: astore_2
11: aload_1
12: monitorexit
13: aload_2
14: athrow
15: invokestatic #3 // Method m:()V
18: return
Exception table:
from to target type
5 7 10 any
10 13 10 any
LineNumberTable:
line 6: 0
line 7: 5
line 9: 15
line 10: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 10
locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
public static synchronized void m();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=0, locals=0, args_size=0
0: return
LineNumberTable:
line 12: 0
}
可以看出:对于同步块的实现使用了monitorenter和monitorexit指令,而同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。
当获取锁时,获取Monitor,若无法获得Monitor,则进入同步队列,设置为BLOCK
2. 等待/通知机制
一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个
过程开始于一个线程,而最终执行又是另一个线程。(生产者-消费者)
经典范式:
-
等待方:
synchronized(对象) { while(condition) { //对象.wait(); } //对应的处理逻辑 }
-
通知方:
synchronized(对象) { //改变条件 //对象.notifyAll(); }
需要注意的点:
- 使用
wait()
、notify()
和notifyAll()
时需要先对调用对象加锁。- 调用
wait()
方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。notify()
或notifyAll()
方法调用后,需要调用notify()或notifyAll()
的线程释放锁之后,等待线程才有机会从wait()返回。notify()
方法将等待队列中的一个等待线程从等待队列中移到同步队列中,线程状态由WAITING变为BLOCKED- 从**wait()**方法返回的前提是获得了调用对象的锁
下面用图呈现一下整个过程:
3. Thread.join()
**Join()**的含义可以参考 happen-before的规则
这里进行源码的分析:
public final void join() throws InterruptedException {
// 调用join() 直接设置等待时长为0
join(0);
}
// 对当前对象加同步锁
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
// 异常判断
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
// 如果当前线程存活,则wait
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
// 超时直接break
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
4. ThreadLocal 的使用
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。
通过set(T)
方法来设置一个值,在当前线程下再通过get()
方法获取到原先设置的值。
这里通过一个栗子进行理解:
这个栗子反应的是:将当前访问用户信息放入 ThreadLocal中,在执行业务逻辑时调用,业务结束时,进行清空!
public class ThreadLocalTest{
public static void main(String[] args) {
ThreadLocalUtil localTest = new ThreadLocalUtil();
localTest.run();
localTest.clear();
}
}
class ThreadLocalUtil{
public static ThreadLocal<User> threadlocal = new ThreadLocal<>();
static {
// init()
threadlocal.set(new User());
System.out.println(threadlocal.get().toString());
}
public void run() {
// service
User user = threadlocal.get();
System.out.println(user);
}
public void clear(){
// clear
threadlocal.remove();
System.out.println(threadlocal.get());
}
}
class User{
String username;
int age;
public User(){
username = "xiaoming";
age = 19;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
}
/*
User{username='xiaoming', age=19}
User{username='xiaoming', age=19}
null
*/