java 两个线程轮流执行 五种方法

本文介绍了如何实现Java中两个线程轮流执行的任务,例如线程A存入数据,线程B取出数据,并保证顺序。文章通过五个示例详细讲解了使用`synchronized`修饰函数、`synchronized`代码块、`ReentrantLock+Condition`、`Unsafe`的CAS操作以及`AtomicInteger`的方法,分析了各种方法的效率和CPU压力差异,对于理解和实践多线程控制具有指导意义。

welcome to my blog

问题: 线程A向一个map中存数据, 然后线程B从map中取数据, 循环这个过程, 最终效果像: 线程A存入1, 线程B取出1, 线程A存入2, 线程B取出2, 线程A存入3, 线程B取出3… 直到线程A存入100, 线程B取出100

问题的要求非常像生产者消费者模式, 但是有个很大的区别, 那就是生产者的生产和消费者的消费没有顺序要求, 但是这道题要求线程A和线程B轮流执行
考察点: 如何让两个线程轮流执行
考察点: 如何让两个线程轮流执行
考察点: 如何让两个线程轮流执行

下面写了五个例子
前三个例子使用了阻塞+唤醒的思路, 涉及用户态和内核态的切换, 效率偏低, 但是CPU压力小
后两个例子使用了vaolatile+CAS, 不能操作时通过自旋进行等待, 更高效, 但是CPU压力大

方法一: 使用synchronized修饰函数
import java.util.HashMap;

public class Study {
    public static void main(String[] args) {
        MyTest test = new MyTest();

        //充当线程A的角色
        new Thread() {
            @Override
            public void run() {
                test.put();
            }
        }.start();

        //充当线程A的角色
        new Thread() {
            @Override
            public void run() {
                test.get();
            }
        }.start();
    }
}

class MyTest {
    HashMap<Integer, Integer> map = new HashMap<>();
    int i = 1;

