个人记录:2018年,工作的第6到7个年头。
重点研究自己不太擅长的技术:分布式、高并发、大数据量、数据库优化、高性能、负载均衡等。
刷题是一种态度,是一种好习惯。
前一篇尝试解决ABC按照顺序输出,没能搞定,今天研究了下网友的代码、思路提示,成功搞定。
题目:有3个线程ABC。按照ABC来运行(A线程输出A,B线程输出B,C线程输出C,以此类推,循环输出)。
实现方法1:
有个网友给了点提示:用 Reentrantlock,用它的newCondition() 方法创建3个condition,按顺序调用 condition 的await和signal 方法就可以了,
就不贴代码了,具体看Reentrantlock 和Condition 讲解。
参考了JDK1.6中文API文档,找到了1个例子。
package cn.fansunion.threadabc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
//有3个线程ABC。按照ABC来运行(A线程输出A,B线程输出B,C线程输出C,以此类推,循环输出)。
//参考JDK官方文档,ReentrantLock和Condition
public class ThreadABCByLockCondition {
public static void main(String[] args) {
//循环次数
final int maxNum = 5;
ReentrantLock reentrantLock = new ReentrantLock();
Condition aCondition = reentrantLock.newCondition();
Condition bCondition = reentrantLock.newCondition();
Condition cCondition = reentrantLock.newCondition();
// 这个地方不能用String currentThread="A";线程内部,要加final,加了final就不能修改引用了
final String[] currentThread = { "A" };
Thread aThread = new Thread(new Runnable() {
@Override
public void run() {
for (int index = 0; index < maxNum; index++) {
reentrantLock.lock();
try {
while (currentThread[0] != "A") {
aCondition.await();
}
System.out.print("A");
currentThread[0] = "B";
bCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}
});
Thread bThread = new Thread(new Runnable() {
@Override
public void run() {
for (int index = 0; index < maxNum; index++) {
reentrantLock.lock();
try {
while (currentThread[0] != "B") {
bCondition.await();
}
System.out.print("B");
currentThread[0] = "C";
cCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}
});
Thread cThread = new Thread(new Runnable() {
@Override
public void run() {
for (int index = 0; index < maxNum; index++) {
reentrantLock.lock();
try {
while (currentThread[0] != "C") {
cCondition.await();
}
System.out.print("C");
// 打印1个换行
System.out.println();
currentThread[0] = "A";
aCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}
});
aThread.start();
bThread.start();
cThread.start();
}
}
官方例子
public interface ConditionCondition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。
作为一个示例,假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
(ArrayBlockingQueue 类提供了这项功能,因此没有理由去实现这个示例类。)
Condition 实现可以提供不同于 Object 监视器方法的行为和语义,比如受保证的通知排序,或者在执行通知时不需要保持一个锁。如果某个实现提供了这样特殊的语义,则该实现必须记录这些语义。
实现方法2:
参考了网友的代码,做了一点改造,可读性更强一些。
package cn.fansunion.threadabc;
import java.util.concurrent.atomic.AtomicInteger;
//使用AtimicInteger,原子CAS,切换正在打印的Thread
public class ThreadABCByAtomic {
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(new PrintRunnable("A", 1, 2));
Thread b = new Thread(new PrintRunnable("B", 2, 3));
Thread c = new Thread(new PrintRunnable("C", 3, 1));
a.start();
b.start();
c.start();
a.join();
b.join();
c.join();
}
static class PrintRunnable implements Runnable {
private String str;
private static AtomicInteger runFlag = new AtomicInteger(1);
private int mySequence;
private int next;
private PrintRunnable(String str, int mySequence, int next) {
this.str = str;
this.mySequence = mySequence;
this.next = next;
}
@Override
public void run() {
//while循环,性能很差
while (true) {
if (runFlag.get() == mySequence)
print();
}
}
public void print() {
System.out.print(str);
if (next < mySequence)
System.out.println();
// cas设置下一个执行序号
runFlag.compareAndSet(mySequence, next);
}
}
}
网友源代码:
//A线程的序列mySequence为啥是3
利用Atomic原子操作,实现顺序控制
@Test
public void test2() throws InterruptedException{
Thread a = new Thread(new PrintRunnable2(“A”,3,2));
Thread b = new Thread(new PrintRunnable2(“B”,2,1));
Thread c = new Thread(new PrintRunnable2(“C”,1,3));
a.start();
b.start();
c.start();
a.join();
b.join();
c.join();
}
static class PrintRunnable2 implements Runnable{
private String str;
private static AtomicInteger runFlag = new AtomicInteger(3);
private int mySequence;
private int next;
private PrintRunnable2(String str,int mySequence,int next) {
this.str = str;
this.mySequence = mySequence;
this.next=next;
}
@Override
public void run() {
while(true){
if(runFlag .get()== mySequence)
print();
}
}
public void print(){
System.out.print(str);
if(next>mySequence)
System.out.println();
//cas设置下一个执行序号
runFlag.compareAndSet(mySequence, next);
}
}
总结:使用while循环,一直等待自己的“锁”。
方法1,是用锁+信号+while循环
方法2,用atomicInteger+while循环。
实际工作中,很少有这种奇葩问题。既然是多线程,自然是希望他们并发执行。
如果任务有先后顺序,就可以换成方法去做了。
问题出处:
http://ifeve.com/question/%e6%9c%893%e4%b8%aa%e7%ba%bf%e7%a8%8babc%e3%80%82%e6%8c%89%e7%85%a7abc%e6%9d%a5%e8%bf%90%e8%a1%8c%ef%bc%88a%e7%ba%bf%e7%a8%8b%e8%be%93%e5%87%baa%ef%bc%8cb%e7%ba%bf%e7%a8%8b%e8%be%93%e5%87%bab%ef%bc%8c/
里面有不少参考思路。
思考:
在方法1的实现中,用了while循环,可以用if条件吗?
while (currentThread[0] != "A") {
aCondition.await();
}
官方的Demo例子中,可以用if条件吗?
while (count == items.length)
notFull.await();