有道面试题是这样考的:
一个全局变量tally,两个线程并发执行(代码段都是ThreadProc),问两个线程都结束后,tally取值范围。
inttally = 0;//glable
void ThreadProc() {
for(int i = 1;i <= 50;i++)
tally += 1;
}
答案是50-100,其实如果写个代码测试一下的话一直都是输出100,不过理论上是50-100无误,可能是计算机运行速度太快了吧,还没等线程挂起就运行完了,当把循环次数提高到10万以上级别的时候效果就很明显了,那为什么是50-100呢,其实是因为tally+=1在汇编里面不止一条语句,有可能在执行一条之后线程被挂起了,跳到另一个线程里面执行去了,然后就造成了数据出错。
tally+=1 ==> tally = tally + 1;
比如当1线程在计算完tally+1之后就被挂起了,没有赋值给tally。然后2线程获取了运行权,顺利执行完tally+=1,然后挂起轮到1线程执行,这时候1线程继续以前没完成的操作,即将结果赋值给tally,这时候虽然两个线程各执行了一次加一操作,实际上相当于只执行了一次,就会出现50+50!=100的结果。
那么如何避免这种现象呢。这就用到了java里面的synchronized.
public void test() throws InterruptedException {
TestThread thread = new TestThread();
new Thread(thread).start();
new Thread(thread).start();
Thread.sleep(1000);
System.out.println(anInt);
}
public class TestThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 800000; ++i) {
addCount();
}
}
private synchronized void addCount() {
anInt++;
}
}
加了synchronized关键字那就表示必须要等到这个函数执行完了才能进行线程切换,也就是说在执行这个函数的时候不允许线程切换。就可以达到并发控制的效果。
再看另一个问题,我们知道这份代码只适用于两个相同的线程。那么如果出现两个不同的线程需要对同一个数据进行访问时该如何同步呢。
答案就是将方法写到一个类中。看一下代码就都明白了。
public class Demo4 {
public static void main(String[] args) {
Demo4 demo4 = new Demo4();
demo4.test();
}
private void test() {
Shared shared = new Shared();
new Thread(new ThreadDemo1(shared)).start();
new Thread(new ThreadDemo2(shared)).start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(anInt);
}
private int anInt = 0;
public class ThreadDemo2 implements Runnable {
Shared shared;
public ThreadDemo2(Shared shared) {
this.shared = shared;
}
@Override
public void run() {
for (int i = 0; i < 500000; ++i)
shared.dev();
}
}
public class ThreadDemo1 implements Runnable {
Shared shared;
public ThreadDemo1(Shared shared) {
this.shared = shared;
}
@Override
public void run() {
for (int i = 0; i < 500000; ++i)
shared.inc();
}
}
public class Shared {
public synchronized void inc() {
anInt++;
}
public synchronized void dev() {
anInt--;
}
}
}