    synchronized void put() {
        for (int k = 0; k < 100; k++) {
            map.put(i, i);
            System.out.println(Thread.currentThread().getName() + "  " + i);
            i++;
            this.notify(); //等待队列中只有一个线程, 所以不用使用notifyAll()
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");

    }

    synchronized void get() {
        for (int k = 0; k < 100; k++) {
            int cur = map.get(i - 1);
            System.out.println(Thread.currentThread().getName() + "  " + cur);
            this.notify();
            try {
                //最多等待1s, 等待1s后没被唤醒则自己醒过来
                //因为最后一次循环时, 当前方法让出CPU后, 再也不会被唤醒了
                this.wait(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }
}

/*
打印结果
...
Thread-0  97
Thread-1  97
Thread-0  98
Thread-1  98
Thread-0  99
Thread-1  99
Thread-0  100
Thread-1  100
Thread-0执行完毕
Thread-1执行完毕
*/
方法二: 使用synchronized代码块
import java.util.HashMap;

public class Study {
    public static void main(String[] args) {
        MyTest test = new MyTest();

        //充当线程A的角色
        new Thread() {
            @Override
            public void run() {
                test.put();
            }
        }.start();

        //充当线程A的角色
        new Thread() {
            @Override
            public void run() {
                test.get();
            }
        }.start();
    }
}

class MyTest {
    HashMap<Integer, Integer> map = new HashMap<>();
    int i = 1;

    void put(){
        synchronized (this){
            for(int k=0; k<100; k++){
                map.put(i,i);
                System.out.println(Thread.currentThread().getName() + "  " + i);
                i++;
                this.notify();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }

    void get(){
        synchronized (this){
            for(int k=0; k<100; k++){
                int cur = map.get(i-1);
                System.out.println(Thread.currentThread().getName() + "  " + cur);
                this.notify();
                try {
                    //最后一次wait()后, 不会被唤醒, 所以需要设置最长阻塞时间
                    this.wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }
}
/*
打印结果
...
Thread-0  97
Thread-1  97
Thread-0  98
Thread-1  98
Thread-0  99
Thread-1  99
Thread-0  100
Thread-1  100
Thread-0执行完毕
Thread-1执行完毕
*/
方法三: 使用ReentrantLock+Condition; 因为synchronized能实现的功能ReentrantLock也能实现

起初想使用ReentrantLock的公平锁模式, 按照线程进入AQS等待队列的顺序获取锁, 这样就不用Condition了, 不过运行时有问题, 线程A启动后, 执行了好几次循环, 线程B才开始执行, 这样就没法保证轮流执行了

使用注意事项: Condition的signal(), signalAll(), await()方法必须在ReentrantLock的lock()和unlock()之间使用,否则报错:IllegalMonitorStateException,这跟wait(), notify(), notifyAll()一定要在synchronized块内执行是一样的

import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Study {
    public static void main(String[] args) {
        MyTest test = new MyTest();

        //充当线程A的角色
        new Thread() {
            @Override
            public void run() {
                test.put();
            }
        }.start();

        //充当线程A的角色
        new Thread() {
            @Override
            public void run() {
                test.get();
            }
        }.start();
    }
}

class MyTest {
    HashMap<Integer, Integer> map = new HashMap<>();
    int i = 1;

    ReentrantLock lock = new ReentrantLock();
    Condition con = lock.newCondition();

    void put() {
        for (int k = 0; k < 100; k++) {
            try {
                lock.lock();
                map.put(i, i);
                System.out.println(Thread.currentThread().getName() + "  " + i);
                i++;
                con.signal();
                try {
                    con.await(); //让出锁, 但是state应该不变, state应该由lock控制, 并不是con来控制
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                lock.unlock();
            }

        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }

    void get() {
        for (int k = 0; k < 100; k++) {
            try {
                lock.lock();
                int cur = map.get(i - 1);
                System.out.println(Thread.currentThread().getName() + "  " + cur);
                con.signal();
                try {
	                //最后一次让出锁后, 无法被唤醒, 所以需要定时
                    con.await(1000, TimeUnit.MILLISECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                lock.unlock();
            }

        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }
}

/*
Thread-0  97
Thread-1  97
Thread-0  98
Thread-1  98
Thread-0  99
Thread-1  99
Thread-0  100
Thread-0执行完毕
Thread-1  100
Thread-1执行完毕

Process finished with exit code 0

*/
方法四: 使用Unsafe类的CAS操作, 不能操作时便自旋; 不涉及线程的阻塞与唤醒, 更高效
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Study {
    public static void main(String[] args) {
        MyTest test = new MyTest();

        //充当线程A的角色
        new Thread() {
            @Override
            public void run() {
                test.put();
            }
        }.start();

        //充当线程A的角色
        new Thread() {
            @Override
            public void run() {
                test.get();
            }
        }.start();
    }
}

class MyTest {
    HashMap<Integer, Integer> map = new HashMap<>();
    int i = 1;
    //使用volatile保证flag在两个线程中的可见性
    volatile int flag = 0; //0表示线程0可以执行, 1表示线程1可以执行
//    private static final Unsafe unsafe = Unsafe.getUnsafe(); //直接这么用会报错
    private static final Unsafe unsafe;
    private static final long flagOffset;


    //此处, try catch必须放到代码块中!
    static {
        try {
            unsafe = getUnsafeInstance();
            //获取flag的偏移量, 为CAS操作做准备
            //getDeclaredField()与getField
            flagOffset = unsafe.objectFieldOffset(MyTest.class.getDeclaredField("flag"));
        } catch (NoSuchFieldException e) {
//            throw new Error(e);
            throw new RuntimeException("初始化flagOffset失败");
        }catch(Exception e){
            throw new RuntimeException("初始化unsafe失败");
        }
    }

    //获取unsafe实例
    public static Unsafe getUnsafeInstance() throws Exception {
        // 通过反射获取rt.jar下的Unsafe类
        Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafeInstance.setAccessible(true);
        // return (Unsafe) theUnsafeInstance.get(null);是等价的
        return (Unsafe) theUnsafeInstance.get(Unsafe.class);
    }

    void put() {
        for (int k = 0; k < 100; k++) {
            //自旋; 等待flag为true
            for(;;){
                if(flag==0){
                    break;
                }
            }
            map.put(i, i);
            System.out.println(Thread.currentThread().getName() + "  " + i);
            i++;
            int tmp = flag;
            //使用CAS赋值
            unsafe.compareAndSwapInt(this, flagOffset, tmp, 1);
        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }

    void get() {
        for (int k = 0; k < 100; k++) {
            for(;;){
                if(flag==1){
                    break;
                }
            }
            int cur = map.get(i - 1);
            System.out.println(Thread.currentThread().getName() + "  " + cur);
            int tmp = flag;
            unsafe.compareAndSwapInt(this, flagOffset, tmp, 0);
        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }
}
/*
打印结果
...
Thread-0  97
Thread-1  97
Thread-0  98
Thread-1  98
Thread-0  99
Thread-1  99
Thread-0  100
Thread-1  100
Thread-0执行完毕
Thread-1执行完毕
*/
方法五: 使用AtomicInteger; 不能操作时便自旋
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Study {
    public static void main(String[] args) {
        MyTest test = new MyTest();

        //充当线程A的角色
        new Thread() {
            @Override
            public void run() {
                test.put();
            }
        }.start();

        //充当线程A的角色
        new Thread() {
            @Override
            public void run() {
                test.get();
            }
        }.start();
    }
}

class MyTest {
    HashMap<Integer, Integer> map = new HashMap<>();
    int i = 1;
    AtomicInteger flag = new AtomicInteger(0);//0表示线程0可以执行, 1表示线程1可以执行

    void put() {
        for(int k=0; k<100; k++){
            //自旋
            for(;;){
                if((flag.intValue()&1)==0){
                    break;
                }
            }
            map.put(i,i);
            System.out.println(Thread.currentThread().getName() + "  " + i);
            i++;
            flag.compareAndSet(flag.intValue(), 1);
        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }

    void get() {
        for(int k=0; k<100; k++){
            //自旋
            for(;;){
                if((flag.intValue()&1)==1){
                    break;
                }
            }
            int cur = map.get(i-1);
            System.out.println(Thread.currentThread().getName() + "  " + cur);
            flag.compareAndSet(flag.intValue(), 0);
        }
        System.out.println(Thread.currentThread().getName() + "执行完毕");
    }
}

/*
执行结果
...
Thread-1  97
Thread-0  98
Thread-1  98
Thread-0  99
Thread-1  99
Thread-0  100
Thread-1  100
Thread-1执行完毕
Thread-0执行完毕
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值