其实掌握这些类的基本用法后,一般面试题都难不倒的,我们当以不变应万变.
题目:实现一个容器,提供两个方法,add,size.写两个线程,线程1添加10个元素到容器中.线程2有一个"监听"容器的效果.当线程1添加的元素个数到5个时,线程2给出提示并结束,然后t1线程继续执行.
这条路子没走通,因为volatile如果修饰的引用类型对象,不能保证该对象的属性对其他线程的可见性.
volatile对于对象变量的思考在这里点我查看
public class T02_WithVolatileAndSynList {
// 添加volatile,使t2能够得到通知
// 使用同步容器,因为list.add是先把元素加入,然后再让size++
volatile List lists = Collections.synchronizedList(new LinkedList<>());
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
T02_WithVolatileAndSynList c = new T02_WithVolatileAndSynList();
new Thread(() -> {
while (true) {
int size = c.size();
if (size == 5) {
System.out.println("t2 end" + " at " + Instant.now());
break;
}
}
System.out.println("t2 end");
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i /*+ " at " + Instant.now()*/);
/*try {
TimeUnit.NANOSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}, "t1").start();
}
}
示例如下:(这里没有用notifyAll,是因为锁上只有俩线程)
public class T04_NotifyFreeLock {
//添加volatile,使t2能够得到通知.但是不加好像也没什么问题,毕竟通过阻塞控制了
/*volatile*/ List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
T04_NotifyFreeLock c = new T04_NotifyFreeLock();
final Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
System.out.println("t2 start");
if (c.size() != 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int size = c.size();
System.out.println("t2 end c.size():" + size);
//通知t1继续执行
lock.notify();
}
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
new Thread(() -> {
System.out.println("t1 start");
synchronized (lock) {
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
lock.notify();
//释放锁,让t2得以执行
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
}, "t1").start();
}
}
同方法1的思路一样,只不过使用CountdownLatch替代;
如果不需要考虑同步,即线程t1添加第5个元素后,可以让t1添加其他元素的同时t2去做一些操作,那就只需一个CountdownLatch;
如果需要考虑同步,即线程t1添加第5个元素后,需要等t2执行一定任务后,t1再继续添加,那就需要两个CountdownLatch
如果只是考虑线程间通信,而不需要同步,那么用synchronized + wait/notify就显得太重了,这时应该考虑countdownlatch/cyclicbarrier/semaphore
代码示例(需要考虑同步的情况):
public class T05_CountDownLatch {
// 添加volatile,使t2能够得到通知
/*volatile*/ List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
T05_CountDownLatch c = new T05_CountDownLatch();
CountDownLatch latchT1 = new CountDownLatch(1);
CountDownLatch latchT2 = new CountDownLatch(1);
new Thread(() -> {
System.out.println("t2start");
if (c.size() != 5) {
try {
latchT2.await();
//也可以指定等待时间
//latchT2.await(5000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 end");
latchT1.countDown();
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
new Thread(() -> {
System.out.println("t1 start");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
// 打开门闩,让t2得以执行
latchT2.countDown();
try {
// 这个是保证线程t2处理完后,再继续执行;
// 如果不需要等t2处理完后再继续,那就可以省去latchT1的操作
latchT1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t1").start();
}
}
这个和CountdownLatch类似,LockSupport用起来更简单一些.
但是,一般情况下我们在工程中不会显式创建线程,这时候LockSupport可能就不太好使了
public class T07_LockSupport_WithoutSleep {
// 添加volatile,使t2能够得到通知
/*volatile*/ List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
static Thread t1 = null, t2 = null;
public static void main(String[] args) {
T07_LockSupport_WithoutSleep c = new T07_LockSupport_WithoutSleep();
t1 = new Thread(() -> {
System.out.println("t1 start");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
LockSupport.unpark(t2);
LockSupport.park();
}
}
}, "t1");
t2 = new Thread(() -> {
LockSupport.park();
System.out.println("t2 end");
LockSupport.unpark(t1);
}, "t2");
t2.start();
t1.start();
}
}
写一个固定容量同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用
思路就是内部设置个LinkedList属性,然后自己维护get和put方法.
每次get前,先while循环判断,如果size=0就wait;size!=0就拿出一个值,并唤醒生产者们;
每次put前,先while循环判断,如果size=max就wait;否则就往list中添加值,然后唤醒消费者们.
public class WzContainer<T> {
private int max = 10;
private int size = 0;
private LinkedList<T> values = new LinkedList<>();
public synchronized T get() {
while (size == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = values.removeFirst();
size--;
this.notifyAll();// 通知生产者
return t;
}
public synchronized void put(T t) {
while (size == this.max) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
values.addLast(t);
size++;
this.notifyAll();// 通知消费者
}
public static void main(String[] args) {
WzContainer<String> c = new WzContainer<>();
//启动消费者线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 4; j++) {
c.get();
System.out.println(c.get());
}
}, "consumer-" + i).start();
}
/*try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//启动生产者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 20; j++) {
c.put(Thread.currentThread().getName() + " at " + Instant.now());
}
}, "producer-" + i).start();
}
}
}
public class WzContainerReentrantLock<T> {
private int max = 10;
private int size = 0;
private LinkedList<T> values = new LinkedList<>();
private Lock lock = new ReentrantLock();
private Condition producerCondition = lock.newCondition();
private Condition consumerCondition = lock.newCondition();
public T get() {
T t = null;
try {
lock.lock();
while (size == 0) {
consumerCondition.await();
}
t = values.removeFirst();
size--;
producerCondition.signalAll();// 通知生产者
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return t;
}
public void put(T t) {
try {
lock.lock();
while (size == this.max) {
producerCondition.await();
}
values.addLast(t);
size++;
consumerCondition.signalAll();// 通知消费者
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
WzContainerReentrantLock<String> container = new WzContainerReentrantLock<>();
//启动消费者线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 4; j++) {
String s = container.get();
System.out.println(s);
}
}, "consumer-" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启动生产者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 20; j++) {
container.put(Thread.currentThread().getName() + " at " + Instant.now());
}
}, "producer-" + i).start();
}
}
}
有好几种实现方法,但是中心思想都是一样的,通过线程的阻塞来实现.
首先公用部分:
static List<String> letters = new ArrayList<>();
static List<String> numbers = new ArrayList<>();
static int count = 0;
static {
for (char i = 'A'; i <= 'Z'; i++) {
letters.add(String.valueOf(i));
count = count + 1;
numbers.add(count + "");
}
}
static Thread t1;
static Thread t2;
public static void useSynchronized() {
Object lock = new Object();
t1 = new Thread(() -> {
synchronized (lock) {
letters.forEach(s -> {
System.out.println(s);
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
lock.notify();
}
}, "t1");
t2 = new Thread(() -> {
synchronized (lock) {
numbers.forEach(s -> {
System.out.println(s);
lock.notify();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
lock.notify();
}
}, "t2");
t1.start();
t2.start();
}
public static void useLockSupport() {
t1 = new Thread(() -> {
letters.forEach(s -> {
System.out.println(s);
LockSupport.unpark(t2);
LockSupport.park();
});
System.out.println("t1end");
}, "t1");
t2 = new Thread(() -> {
numbers.forEach(s -> {
LockSupport.park();
System.out.println(s);
LockSupport.unpark(t1);
});
LockSupport.unpark(t1);
System.out.println("t2end");
}, "t2");
// 注意t2先开始,因为我让t2先park了
t2.start();
t1.start();
}
public static void useLockSupport() {
t1 = new Thread(() -> {
letters.forEach(s -> {
System.out.println(s);
LockSupport.unpark(t2);
LockSupport.park();
});
System.out.println("t1end");
}, "t1");
t2 = new Thread(() -> {
numbers.forEach(s -> {
LockSupport.park();
System.out.println(s);
LockSupport.unpark(t1);
});
LockSupport.unpark(t1);
System.out.println("t2end");
}, "t2");
// 注意t2先开始,因为我让t2先park了
t2.start();
t1.start();
}
public static void useReentrantLock() {
Lock lock = new ReentrantLock();
Condition letterCondition = lock.newCondition();
Condition numberCondition = lock.newCondition();
t1 = new Thread(() -> {
lock.lock();
try {
letters.forEach(s -> {
System.out.println(s);
numberCondition.signal();
try {
letterCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
numberCondition.signal();
} finally {
lock.unlock();
}
}, "t1");
t2 = new Thread(() -> {
lock.lock();
try {
letters.forEach(s -> {
System.out.println(s);
letterCondition.signal();
try {
numberCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
letterCondition.signal();
} finally {
lock.unlock();
}
}, "t2");
t1.start();
t2.start();
}
enum ThreadRun {
T1, T2
}
static volatile ThreadRun threadRun = ThreadRun.T1;
public static void useCustomCas() {
t1 = new Thread(() -> {
letters.forEach(s -> {
while (threadRun != ThreadRun.T1) {
}
System.out.println(s);
threadRun = ThreadRun.T2;
});
}, "t1");
t2 = new Thread(() -> {
numbers.forEach(s -> {
while (threadRun != ThreadRun.T2) {
}
System.out.println(s);
threadRun = ThreadRun.T1;
});
}, "t2");
t1.start();
t2.start();
}
static TransferQueue<String> transferQueue = new LinkedTransferQueue<>();
public static void useSemaphore() {
t1 = new Thread(() -> {
numbers.forEach(s -> {
try {
System.out.println(transferQueue.take());
transferQueue.transfer(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}, "t1");
t2 = new Thread(() -> {
letters.forEach(s -> {
try {
transferQueue.transfer(s);
System.out.println(transferQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}, "t2");
t1.start();
t2.start();
}