最近看了许多线程同步的内容,这里,简要记录我学习线程同步的过程。线程的概念就不说了,着重写遇到的各种问题。
首先要弄清楚的是,线程同步是什么情况,为什么会出现不同步。比如说我(被调用的对象)现在有100块钱,并告知了朋友A(线程1)和朋友B(线程2),朋友A问我借了50,于是我就只有50块钱了,而朋友B并不知道,准备把我的100块钱全部借走,但突然发现我只有50块钱,不够借了。这个过程就造成了线程不同步。
当多个线程调用同一个对象的时候,如果不采取措施,被调用对象的实际内容与线程中储存的该对象的内容很容易出现差异,即信息不同步。
下面的代码就是一个典型的例子
public class StringBufferTest {
StringBuilder sb = new StringBuilder("12345");
public StringBuilder appendChar(char c,int id) {
System.out.println(id+" -before,:"+sb);
sb.append(c);
System.out.println(id+" -later,:"+sb);
return sb;
}
public static void main(String[] args){
StringBufferTest sbt = new StringBufferTest();
//开启5个线程
for(int i=0;i<5;i++){
new Thread(new addStringBuffer(sbt,i)).start();
}
}
}
class addStringBuffer implements Runnable{
private StringBufferTest sbt;
static char c=97;
int id;
public addStringBuffer(StringBufferTest sbt,int id){
this.sbt = sbt;
this.id = id;
}
public void run(){
sbt.appendChar(c++,id);
}
}
代码的appendChar()实际上是重写了StringBuffer类中的append()方法,参数id纯粹是用来标记5个线程的id,从0-4。运行的结果如下:
从结果看出,id为0的线程,执行apend()方法前,StringBuilder的内容是12345,而执行apend()之后,却变成了12345cba。这说明,在id=0的线程执行之前,先完成了线程id=3和id=1的这两个线程。StringBuilder对象被前三个线程不分先后的调用,导致了其内容变得不可控制。典型的线程不同步的后果。(当然,结果页可能是同步的,这个很好理解)
解决这个问题,线程排队是一个不错的方法。对象A同一时间只能被一个线程调用,其他想调用对象A的线程要依次排队(就绪队列)。只有让当前线程结束之后,下一个线程才能调用对象A。这样就保证了对象A信息的同步。在JAVA中只需添加同步关键字,便可解决问题。
public synchronized StringBuilder appendChar(char c,int id) {
System.out.println(" "+id+" -before,:"+sb);
sb.append(c);// 往盘子里放鸡蛋
System.out.println(" "+id+" -later,:"+sb);
return sb;
}
运行的结果如下,
结果上可以看出,依次运行线程0-3-1-2-4,线程之间没有冲突,这样就保证的调用StringBuilder的同步。synchronized关键字锁住了它修饰的方法,保证了方法appendChar()同一时间只能在一个线程中运行,如果其他线程想调用appendChar()方法,就必须排队,等当前线程运行结束后,才能调用。
还有一个问题,为什么不是按线程id=0-1-2-3-4运行,这和synchronized有关系吗。synchronized关键字只是扮演了维护治安的作用,督促所有的线程(除了当前线程外)都排队,依次运行。至于谁先谁后,它不管。先到先运行。所以每次运行线程id的顺序可能不同,但保证了线程之间没有冲突。