本文章内容整理自:张孝祥_Java多线程与并发库高级应用视频教程
当两条线程访问同一个资源时,可能会出现安全隐患。以打印字符串为例,先看下面的代码:
// public class Test2 { public static void main(String[] args) { new Test2().init(); } public void init(){ final Outputer c = new Outputer(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } c.output("aaaaa"); } } }); thread1.start(); Thread thread2 = new Thread(new Runnable() { @Override public void run() { while(true){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } c.output("bbbbb"); } } }); thread2.start(); } class Outputer{ public void output(String name){ for (int i = 0; i < name.length(); i++) { System.out.print(name.charAt(i)); } System.out.println(); } } }
在以上代码中,thread1和thread2都调用了output方法,理想的情况下,当thread1调用output方法时,应该一次性打印完“aaaaa”,而当thread2调用output方法,也是一次性打印完“bbbbb”。但在实际的运行中,会出现打印出诸如“aabbbaaa”这种情况,也就是说,thread1正在调用output方法时,thread2插了进来,两个线程同时操作了一个对象中的同一个方法。
为了避免这种情况,我们要求,当某个线程在调用output方法时,不允许其他线程调用。概括一下,即当某个线程在执行一段代码时,不允许其他线程执行这段代码。我们把这种行为,叫做线程互斥。
要实现线程互斥,最简单的方法就是加同步锁。
加同步锁的方式有两种。
第一种是同步代码块,以上面的代码为例,加同步代码块应该这么加:
class Outputer{ public void output(String name){ synchronized (this) { for (int i = 0; i < name.length(); i++) { System.out.print(name.charAt(i)); } System.out.println(); } } }
在上面的代码中,“synchronized (this)”中的“this”被称为锁栓(也有别的叫法,在这里姑且先这么叫吧)。多个需要互斥的线程必须使用同一个对象当锁栓。在这段代码中,“this”指Outputer对象,而两条线程使用的是同一个Outputer对象,所以可以达到互斥效果。
第二种方式是声明同步方法,写法如下:
public synchronized void output2(String name){ System.out.println(this.getClass()); for (int i = 0; i < name.length(); i++) { System.out.print(name.charAt(i)); } System.out.println(); }
现在有一个问题,加入thread1调用output1,thread2调用output2,可以实现线程互斥吗?
答案是可以的。
同步方法使用的锁栓就是“this”。当同步代码块也使用“this”当锁栓时,这两个地方的代码就共用了同一个对象当锁栓,当某个线程进入其中一个时,另一个也被锁住了,不让其他线程进来。
那么,静态同步方法使用的锁栓是什么呢?
以下面这段代码为例:
static class Outputer{ public static synchronized void output3(String name){ for (int i = 0; i < name.length(); i++) { System.out.print(name.charAt(i)); } System.out.println(); } }
它的同步锁是Outputer.class,即类的字节码对象。
最后说一说线程同步和线程互斥的关系。
同步是一种更为复杂的互斥,而互斥是一种特殊的同步。
也就是说互斥是两个线程之间不可以同时运行,他们会相互排斥,必须等待一个线程运行完毕,另一个才能运行,而同步也是不能同时运行,但他是必须要安照某种次序来运行相应的线程(也是一种互斥)!
总结:
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥。