You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 335 Next »

Guidelines

[CON00-J. Ensure visibility when accessing shared primitive variables]

[CON02-J. Ensure that compound operations on shared variables are atomic]

[CON08-J. Do not synchronize on objects that may be reused]

[CON03-J. Do not use background threads during class initialization]

[CON07-J. Use private final lock objects to synchronize classes that may interact with untrusted code]

[CON05-J. Do not invoke Thread.run()]

[CON11-J. Do not synchronize on a collection view if the backing collection is accessible]

[CON03-J. Do not assume that a group of calls to independently atomic methods is atomic]

[CON01-J. Ensure visibility of shared references to immutable objects]

[CON10-J. Do not override thread-safe methods with methods that are not thread-safe]

[CON06-J. Do not assume that declaring an object reference volatile guarantees visibility of its members]

[CON12-J. Avoid deadlock by requesting and releasing locks in the same order]

[CON13-J. Do not use Thread.stop() to terminate threads]

[CON14-J. Do not let the (this) reference escape during object construction]

[CON15-J. Ensure actively held locks are released on exceptional conditions]

[CON16-J. Do not assume that the sleep(), yield() or getState() methods provide synchronization semantics]

[CON17-J. Do not invoke ThreadGroup methods]

[CON18-J. Always invoke wait() and await() methods inside a loop]

[CON19-J. Notify all waiting threads instead of a single thread]

[CON20-J. Do not perform operations that may block while holding a lock]

[CON21-J. Use thread pools to enable graceful degradation of service during traffic bursts]

[CON22-J. Do not use incorrect forms of the double-checked locking idiom]

[CON24-J. Ensure that threads and tasks performing blocking operations can be terminated]

[CON05-J. Ensure atomicity when reading and writing 64-bit values]

[CON26-J. Do not publish partially initialized objects]

[CON27-J. Ensure ThreadLocal variables are reinitialized when using thread pools]

[CON29-J. Do not execute interdependent tasks in a bounded thread pool]

[CON04-J. Ensure that calls to chained methods are atomic]

[CON31-J. Avoid client-side locking when using classes that do not commit to their locking strategy]

[CON12-J. Synchronize access to static fields that may be modified by untrusted code]

[CON33-J. Document thread-safety and use annotations where applicable]

[CON13-J. Do not use an instance lock to protect shared static data]

[CON10-J. Do not synchronize on the intrinsic locks of high-level concurrency objects]

[CON36-J. Ensure that tasks submitted to a thread pool are interruptible]

[CON37-J. Ensure that tasks executing in a thread pool do not fail silently]

[CON09-J. Do not synchronize on the class object returned by getClass()]

[Order of concurrency guidelines]

Introduction

Memory that can be shared between threads is called shared memory or heap memory. The term variable as used in this section refers to both fields and array elements [[JLS 05]]. Variables that are shared between threads are referred to as shared variables. All instance fields, static fields, and array elements are shared variables and are stored in heap memory. Local variables, formal method parameters, or exception handler parameters are never shared between threads and are not affected by the [memory model].

In a modern shared-memory multiprocessor architecture, each processor has one or more levels of cache that are periodically reconciled with main memory as shown in the following figure:

The visibility of writes to shared variables can be problematic because the value of a shared variable may be cached and not written to main memory immediately. Consequently, another thread may read a stale value of the variable.

A further concern is that concurrent executions of code are typically interleaved and statements may be reordered by the compiler or runtime system to optimize performance. This results in execution orders that are not immediately obvious from an examination of the source code. Failure to account for possible reorderings is a common source of [data races].

Consider the following example in which a and b are (shared) global variables or instance fields, but r1 and r2 are local variables that are not accessible to other threads.

Initially, let a = 0 and b = 0.

Thread 1

Thread 2

a = 10;

b = 20;

r1 = b;

r2 = a;

Because, in Thread 1, the two assignments a = 10; and r1 = b; are unrelated, the compiler or runtime system is free to reorder them. Similarly in Thread 2, the statements may be freely reordered. Although it may seem counter-intuitive, the Java memory model allows a read to see the value of a write that occurs later in the execution order.

A possible execution order showing actual assignments is:

Execution Order (Time)

Thread#

Assignment

Assigned Value

Notes

1.

t1

