多线程的问题可以拆分成四个部分,从这四个方面来考虑并发或者同步执行时是否会出现问题:
1,原子性:在进行关键操作时,比如进行增加删除操作时,代码块部分是否只能被一个线程访问
2,可见性:所有线程是否可以得到共享变量的最新值
3,有序性:已经上锁的代码块被线程访问时,未上锁的代码块查看共享变量是否会出现脏数据
4,线程之间的通信:所有线程是否有一套完整的唤醒与等待的机制,当需要同步的时候可以考虑进程间通信
因为Atomic类、AQS组件使用起来没有那么顺手,在遇到多线程题目时未必能反应过来,不如掌握两种简单的方法,在以后遇到问题时以不变应万变
第一种synchronized实现原子性,volatile保证共享变量可见,所有代码上锁不存在指令重排序问题,waitnotife实现通信
public class Main {
final static Object lock = new Object();
volatile static int MAX = 10;
volatile static int con = 0;
public static void main(String[] args) {
for(int i = 0; i < 100; i++){
new Consumer().start();
new Producer().start();
}
}
}
public class Consumer extends Thread{
@Override
public void run() {
//锁可以加在while之外,表示执行这些操作之前必须拿锁
synchronized (Main.lock){
//必须使用while
while(Main.con == 0){
try {
//等待前叫醒其他人
Main.lock.notifyAll();
Main.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Main.con--;
System.out.println("消费后" + Main.con);
//结束时叫醒其他人
Main.lock.notifyAll();
}
}
}
public class Producer extends Thread{
@Override
public void run() {
synchronized (Main.lock){
while(Main.con == Main.MAX){
try {
Main.lock.notifyAll();
Main.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Main.con++;
System.out.println("生产后" + Main.con);
Main.lock.notifyAll();
}
}
}
第二种使用lock锁,实现选择性通知;分成两个等待队列来获取同一把锁,使用signAll+await实现线程之间通信
public class Main {
final static Lock lock = new ReentrantLock();
volatile static int MAX = 10;
volatile static int con = 0;
static Condition producer = lock.newCondition();
static Condition consumer = lock.newCondition();
public static void main(String[] args) {
for(int i = 0; i < 100; i++){
new Consumer().start();
new Producer().start();
}
}
}
public class Consumer extends Thread{
@Override
public void run() {
Main.lock.lock();
try {
while(Main.con == 0){
Main.producer.signalAll();
Main.consumer.await();
}
Main.con--;
System.out.println("消费后" + Main.con);
Main.producer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
Main.lock.unlock();
}
}
}
public class Producer extends Thread{
@Override
public void run() {
Main.lock.lock();
try {
while(Main.con == Main.MAX){
Main.consumer.signalAll();
Main.producer.await();
}
Main.con++;
System.out.println("生产后" + Main.con);
Main.consumer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
Main.lock.unlock();
}
}
}
在上面的代码中重点关注一个问题,虚假唤醒问题:wait 方法的特性是,在哪里睡觉,在哪里醒来,这导致如果使用 if 判断,会出现线程安全问题,因此需要使用 while 来循环判断
注意这里的 lock 在 try 的外面。如果放在里面,并且在获取锁时发生了异常,那么肯定也会走 finally 代码块,执行 lock.unlock 去释放锁,可问题是我还没获取到锁啊,因此会抛出如下异常