1.2-Threads and Concurrency in Java(下)
Threads and Concurrency in Java(下)
volatile variables and locks
Volatile
Volatile helps Java keep variable safe
Volatile is used to indicate that a variable’s value will be modified by different threads
public volatile int counter = 0;
Access to the variable is only given to one thread at a time
The load, store, read, and write actions on volatile variables are atomic
常见坑:
declare arr[] as volatile means that the reference to the array is volatile individual field accesses (e.g., arr[5]) are not thread-safe
volatile不保证原子性,只保证可见性
Unary operations (++, –) aren’t atomic NOT SAFE
举例:一百个线程每个从1加到100;
因为count++是非原子性的:可以分解为
从主内存中读取数据到工作内存
对工作内存中的数据进行++操作
将工作内存中的数据写回到主内存
在某一个时刻对某一个操作的执行,有可能被其他的线程打断。
假设此时count=100
A线程读取数据后(还未自增)此时CPU切换到B, B线程也读取数据,此时AB线程分别进行自增操作,B返回给主内存101,而A也返回主线程101,也就是说两次加一的过程对结果只改变了1
count加volatile修饰后也是如此
- If you set a variable to volatile:
– The value of this variable will never be cached thread-locally
– All reads and writes will go straight to “main memory”;
(可见性)
不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存中转来完成。
用volatile定义的变量会在程序外被改变,每次都必须从主内存中读取,而不能重复使用放在cache或寄存器中的备份。
When not to use volatile
Volatile is not necessary for fields that declared final
final 不用 volatile
Volatile is not necessary for variables that are accessed by only one thread
私有内存中,不共享
- Volatile is not suitable for complex operations
无法保证原子性
Thread safe code
Two threads should not manipulate the same variable at the same time without being protected against mistakes
线程中私有变量不会发生线程安全问题
成员变量和静态变量是否线程安全?
如果它们没有共享,则线程安全
如果它们被共享了,根据它们的状态是否能够改变,又分两种情况
如果只有读操作,则线程安全
如果有读写操作,则这段代码是临界区,需要考虑线程安全
局部变量是否线程安全?
局部变量是线程安全的
但局部变量引用的对象则未必
如果该对象没有逃离方法的作用范围,它是线程安全的
如果该对象逃离方法的作用范围,需要考虑线程安全
Critical sections
The code segments within a program that access the same data from within separate, concurrent threads are known as critical sections
In the Java language, you mark critical sections (e.g., methods) in your program with the synchronized keyword
用synchronized 标记临界区
• A synchronized section can only be accessed by a single thread at any give time
在一段时间内只能被一个线程访问(原子的)多个线程会进行排队
We use locks to control/restrict access to critical sections
locks
In Java, there are two types of locks:
– Intrinsic locks (the type locks we’ll talk about here) - every object can function as a lock that is triggered using the keyword synchronized
– Extrinsic locks (not covered)
A lock applies to a particular section of code If the code is locked, no other thread can execute it
If the code is unlocked( release the lock), any thread can take the lock and execute it
synchronized methods同步方法
If you have two methods with the synchronized keyword(同步方法)
– only one method of the two will be executed at the same time
一次只能运行一个方法(printer1运行完,printer2才会运行)[不可能有两个线程同时运行这两个方法,因为这两个方法具有同一个锁]
– because the same lock is used for all methods in an object(都为this)
public synchronized void printer1() {}
上述代码为同步方法的写法,其锁为this所指对象(不用手动添加)
class Printer {
//非静态同步方法锁对象是this指代的对象
public synchronized void printer1() { //同步方法
System.out.print("1");
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.print("5");
System.out.println();
}
public void printer2() {
synchronized (this) {
System.out.print("a");
System.out.print("b");
System.out.print("c");
System.out.print("d");
System.out.print("e");
System.out.println();
}
}
}
synchronized statements同步代码块-Explicit use
非静态方法(锁为this)
public class Example {
private int value = 0;
public int getNextValue() {
synchronized (this)
{ return value++; }
}}
静态方法(锁为Class object类字节码对象 类名.class)
– synchronized (getClass()) {…}
public static void printer2() {
synchronized (Printer.class) {
System.out.print("a");
System.out.print("b");
System.out.print("c");
System.out.print("d");
System.out.print("e");
System.out.println();
}
}
}
synchronized statements must specify the object that provides the intrinsic lock
For the majority of your Java programming purposes, it’s best to use synchronized only at the method level
Scope of a lock
The time between when the lock is taken and when the lock is released
determined by segments of code
locks apply to objects not methods
锁作用于对象而非方法
创建一个锁对象(任意一个对象可以当作锁):
只有两段代码使用相同锁才能同步
线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。所以我们用同步机制来解决这些问题,加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
synchronized method call another non synchronized/synchronized(same object) method
• Each lock acts as a counter
– if counter is not 0 on entry to a synchronized method or block because another thread holds the lock, the current thread is blocked until the count is 0
– on entry the counter is incremented
– on return or exit by an exception the counter is decremented
- Any method or code block marked as synchronized is executed entirely
– Unless explicitly suspended by a wait (see later)
– Other threads are blocked until the synchronized block can complete
If a method is not marked as synchronized then it can be executed immediately
– Even when another method, even a synchronized method, of the object is executing
The synchronized qualifier is not automatically inherited
Synchronized vs volatile
Only a primitive variable may be declared volatile
修饰引用类型变量,并不会让其引用的元素都保证可见
Access to a volatile variable never has the potential to block
多线程访问volatile不会发生阻塞,而sychronized会出现阻塞。
A synchronized method can protect more complex code
MONITORS
A lock that supports Cooperation through the wait() & notify() methods is called a Monitor
Using synchronized creates the lock to protect the critical section of the code
Calling wait() on an object pauses a thread and puts it in a wait set (the set of threads waiting for the lock to become free)提前结束线程
Calling notify() on that object re-awakens a thread from the wait set
mechanism:
• In the center, a large rectangle contains a single thread, the lock owner(Notice only one active thread)
• On the left, a small rectangle contains the entry set
• Active threads are shown as dark grey circles
• Suspended (sleeping) threads are shown as light grey circles
线程进入同步方法中:
Threads entering a lock region are placed into an entry set for the associated monitor
为了继续执行临界区代码,线程必须获取 Monitor 锁。如果获取锁成功,将成为该监视者对象的拥有者。任一时刻内,监视者对象只属于一个活动线程(The Owner)
If no other thread is waiting in the entry set and no other thread currently owns the lock, the thread acquires the lock and continues executing the lock region
拥有监视者对象的线程可以调用 wait() 进入等待集合(Wait Set),同时释放监视锁,进入等待状态。
其他线程调用 notify() / notifyAll() 唤醒等待集合中的线程,这些等待的线程需要重新获取监视锁后才能执行 wait() 之后的代码。
同步方法执行完毕了,线程退出临界区,并释放监视锁。
When the thread finishes executing the lock region, it exits (and releases) the lock
Monitor Vs lock ?
Using the synchronized keyword ensures mutual exclusion
A monitor also allows cooperation between threads
– Allows threads to pause their execution and notify other threads of events
wait(); notify(); notifyAll()
这三个方法都是锁对象调用的(都是object类中方法)
wait(): If a thread executing a synchronized method determines that it cannot proceed, then it may put itself into a waiting state by calling wait. This releases the thread’s lock on the shared object and allows other threads to obtain the lock.
当某个线程获取到锁后,可以调用对象锁的wait方法,进入等待状态。同时,wait()也会让当前线程释放它所持有的锁。
A call to wait may lead to an InterruptedException, which must either be caught or declared to be thrown by the containing (synchronized) method.
notify(): When a synchronized method reaches completion, a call may be made to notify, which will ‘wake up’ a thread that is in the waiting state.
其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”) notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
Since there is no way of specifying which thread is to be woken, this is only really appropriate if there is only one waiting thread.
notify被唤醒的线程是随机的,所以通常是没办法指定是谁被唤醒。
If all threads waiting for a lock on a given object are to be woken, then we use notifyAll.
However, there is still no way of determining which thread gets control of the object. The JVM will make this decision.
Methods wait, notify and notifyAll may only be called when the current thread has a lock on the object (i.e., from within a synchronized method or from within a method that has been called by a synchronized method).
If any of these methods is called from elsewhere, an
IllegalMonitorStateException
is thrown.
wait set
A thread that currently owns the lock can suspend itself inside the lock by executing a wait() command
When a thread executes a wait(), it releases the lock and enters a wait set
The thread will stay suspended in the wait set until another thread executes a notify() command inside the lock
一个线程释放锁:要不被wait要不自然释放(当方法wait()被执行后,锁自动被释放,但执行完notify()方法后,锁不会自动释放。必须执行完notify()方法所在的synchronized代码块后才释放。)
If the (former) lock owner did not execute a notify before it released the lock then only the threads in the entry set will compete to acquire the lock
如果没有调用 notify,则entry set中的线程自己竞争。
If the former owner did execute a notify, then the entry set threads will have to compete with one or more threads from the wait set
如果上一个锁的拥有者在释放之前调用notify() 则唤醒wait set和entry set 中的线程竞争
wait and notify should be placed within synchronized code to ensure that the current code owns the lock (只能在synchronized代码块中使用)
如果不使用monitor,输出的sum可能为1-10任何数字
如果使用monitor:1、wait(100)是为了如果sum线程先被调度,主线程在其结束后开始,会陷入一直等待的状态
为什么不用join()代替外wait+notify?
join必须等另一个线程完全结束才能切换线程,而notify等待某一个同步代码块结束就可以切换线程
wait condition
while( ! the condition which should be true) { wait(); }
Code keeps waiting until condition is true
Deadlock
Two or more threads waiting for two or more locks to be freed, and the circumstances in the program is such that the locks will never be freed
Java provides no mechanisms to support deadlock prevention
deadlock发生的情况:
示例参见case12: deadlock
• Prevention
– Design code so deadlock is impossible
• Avoidance
– Steer around deadlock with smart scheduling
• Detection and Recovery
– Check for deadlock periodically
– Recover by killing threads and restarting
Deadlock prevention
Avoid mutual exclusion 不用 synchronized(不合理)
Allow pre-emption(allow a thread to interrupt another to take its lock)->dangerous
Don’t allow a thread to hold multiple locks
– Or force it to get all locks at the same time
Thread priority
线程优先权只是说提升线程被调度的可能性,并不意味一定被调度
Method table
Name | try catch ? | clear interrupt flag? | static? | 只能给当前线程用? |
---|---|---|---|---|
sleep | y | y | y | y |
yield | n | n | y | y |
join | y | y | n | n |
wait | y | y | y | y |
notify | n | n | n | y |
notifyAll | n | n | n | y |
interrupt | n | n | n | n |
interrupted | n | y | y | y |
isInterrupted | n | n | n | n |
isAlive | n | n | n | n |