a = 10;

10

 

2.

t2

b = 20;

20

 

3.

t1

r1 = b;

0

Reads initial value of b, that is 0

4.

t2

r2 = a;

0

Reads initial value of a, that is 0

In this ordering, r1 and r2 read the original values of the variables b and a respectively, even though they are expected to see the updated values, 20 and 10. Another possible execution order showing actual assignments is:

Execution Order (Time)

Thread#

Statement

Assigned Value

Notes

1.

t1

r1 = b;

20

Reads later value (in step 4.) of write, that is 20

2.

t2

r2 = a;

10

Reads later value (in step 3.) of write, that is 10

3.

t1

a = 10;

10

 

4.

t2

b = 20;

20

 

In this ordering, r1 and r2 read the values of a and b written from step 3 and 4, even before the statements corresponding to these steps have executed.

Restricting the set of possible reorderings makes it easier to reason about the correctness of the code.

Even if statements execute in the order of their appearance in a thread, caching can prevent the latest values from being reflected in the main memory.

The Java Language Specification defines the Java Memory Model (JMM) which provides certain guarantees to the Java programmer. The JMM is specified in terms of actions, which include variable reads and writes, monitor locks and unlocks, and thread starts and joins. The JMM defines a partial ordering called [happens-before] on all actions within the program. To guarantee that a thread executing action B can see the results of action A, for example, there must be a happens-before relationship defined such that A happens-before B.

According to section 17.4.5 "Happens-before Order" of the Java Language Specification [[JLS 05]]:

  1. An unlock on a monitor happens-before every subsequent lock on that monitor.
  2. A write to a volatile field happens-before every subsequent read of that field.
  3. A call to start() on a thread happens-before any actions in the started thread.
  4. All actions in a thread happen-before any other thread successfully returns from a join() on that thread.
  5. The default initialization of any object happens-before any other actions (other than default-writes) of a program.
  6. A thread calling interrupt on another thread happens-before the interrupted thread detects the interrupt
  7. The end of a constructor for an object happens-before the start of the finalizer for that object

If a happens-before relationship does not exist between two operations, the Java Virtual Machine (JVM) is free to reorder them. A [data race] occurs when a variable is written to by at least one thread and read by at least another thread, and the reads and writes are not ordered by a happens-before relationship. A correctly synchronized program is one with no data races. The Java Memory Model (JMM) guarantees sequential consistency for correctly synchronized programs. Sequential consistency means that the result of any execution is the same as if the reads and writes by all threads on shared data were executed in some sequential order and the operations of each individual thread appear in this sequence in the order specified by its program

Unknown macro: {mc}

shall we say program order in brackets?

[[Tanenbaum 03]]. In other words:

  1. Take the read and write operations performed by each thread and put them in the order the thread executes them (thread order)
  2. Interleave the operations in some way allowed by the happens-before relationships to form an execution order
  3. Read operations must return most recently written data in the total [program order] for the execution to be sequentially consistent
  4. Implies all threads see the same total ordering of reads and writes of shared variables

The actual execution order of instructions and memory accesses can vary as long as the actions of the thread appear to that thread as if [program order] were followed, and provided all values read are allowed for by the memory model. This allows the programmer to understand the semantics of the programs they write, and allows compiler writers and virtual machine implementors to perform various optimizations [[JPL 06]].

There are several concurrency primitives that can help a programmer reason about the semantics of multithreaded programs:

The volatile Keyword

Declaring shared variables as volatile ensures visibility and limits reordering of accesses. Volatile accesses do not guarantee the atomicity of composite operations such as incrementing a variable. Consequently, volatile is not applicable in cases where the atomicity of composite operations must be guaranteed (see [CON02-J. Ensure that compound operations on shared variables are atomic] for more information.)

Declaring variables as volatile establishes a happens-before relationship such that a write to a volatile variable is always seen by threads performing subsequent reads of the same variable. Statements that occur before the write to the volatile field also happen-before any reads of the volatile field.

Consider two threads that are executing some statements:

Thread 1 and Thread 2 have a happens-before relationship such that Thread 2 does not start before Thread 1 finishes.

Unknown macro: {mc}

Seems to be wrong ~DM => This is established by the semantics of volatile accesses.

