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

image-20220924214404893

常见坑:

  1. declare arr[] as volatile means that the reference to the array is volatile individual field accesses (e.g., arr[5]) are not thread-safe

  2. volatile不保证原子性,只保证可见性

  • Unary operations (++, –) aren’t atomic NOT SAFE

    举例:一百个线程每个从1加到100;

image-20220925104005004

因为count++是非原子性的:可以分解为

  • 从主内存中读取数据到工作内存

  • 对工作内存中的数据进行++操作

  • 将工作内存中的数据写回到主内存

    在某一个时刻对某一个操作的执行,有可能被其他的线程打断。

image-20220925104223829

假设此时count=100

A线程读取数据后(还未自增)此时CPU切换到B, B线程也读取数据,此时AB线程分别进行自增操作,B返回给主内存101,而A也返回主线程101,也就是说两次加一的过程对结果只改变了1

count加volatile修饰后也是如此

  1. 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

image-20220925145509859

无法保证原子性

Thread safe code

Two threads should not manipulate the same variable at the same time without being protected against mistakes

image-20220925150144863

线程中私有变量不会发生线程安全问题

成员变量和静态变量是否线程安全?

如果它们没有共享,则线程安全

如果它们被共享了,根据它们的状态是否能够改变,又分两种情况

如果只有读操作,则线程安全

如果有读写操作,则这段代码是临界区,需要考虑线程安全

局部变量是否线程安全?

局部变量是线程安全的

但局部变量引用的对象则未必

如果该对象没有逃离方法的作用范围,它是线程安全的

如果该对象逃离方法的作用范围,需要考虑线程安全

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

image-20220925152455066

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

image-20220925160934441

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

锁作用于对象而非方法

image-20220925161605960

创建一个锁对象(任意一个对象可以当作锁):

只有两段代码使用相同锁才能同步

线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。所以我们用同步机制来解决这些问题,加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

image-20220925161709603

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:

image-20221010083459376

• In the center, a large rectangle contains a single thread, the lock ownerNotice 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

  1. 线程进入同步方法中:

    Threads entering a lock region are placed into an entry set for the associated monitor

  2. 为了继续执行临界区代码,线程必须获取 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

  3. 拥有监视者对象的线程可以调用 wait() 进入等待集合(Wait Set),同时释放监视锁,进入等待状态。

  4. 其他线程调用 notify() / notifyAll() 唤醒等待集合中的线程,这些等待的线程需要重新获取监视锁后才能执行 wait() 之后的代码。

  5. 同步方法执行完毕了,线程退出临界区,并释放监视锁。

    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代码块中使用)

    image-20221010154532953

    如果不使用monitor,输出的sum可能为1-10任何数字

    如果使用monitor:1、wait(100)是为了如果sum线程先被调度,主线程在其结束后开始,会陷入一直等待的状态

    image-20221010154759397

    为什么不用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

image-20221011130317154

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
  1. Avoid mutual exclusion 不用 synchronized(不合理)

  2. Allow pre-emption(allow a thread to interrupt another to take its lock)->dangerous

  3. 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