出现线程安全就是在使用多线程的时候程序出现了不期望的结果。
怎样思考线程安全:线程中任何一步运行完都可能交出CPU的控制权。
下面是一个可能出现线程安全的例子:
class ThreadDemo { public static void main(String[] args) { TestThread t = new TestThread(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } } class TestThread implements Runnable { int tickets = 100; public void run() { while(true) { if(tickets<=0) break; //try{Thread.sleep(10);}catch(Exception e) {} System.out.println(Thread.currentThread().getName() + " is saling " + tickets--); } } }
例子展示了四个售票员一起卖这100张票,那么卖出同样序号的票是我们不期望看到了情况吧,上面已经提到了,分析线程安全的最基本的方法就是认为线程运行的时候任何情况下都可以交出CPU的控制权,上面一共启动了4个线程,现在假设tickets的值为1时,线程1在运行完“if(tickets<=0)”后交出CPU,后线程2得到了CPU,卖出了票1,后线程1重新得到了CPU,又卖出了票1或0。当然,上述情况是我们假设在什么情况下会出现象这样的线程安全的问题,在一般情况,我们运行这个程序好几次也不能看出这样的线程安全问题,那我们怎么确定这个问题呢?我们可以用“sleep(100)”来主动交出CPU来验证我们的想法:去掉上面注释的sleep再运行程序,得到
卖出了0、-1和-2的票,证明上述这种写法是存在线程安全的。
为了解决线程安全的问题,就要引入线程同步。
class ThreadDemo { public static void main(String[] args) { TestThread t = new TestThread(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } } class TestThread implements Runnable { int tickets = 100; String str = ""; public void run() { while(true) { synchronized(str) { if(tickets<=0) break; //try{Thread.sleep(10);}catch(Exception e) {} System.out.println(Thread.currentThread().getName() + " is saling " + tickets--); } } } }
在上面的代码中加入synchronized,就可以实现线程同步了。
这个线程同步怎么理解呢?这里有个锁旗标的概念,锁旗标可以理解为java中的每一个对象都有个标志位,该标志位开始的状态是1,当执行完synchronized后这个对象的标志位被置为了0,这个过程就说这个线程得到了这个对象的锁旗标,synchronized块运行完之后这个线程会让出这个对象的锁旗标,而每个线程在遇到synchronized是都回查看这个对象的锁旗标在不在,如果不在该线程就会主要让出CPU。
这里还要记住synchronized的对象一定要是多个线程的公共对象,要是各自的对象就不能实现同步了。如下面改变str定义的位置。
class ThreadDemo { public static void main(String[] args) { TestThread t = new TestThread(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } } class TestThread implements Runnable { int tickets = 100; public void run() { String str = ""; while(true) { synchronized(str) { if(tickets<=0) break; //try{Thread.sleep(10);}catch(Exception e) {} System.out.println(Thread.currentThread().getName() + " is saling " + tickets--); } } } }
另外,在运行加入了synchronized同步块的程序的时会发现速度明显比没有同步块的程序要慢的多,所以在确定不会出现线程安全问题的程序中不要加入同步块,就像我们经常先使用Vector还是有ArrayList呢?它们两个的功能基本是完全一样的都是List,而Vector中的函数考虑了线程同步,ArrayList没有,这下就非常明显了,如果需要保证线程安全是就用Vector,不需要就用ArrayList效率高些。
同步函数实现
class ThreadDemo { public static void main(String[] args) { TestThread t = new TestThread(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } } class TestThread implements Runnable { int tickets = 100; public void run() { while(true) { sale(); } } public synchronized void sale() { if(tickets<=0) return; try{Thread.sleep(10);}catch(Exception e) {} System.out.println(Thread.currentThread().getName() + " is saling " + tickets--); } }
同步函数实际上同步的是this对象,这样如果要想某一个同步块和一个同步函数同步,就在同步块中使用this对象。