Java中线程并发问题(Concurrent),synchronized(同步关键字),线程死锁 的介绍

线程的并发

并发问题:

static int i = 0;  //在方法区

public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new Thread() {
        @Override
        public void run() {
            for (int j = 0; j < 5000; j++) {
                i++;
            }
        }
    };
    Thread thread2 = new Thread() {
        @Override
        public void run() {
            for (int j = 0; j < 5000; j++) {
                i--;
            }
        }
    };
    //两个线程都启动
    thread1.start();
    thread2.start();
    //等两个线程都结束了
    thread1.join();
    thread2.join();
    //两个线程分别给i++5000次和--了5000次,此时的i应该还是0
    System.out.println("i:"+i);//i:-719
    //从控制台的输出可以看出i最后的值并不0,而且运行的值是不确定的
}

出现这种问题的原因:
i++ 中的 ++ 实际上有4行代码

getstatic 
iconst_1
iadd
putstatic

所以在并行执行时就会出问题,具体如下图
Java中线程并发问题(Concurrent),synchronized(同步关键字),线程死锁 的介绍

用synchronized 解决并发问题:

static  int i = 0;
static Object obj1 = new Object();// 房间,能容纳一个人
static Object obj2 = new Object();
public static void main(String[] args) throws InterruptedException {

    Thread thread1 = new Thread() {// 甲
        @Override
        public void run() {
            for (int j = 0; j < 5000; j++) {
                synchronized (obj1) {// 甲会锁住这个房间
                    i++;
                } // 甲从房间出来解开了锁
            }
        }
    };
    Thread thread2= new Thread() {// 乙
        @Override
        public void run() {
            for (int j = 0; j < 5000; j++) {
                synchronized (obj1) {// 乙   在门外等待,等甲出来了才能进
                    i--;
                }
            }
        }
    };

    thread1.start();
    thread2.start();

    thread1.join();
    thread2.join();

    System.out.println("i:"+i);   // 0
    }

每个对象都有一个自己的monitor(监视器),当一个线程调用synchronized(对象),就相当于进入了这个对象的监视器。要检查有没有owner,如果没有,此线程成为owner; 但如果已经有owner了,这个线程在entryset的区域等待owner的位置空出来。

成为owner可以理解为获得了对象的锁

在竞争的时候,是非公平的

synchronized必须是进入同一个对象的monitor 才有上述的效果
Java中线程并发问题(Concurrent),synchronized(同步关键字),线程死锁 的介绍


Volatile易变的

可以用来修饰成员变量和静态成员变量,他可以防止线程从自己的高速缓存中查找变量的值,必须到主存中获取它的值。
它保证的是变量在多个线程之间的可见性, 不能保证原子性

static boolean run = true;
public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread() {
        @Override
        public void run() {
            while (run){
                //各种操作
            }
        }
    };
    thread.start();
    Thread.sleep(1000);
    run = false;
}

一个线程对run变量的修改对于另一个线程不可见,导致了另一个线程无法停止
我门加入volatile修饰.

 volatile static boolean run = true;
 public static void main(String[] args) throws InterruptedException {
     Thread thread = new Thread() {
         @Override
         public void run() {
             while (run){
                 //各种操作
             }
         }
     };
     thread.start();
     Thread.sleep(1000);
     run = false;
 }

此时线程不去高速缓存查找变量,而直接去主存中获取。

synchronized 语句块既可以保证代码块的原子性,也可以保证代码块内变量的可见性。但缺点是synchronized是属于重量级操作,性能会受到影响。


synchronized的其他写法

public synchronized void test() {

}
等价于
public void test() {
	synchronized(this) {
	
	}
}
class Test{
	public synchronized static void test() {

	}
}
等价于
public static void test() {
	synchronized(Test.class) {
		
	}
}

线程死锁

a 线程 获得 A 对象 锁
接下来获取B对象的锁
b 线程获得 B对象 锁
接下来获取A对象的锁

Object A = new Object();
Object B = new Object();


Thread a = new Thread(()->{
    synchronized (A) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (B) {
            System.out.println("操作...");
        }
    }
});

Thread b = new Thread(()->{
    synchronized (B) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (A) {
            System.out.println("操作...");
        }
    }
});
a.start();
b.start();

检测死锁可以使用 jconsole工具

wait() notify() notifyAll()

都属于Object对象的方法

wait() 等待
notify() 唤醒
它们都是线程之间进行协作的手段

obj.wait(); 让object监视器的线程等待
obj.notify(); 让object上正在等待的线程中挑一个唤醒
obj.notifyAll(); 让object上正在等待的线程全部唤醒

必须获得此对象的锁,才能调用这几个方法

Object obj = new Object();

new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-0线程执行....");
        try {
            obj.wait(); // 让线程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-0其它代码....");
    }
}).start();

new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-1线程执行....");
        try {
            obj.wait(); // 让线程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-1其它代码....");
    }
}).start();

try {
    Thread.sleep(2000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("唤醒obj上其它线程");
synchronized (obj){
//            obj.notify();
    obj.notifyAll();
}

}

wait() 方法实际是会释放对象的锁,进入WaitSet等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify为止

wait(long n) 有时限的等待, 到n毫秒后结束等待,或是被notify

面试题:sleep(long n) 睡眠n毫秒, wait(long n) 等待n毫秒
1) sleep是Thread方法,而wait是Object的方法
2) sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起用
3) sleep 在睡眠的同时,不会释放对象锁的,但wait在等待的时候会释放对象锁。

线程状态