0
点赞
收藏
分享

微信扫一扫

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


​​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执行完毕
*/


举报

相关推荐

0 条评论