写在前面
建议学习Java并发编程前,先学习一下jvm,这样可以理解的更清楚些。
Java体现
两个线程对初始值为0的静态变量一个做自增,一个做自减,各做5000次,结果是0么?
static int counter = 0;
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(()->{
for(int i=0;i<5000;i++){
counter++;
}
},"t1");
Thread t2 = new Thread(()->{
for(int i=0;i<5000;i++){
counter--;
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}",counter);
}
问题分析
例如对于i++而言(i为静态变量),实际会产生如下的JVM字节码指令:
getstatic i //获取静态变量i的值
iconst_1 //准备常量1
iadd //自增
putstatic i //将修改后的值存入静态变量i
而对于i–也是类似:
getstatic i //获取静态变量i的值
iconst_1 //准备常量1
isub //自减
putstatic i //将修改后的值存入静态变量i


临界区Critical Section
例如,下面代码中的临界区
static int counter = 0;
static void increment()
//临界区
{
counter++;
}
static void decrement()
//临界区
{
counter--;
}
竞态条件Race Condition
多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。
为了避免临界区的竞态条件发生,有多种手段可以达到目的。
本节先讲解使用synchronized来解决,其余方法将在后续讲到。加了synchronized的Java代码如下:
static int counter = 0;
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(()->{
for(int i=0;i<5000;i++){
synchronized(lock){
counter++;
}
}
},"t1");
Thread t2 = new Thread(()->{
for(int i=0;i<5000;i++){
synchronized(lock){
counter--;
}
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
log.debug("{}",counter);
}
从下图中可以看出,当线程2完成isub减法后,虽然当前时间片到了,线程2暂时离开,但是锁还在,所以在线程1尝试获取锁,获取不到时,线程1会被阻塞(后面会介绍自旋的概念,线程并不是一开始就会被阻塞);当再次到线程2的时间片时,线程2执行putstatic操作,写入-1,然后把当前锁释放,并唤醒阻塞的线程。

在讲解了synchronized是如何解决问题的,下面将讲解synchronized的语法。
synchronized可以修饰代码块、方法(静态方法、普通方法)。
class Test{
public synchronized static void test(){
}
}
//等价于
class Test{
public static void test(){
synchronized(Test.class){
}
}
}
class Test{
public synchronized void test(){
}
}
//等价于
class Test{
public void test(){
synchronized(this){
}
}
}
下面将举例来具体理解synchronized到底锁的是什么!
情况1
class Number{
public synchronized void a(){ //锁的是 对象实例
lod.debug("1");
}
public synchronized void b(){ //锁的是 对象实例
lod.debug("2");
}
}
public static void main(String[] args){
Number n1 = new Number();//同一个对象实例,有互斥
new Thread(()->{n1.a();}).start();
new Thread(()->{n1.b();}).start();
}
//输出分析
//12 或 21
情况2
class Number{
public synchronized void a(){ //锁的是 对象实例
sleep(1); //注意sleep不会释放锁
lod.debug("1");
}
public synchronized void b(){ //锁的是 对象实例
lod.debug("2");
}
}
public static void main(String[] args){
Number n1 = new Number();//不是同一个对象实例,不存在互斥
Number n2 = new Number();
new Thread(()->{n1.a();}).start();
new Thread(()->{n2.b();}).start();
}
//输出分析
//2 1s后1
情况3
class Number{
public static synchronized void a(){ //锁的是 类对象
sleep(1); //注意sleep不会释放锁
lod.debug("1");
}
public synchronized void b(){ //锁的是 对象实例
lod.debug("2");
}
}
public static void main(String[] args){
Number n1 = new Number();//不存在互斥
new Thread(()->{n1.a();}).start();
new Thread(()->{n1.b();}).start();
}
//输出分析
//2 1s后1
情况4
class Number{
public static synchronized void a(){ //锁的是 类对象
sleep(1); //注意sleep不会释放锁
lod.debug("1");
}
public static synchronized void b(){ //锁的是 类对象
lod.debug("2");
}
}
public static void main(String[] args){
Number n1 = new Number();//有互斥
new Thread(()->{n1.a();}).start();
new Thread(()->{n1.b();}).start();
}
//输出分析
//2 1s后1 或 1s后12
情况5
class Number{
public static synchronized void a(){ //锁的是 类对象
sleep(1); //注意sleep不会释放锁
lod.debug("1");
}
public synchronized void b(){ //锁的是 对象实例
lod.debug("2");
}
}
public static void main(String[] args){
Number n1 = new Number();//不存在互斥
Number n2 = new Number();
new Thread(()->{n1.a();}).start();
new Thread(()->{n2.b();}).start();
}
//输出分析
//2 1s后1
情况6
class Number{
public static synchronized void a(){ //锁的是 类对象
sleep(1); //注意sleep不会释放锁
lod.debug("1");
}
public static synchronized void b(){ //锁的是 类对象
lod.debug("2");
}
}
public static void main(String[] args){
Number n1 = new Number();//有互斥
Number n2 = new Number();
new Thread(()->{n1.a();}).start();
new Thread(()->{n2.b();}).start();
}
//输出分析
//2 1s后1 或 1s后12
成员变量和静态变量是否线程安全?
局部变量是否线程安全?
下面对文字信息进行举例讲解。
局部变量线程安全分析
局部变量为基本数据类型
public static void test1(){
int i = 10;
i++;
}
每个线程调用test1()方法时局部变量i,会在每个线程的栈帧内存中被创建多份,因此不存在共享。
public static void test1();
descriptor:()V
flags: ACC_PULIC,ACC_STATIC
Code:
stack=1,locals=1,args_size=0
0:bipush 10
2:istore_0
3:iinc 0,1
6:return
LineNumberTable
line 10:0
line 11:3
line 12:6
LocalVariableTable:
Start Length Slot Name Signature
3 4 0 i I
如图

局部变量为引用类型
先看一个成员变量的例子。
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args){
ThreadUnsafe test = new ThreadUnsafe();
for(int i=0;i<THREAD_NUMBER;i++){
new Thread(()->{
test.method1(LOOP_NUMBER);
},"Thread"+(i+1)).start();
}
}
class ThreadUnsafe{
ArrayList<String> list = new ArrayList<>();
public void method1(int loopNumber){
for(int i=0;i<loopNumber;i++){
method2();
method3();
}
}
private void method2(){list.add("1")};
private void method3(){list.remove(0)};
}
因为add不保证原子性,线程1在添加的时候有可能线程2也在添加,然后就只添加了一个元素,但是接下来却碰到了两个移除0位置的操作,就报错了:

无论哪个线程中的method2引用的都是同一个对象中的list成员变量。method3和method2分析相同。

将list修改为局部变量
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args){
ThreadSafe test = new ThreadSafe();
for(int i=0;i<THREAD_NUMBER;i++){
new Thread(()->{
test.method1(LOOP_NUMBER);
},"Thread"+(i+1)).start();
}
}
class ThreadSafe{
public final void method1(int loopNumber){
ArrayList<String> list = new ArrayList<>();
for(int i=0;i<loopNumber;i++){
method2(list);
method3(list);
}
}
private void method2(ArrayList<String> list){list.add("1")};
private void method3(ArrayList<String> list){list.remove(0)};
}
为什么改成局部变量就没有问题了呢?
分析

常见线程安全类
这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。也可以理解为
Hashtable table = new Hashtable();
new Thread(()->{
table.put("key","value1");
}).start();
new Thread(()->{
table.put("key","value2");
}).start();
** 线程安全类方法的组合**
分析下面代码是否线程安全?
Hashtable table = new Hashtable();
//线程1,线程2
if(table.get("key") == null){
table.put("key",value);
}
