讲到了线程之会会有优先级和一些其他因素来抢占CPU资源,它们是争先恐后的去完成各自的方法,这样导致运行结果是不可预料的。
public class Demo {
public static void main(String[] args) throws InterruptedException {
Output out=new Output();
MyThread t1=new MyThread(out, "Hello");
MyThread t2=new MyThread(out, "World");
MyThread t3=new MyThread(out, "Hello World");
t1.join();
t2.join();
t3.join();
System.out.println("Main Thread end");
}
}
class MyThread extends Thread {
private Output out=null;
private String msg=null;
public MyThread(Output out, String msg) {
super();
this.msg=msg;
this.out=out;
this.start();
}
@Override
public void run() {
out.print(msg);
}
}
class Output {
public synchronized void print(String msg){
try {
System.out.print("["+msg);
Thread.sleep(1000);
System.out.println("]");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
[Hello[World[Hello World]
]
]
Main Thread end
多个线程的运行是争先恐口的,但是我们想要每次只让一个线程去调用print()方法,来打印一个“[msg]”这样的语句,而不是上面运行结果这样打乱了的输出。这时候就需要用到同步,使用同步之前,我们先来看看什么是线程的同步?
当两个或两个以上的线程需要共享某些资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用。达到此目的的过程就做同步(synchronization)。
同步的关键是管程的概念。管程是一个互斥独立的对象,或称互斥体。在给定的时间,仅有一个线程可以获得管程。当一个线程需要锁定,它必须进入管程。所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程。这些其他的线程被称为等待线程。一个拥有管程的线程如果愿意可以再次进入相同的管程。
理解同步很重要,举个例子:这就相当于中午要炒几样菜,但是只有一口锅,当炒一个菜的时候去,其他的菜是不能倒进锅里一起炒的,否则这样炒出来的菜就不是预期的结果,必须一样菜一样菜去炒。这几样菜占用共同的资源:锅。
在Java中可以使用两个方法来同步代码。两者都包括synchronized关键字的使用。
使用同步方法
在上面的例子的print方法上加上synchronized关键字,让方法实现同步:
public synchronized void print(String msg){
//...
}
所有对象都有它们与之对应的隐式管程。进入某一对象的管程,就是调用被synchronized关键字修饰的方法。当一个线程在一个同步方法内部,所有试图调用该同步方法的同实例的其他线程必须等待。
任何时候在多线程情况下,你有一个方法或多个方法操纵对象的内部状态,都必须用synchronized关键字来防止状态出现竞争。一旦线程进入实例的同步方法,没有其他线程可以进入相同实例的同步方法。但是,该实例的其他不同步方法仍可以被调用。
同步语句
假设你想获得不为多线程访问设计的类对象的同步访问,也就是,该类没有用到同步方法。而且,该类不是你自己,而是第三方创建的,你不能获得它的源代码。这时候,我们就可以在调用该方法的时候放入一个synchronized块内就可以了。
synchronized (out) {
out.print(msg);
}
把子线程的run()方法改成这样就OK了,其中,out是被同对象的引用。一个同步块确保对out成员方法的调用仅在当前线程成功进入out管程中发生。