In this example, Statement 3 writes to a volatile variable, and statement 4 (in Thread 2) reads the same volatile variable. The read sees the most recent write (to the same variable v) from statement 3.

Volatile read and write operations cannot be reordered with respect to each other and also with respect to non-volatile variables accesses. When Thread 2 reads the volatile variable, it sees the results of all the writes occurring before the write to the volatile variable in Thread 1. Because of the relatively strong guarantees of volatile, the performance overhead of volatile is almost the same as that of synchronization.

Unknown macro: {mc}

last sentence needs citation; appears to be slightly risky ~DM

In the previous example, there is no guarantee that statements 1 and 2 will be executed in the order in which they appear in the program. They may be freely reordered by the compiler because there is no happens-before relationship between these two statements.

The possible reorderings between volatile and non-volatile variables are summarized in the matrix shown below. Load and store operations are synonymous to read and write operations, respectively. [[Lea 08]]

Unknown macro: {mc}

Might as well rename in the table ~DM

Synchronization

A correctly synchronized program is one whose sequentially consistent executions do not have any data races. The example shown below uses a non-volatile variable x and a volatile variable y. It is not correctly synchronized.

Thread 1

Thread 2

x = 1

r1 = y

y = 2

r2 = x

There are two sequentially consistent execution orders of this example:

Step (Time)

Thread#

Statement

Comment

1.

t1

x = 1

Write to non-volatile variable

2.

t1

y = 2

Write to volatile variable

3.

t2

r1 = y

Read of volatile variable

4.

t2

r2 = x

Read of non-volatile variable

and,

Step (Time)

Thread#

Statement

Comment

1.

t2

r1 = y

Read of volatile variable

2.

t2

r2 = x

Read of non-volatile variable

3.

t1

x = 1

Write to non-volatile variable

4.

t1

y = 2

Write to volatile variable

In the first case, there is a happen-before relationship between actions such that steps 1 and 2 always occur before steps 3 and 4. However, in the second case, there is no happens-before relationship between any of the steps. Consequently, because there is a sequentially consistent execution that has no happens-before relationship, this example contains data races.

Correct synchronization entails deciding on one sequentially consistent execution order and using synchronized methods or blocks to perform all the actions sequentially. For example, the code shown below ensures that there is only one sequentially consistent execution order that performs all the actions of thread 1 before thread 2.

public synchronized void doSomething() {
  // Perform Thread 1 actions
  x = 1;
  y = 2; 
  // Perform Thread 2 actions
  r1 = y;
  r2 = x;
}

The doSomething() method is said to be atomic. When using synchronization, there is no need to declare the variable y as volatile.

Risk Assessment Summary

Guideline

Severity

Likelihood

Remediation Cost

Priority

Level

CON00-J

medium

probable

medium

P8

L2

CON01-J

medium

probable

medium

P8

L2

CON02-J

low

likely

high

P3

L3

CON03-J

low

probable

medium

P4

L3

CON04-J

low

probable

medium

P4

L3

CON05-J

low

probable

medium

P4

L3

CON06-J

low

probable

medium

P4

L3

CON07-J

low

likely

high

P3

L3

CON08-J

low

likely

high

P3

L3

CON09-J

low

probable

medium

P4

L3

CON10-J

low

probable

medium

P4

L3

CON11-J

low

likely

high

P3

L3

CON12-J

low

probable

medium

P4

L3

CON14-J

low

probable

medium

P4

L3

CON15-J

low

likely

low

P9

L2

CON16-J

low

probable

medium

P4

L3

CON17-J

low

probable

low

P6

L2

CON18-J

low

unlikely

medium

P2

L3

CON19-J

low

unlikely

medium

P2

L3

CON20-J

low

probable

high

P2

L3

CON21-J

low

probable

high

P2

L3

CON22-J

low

probable

medium

P4

L3

CON23-J

low

unlikely

medium

P2

L3

CON24-J

low

unlikely

medium

P2

L3

CON25-J

low

unlikely

medium

P2

L3


[!The CERT Sun Microsystems Secure Coding Standard for Java^button_arrow_left.png!]      [!The CERT Sun Microsystems Secure Coding Standard for Java^button_arrow_up.png!]      [!The CERT Sun Microsystems Secure Coding Standard for Java^button_arrow_right.png!]

  • No labels