题目:
实现一个容器,提供两个方法,add 和 size写两个线程,线程1 添加10个元素到容器中,线程2实现监控元素的个数,当个数 为 5 的时候,线程2给出提示并结束。
思路
1、写一个容器类,写一个 List 集合用于存储对象,写一个 add 方法向 集合中添加对象,写一个 size 方法返回集合中当前对象的个数。
public class Test1 {
static List lists = new ArrayList();
void add(Object o) {
lists.add(o);
}
int size() {
return lists.size();
}
public static void main(String[] args) {
......
}
2、在main 函数中开启一个线程,向集合中通过 for 循环添加是个对象,另外开启一个线程,等待集合中有五个对象的时候,给出提示。
public static void main(String[] args) {
Test1 t1 = new Test1();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i<10;i++) {
System.out.println("添加了第 "+i+" 个对象了");
t1.add(new Object());
}
}
}).start();
}
2.1 那第二个线程怎么知道什么时候第五个对象被添加呢?
脑海中有两个思路:1、while 循环一直判断 集合的 size,如果到5,就给出提示;
2、让第一个线程添加完第五个对象之后通知第二个线程。
1、while 循环一直判断 集合的 size,如果到5,就给出提示
都来尝试一下吧,首先第一种方法:
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while(t1.size()!=5) {
System.out.println("现在添加了 "+t1.size()+" 个对象");
}
System.out.println("已经是第五个对象了。。。");
}
}).start();
似乎没啥问题,看看 log,偶尔会出现正常log,但是更多时候是这样的。
现在添加了 0 个对象
现在添加了 0 个对象
现在添加了 0 个对象
现在添加了 0 个对象
添加了第 0 个对象了
现在添加了 0 个对象
添加了第 1 个对象了
添加了第 2 个对象了
添加了第 3 个对象了
添加了第 4 个对象了
添加了第 5 个对象了
添加了第 6 个对象了
添加了第 7 个对象了
添加了第 8 个对象了
添加了第 9 个对象了
现在添加了 1 个对象
现在添加了 10 个对象
现在添加了 10 个对象
现在添加了 10 个对象
现在添加了 10 个对象
很明显,多线程导致的问题,我们可以看出:
线程1修改集合后,并没有立即把缓存中的集合进行同步,修改。所以缓存中的 size 并不是 5。
怎么解决这个问题呢? volatile 关键字;试一试吧,实践是检验真理的唯一标准。
现在添加了 0 个对象
现在添加了 0 个对象
现在添加了 0 个对象
添加了第 0 个对象了
现在添加了 0 个对象
添加了第 1 个对象了
添加了第 2 个对象了
添加了第 3 个对象了
添加了第 4 个对象了
添加了第 5 个对象了
添加了第 6 个对象了
添加了第 7 个对象了
添加了第 8 个对象了
添加了第 9 个对象了
现在添加了 1 个对象
现在添加了 10 个对象
现在添加了 10 个对象
可以看出没啥实质性的改变,再找原因呗。因为多线程,两个线程并不是依次执行,所以会出现这种现象。
解决多线程问题,可以使用锁机制来解决。
就回到的上边所说的第二种思路:
2、让第一个线程添加完第五个对象之后通知第二个线程
就是使用线程的,wait 和 notify 方法。
package com.cxp.test;
import java.util.ArrayList;
import java.util.List;
public class Test1 {
static List lists = new ArrayList();
final static Object obj = new Object();
void add(Object o) {
lists.add(o);
}
int size() {
return lists.size();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Test1 t1 = new Test1();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(obj) {
System.out.println("此时长度为 "+t1.size());
if(t1.size() !=5) {
try {
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 切记此处不能写到 else 代码中,如果写到 else 代码中,很大可能不会执行。
// 在调用 wait 之后,线程等待,收到 notify ,可能从 wait 调用处继续向后
//执行,也可能重新执行 run 方法。所以下边代码不能写到上一个 if 语句的 else 中,切记
if(t1.size()==5) {
System.out.println("zhi xin dao le di wu ge o ");
obj.notify();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
synchronized(obj) {
for(int i = 0;i<10;i++) {
System.out.println("添加了第 "+i+" 个对象了");
t1.add(new Object());
if(i==4) {
// 添加完第五个对象之后,唤醒线程1
obj.notify();
try {
//唤醒线程1之后,自己wait,因为notify方法并不会释放锁
//所以如果不调用 notify 方法的话,线程1也可能无法在添
//加第六个对象之前执行
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}).start();
}
}
需要注意的地方,在代码中注释以详细标记,我们需要时时刻刻有多线程的思想,也需要时时刻刻提防多线程将会出现的问题。
在多线程编程过程中,还有一些疑惑在此记录一下:
1、Synchronized 获取得到的是在堆中对象的锁,这样的话,即使引用发生变化,我们的对象锁不会变化。
2、一个同步方法可以调用另外一个同步方法,一个线程已经拥有了某个对象的锁,再次申请的时候仍会得到该锁(同样的锁)
3、在多线程执行的过程中,在一个同步方法中,如果执行代码出现异常,就会自动释放锁,这就意味着后边的等待的线程就会来访问这个没有处理完成的脏数据,所以多线程编程危险系数较高。
4、如果不想因为异常而释放锁,出现问题,加上 try…cache… 正确处理异常。
5、在实际开发中,有时候引用的库中也许会使用 字符串加锁,如果你在开发中恰巧使用了相同的字符串来加锁,就有可能出现死锁的现象了。所以最好不要用字符串对象进行加锁。
String s1 = "haha";
String s2 = "haha";
void m1() {
synchronized(s1) {
for(int i = 0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"----- "+i);
}
}
}
void m2() {
synchronized(s2) {
for(int i = 0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+"----- "+i);
}
}
}