Java多线程程序现在很常见,和数据库操作系统一样,多个线程会共享一个堆内存,如果不加以控制,不进行线程之间的同步,会造成数据混乱等。
先看看下面这个程序:
public class TestSynchronized implements Runnable {
Timer timer = new Timer();
public static void main(String args[]) {
TestSynchronized test = new TestSynchronized();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
public void run() {
timer.add(Thread.currentThread().getName());
}
}
class Timer {
private static int num = 0;
public void add(String name) {
// synchronized(this){ //没有同步,将导致num的数目不正常
num++;
try {
Thread.sleep(1);
} catch (Exception e) {
}
System.out.println(name + ", 你是第" + num + "个使用timer的线程");
// }
}
}输出的结果:
t1, 你是第2个使用timer的线程
t2, 你是第2个使用timer的线程
num两次都是2。这里很明显是两个线程在访问同一个Timer对象的num变量时交替进行,所以最后打印的时候都是2;
如何实现不同线程之间互斥访问这个num呢,使用synchronized同步代码块即可解决,用synchronized(this){}将需要同步代码块圈起来。
输出正常结果:
t2, 你是第1个使用timer的线程
t1, 你是第2个使用timer的线程
synchronized的另一个用法是同步方法,就是在方法之前加上修饰符synchronized。那么不同线程在访问同一个对象的synchronized方法时就会互斥访问,从而达到同步
下面是一个经典的生产消费者模型代码:
public class TestSynchronized {
public static void main(String[] args) {
SyncStack ss = new SyncStack();
Producer p = new Producer(ss);
Consumer c = new Consumer(ss);
Thread tp = new Thread(p);
Thread tc = new Thread(c);
tp.start();
tc.start();
}
}
class Bread {
int id;
Bread(int id) {
this.id = id;
}
public String toString() {
return "Bread: " + id;
}
}
class SyncStack {
int index = 0;
Bread[] breads = new Bread[6];
public synchronized void push(Bread b) {
if (index == breads.length) {// 注意只能用while不能用if;因为wait会异常
try {
this.wait();// 访问当前对象的线程wait()
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
breads[index] = b;
index++;
System.out.println("生产了:" + b);
}
public synchronized Bread pop() {
if (index == 0) {
try {
this.wait();// wait的时候锁已经不归该线程所有,但是sleep还有锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();// 叫醒一个正在该对象上wait的一个线程
index--;
System.out.println("消费了:" + breads[index]);
return breads[index];
}
}
class Producer implements Runnable {
SyncStack ss = null;
Producer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for (int i = 0; i < 20; i++) {
Bread b = new Bread(i);
ss.push(b);
// System.out.println("生产了:"+b);
try {
Thread.sleep((int) Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
SyncStack ss = null;
Consumer(SyncStack ss) {
this.ss = ss;
}
public void run() {
for (int i = 0; i < 20; i++) {
Bread b = null;
b = ss.pop();
// System.out.println("消费了:"+breads[index]);//放到这里用if的话会出问题,先打出消费0,再打出生产0
try {
Thread.sleep((int) Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同步栈对象SyncStack的push和pop方法就是同步方法。生产者线程和消费者线程将会互斥访问pop和push方法。