Object中的同步机制
作者:qixiaorui 日期:2009-04-30 09:32:20
robot.JPG(20.0 K) | |
程序不是玩具,必须具备商业运作的稳定性。所以在运行的时候要充分考虑不同线程之间的同步性。在使用同步性技术的时候,考虑到标志布尔型变量与while循环搭配的效果容易对CPU造成很大的资源浪费,所以在这里采取了wait+notify技术。
Java在设计之初就充分考虑到了线程同步的问题,因而在设计基础类Oject的时候就为其提供了基于同步技术的三个函数wait+notify+notifyAll。这就意味着你所编写的任何类都可以使用这些方法(虽然在某些时候这些方法是根本没有意义的)。
我们知道,Thread对象的sleep()方法也可以使当前线程暂停运行。但sleep()方法和wait()方法从本质上来说是不一样的。sleep()使得一个进程进入睡眠状态,但其线程所占有的资源并没有释放。假设,你在synchronized模块(加函数锁或者对象锁)里调用了sleep(),虽然线程睡着了而且没有使用资源,但是它依然保存着锁,别的线程依然无法调用相关的synchronized模块。而wait()则不同,它实际上了是放弃了锁,将资源贡献出来,而使自己暂时离岗。这个离岗状态一直会持续到“boss”(其他的线程)调用了notify(),通知线程继续回来工作为止。
关于线程同步的三个函数,JAVA DOC中是这样描述的:
wait导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。当前的线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行.
notify唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。此方法只应由作为此对象监视器的所有者的线程来调用.
"当前的线程必须拥有此对象监视器"与"此方法只应由作为此对象监视器的所有者的线程来调用"说明wait方法与notify方法必须在同步块内执行,即synchronized(obj之内).
调用对像wait方法后,当前线程释放对像锁,进入等待状态.直到其他线程(也只能是其他线程)通过notify 方法,或 notifyAll.该线程重新获得对像锁.
继续执行,记得线程必须重新获得对像锁才能继续执行.因为synchronized代码块内没有锁是寸步不能走的.
下面我举一个实际例子来说明线程的同步问题:
例子非常通俗易懂。我将构造一个机器人。该机器人有一个身子,两只胳膊和两条腿。除了身子不动外,头、腿和胳膊要有节律地运动。因为每个零件的运动都在单独的一个线程内,所以要想让胳膊和腿按照一定的顺序执行,必须使用同步机制。
首先来看一个无规律运动的实例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | package ql.abs; import java.io.*; import java.text.*; import java.util.*; import java.net.*; import quanwen.*; //常用函数 import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; import javax.swing.border.*; import java.util.Iterator; import java.util.List; /** * 一个未基于线程同步的小机器人 */ public class ExpNoSync extends JFrame{ JPanel pnlTop=new JPanel(), pnlBottom=new JPanel(); JPanel pnlTopLeft=new JPanel(), pnlTopRight=new JPanel(), pnlTopCenter=new JPanel(); JPanel armLeft=new Arm1(), pnlTopLeft2=new JPanel(); JPanel armRight=new Arm2(), pnlTopRight2=new JPanel(); JPanel pnlBottomLeft=new JPanel(), leg=new Leg(), pnlBottomRight=new JPanel(); JPanel head=new Head(); Container c; public ExpNoSync(){ super("小机器人"); armLeft.setPreferredSize(new Dimension(50,50)); pnlTopLeft2.setPreferredSize(new Dimension(50,50)); pnlTopCenter.setBackground(Color.white); pnlTopCenter.setPreferredSize(new Dimension(50,50)); armRight.setPreferredSize(new Dimension(50,50)); pnlTopRight2.setPreferredSize(new Dimension(50,50)); pnlBottomLeft.setPreferredSize(new Dimension(50,50)); leg.setPreferredSize(new Dimension(50,50)); pnlBottomRight.setPreferredSize(new Dimension(50,50)); c=getContentPane(); c.setLayout(new GridLayout(3,3,0,0)); c.add(pnlBottomLeft); c.add(head); c.add(pnlBottomRight); c.add(armLeft); c.add(pnlTopCenter); c.add(armRight); c.add(pnlTopRight2); c.add(leg); c.add(pnlTopRight); setSize(167,160); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); show(); } public static void main(String[] args){ ExpNoSync exp=new ExpNoSync(); } class Arm1 extends JPanel{ ImageIcon[] icon=new ImageIcon[2]; int step=0; JLabel lblArm1=new JLabel(); Runnable changeIt=new Runnable(){ public void run(){ lblArm1.setIcon(icon[step]); } }; Thread t=new Thread(){ public void run(){ //休眠1秒钟 while(true){ try{ this.sleep(500); }catch(Exception ex){return;} SwingUtilities.invokeLater(changeIt); step++; if(step>1) step=0; } } }; public Arm1(){ icon[0]=new ImageIcon(Arm1.class.getResource("image/armLeft1.GIF")); icon[1]=new ImageIcon(Arm1.class.getResource("image/armLeft2.GIF")); setLayout(new FlowLayout(FlowLayout.RIGHT)); lblArm1.setIcon(icon[step]); add(lblArm1); t.start(); } public Dimension getPreferredSize(){ return new Dimension(50,50); } } class Arm2 extends JPanel{ ImageIcon[] icon=new ImageIcon[2]; int step=0; JLabel lblArm2=new JLabel(); Runnable changeIt=new Runnable(){ public void run(){ lblArm2.setIcon(icon[step]); } }; Thread t=new Thread(){ public void run(){ //休眠1秒钟 while(true){ try{ this.sleep(500); }catch(Exception ex){return;} SwingUtilities.invokeLater(changeIt); step++; if(step>1) step=0; } } }; public Arm2(){ icon[0]=new ImageIcon(Arm2.class.getResource("image/armRight1.GIF")); icon[1]=new ImageIcon(Arm2.class.getResource("image/armRight2.GIF")); setLayout(new FlowLayout(FlowLayout.RIGHT)); lblArm2.setIcon(icon[step]); add(lblArm2); t.start(); } public Dimension getPreferredSize(){ return new Dimension(50,50); } } class Leg extends JPanel{ ImageIcon[] icon=new ImageIcon[2]; int step=0; JLabel lblLeg=new JLabel(); Runnable changeIt=new Runnable(){ public void run(){ lblLeg.setIcon(icon[step]); } }; Thread t=new Thread(){ public void run(){ //休眠1秒钟 while(true){ try{ this.sleep(500); }catch(Exception ex){return;} SwingUtilities.invokeLater(changeIt); step++; if(step>1) step=0; } } }; public Leg(){ icon[0]=new ImageIcon(Leg.class.getResource("image/leg1.GIF")); icon[1]=new ImageIcon(Leg.class.getResource("image/leg2.GIF")); setLayout(new FlowLayout(FlowLayout.RIGHT)); lblLeg.setIcon(icon[step]); add(lblLeg); t.start(); } public Dimension getPreferredSize(){ return new Dimension(50,50); } } class Head extends JPanel{ ImageIcon[] icon=new ImageIcon[2]; int step=0; JLabel lblHead=new JLabel(); Runnable changeIt=new Runnable(){ public void run(){ lblHead.setIcon(icon[step]); } }; Thread t=new Thread(){ public void run(){ //休眠1秒钟 while(true){ try{ this.sleep(500); }catch(Exception ex){return;} SwingUtilities.invokeLater(changeIt); step++; if(step>1) step=0; } } }; public Head(){ icon[0]=new ImageIcon(Head.class.getResource("image/head1.GIF")); icon[1]=new ImageIcon(Head.class.getResource("image/head2.GIF")); setLayout(new FlowLayout(FlowLayout.RIGHT)); lblHead.setIcon(icon[step]); add(lblHead); t.start(); } public Dimension getPreferredSize(){ return new Dimension(50,50); } } } |
瞬间捕捉的图形如下:
从运行效果上看,因为眼睛、手、脚都没有使用同步机制,所以其运动是没有规律的。现在我们为它加上同步机制,要求按照先动眼睛,再动左手,再动右手,最后动脚的顺序循环播放,更改后的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | package ql.abs; import java.io.*; import java.text.*; import java.util.*; import java.net.*; import quanwen.*; //常用函数 import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.table.*; import javax.swing.border.*; import java.util.Iterator; import java.util.List; /** * 一个基于线程同步的机器人 */ public class ExpSync extends JFrame{ Object headLock = new Object(); Object arm1Lock = new Object(); Object arm2Lock = new Object(); Object legLock = new Object(); JPanel pnlTop=new JPanel(), pnlBottom=new JPanel(); JPanel pnlTopLeft=new JPanel(), pnlTopRight=new JPanel(), pnlTopCenter=new JPanel(); JPanel armLeft=new Arm1(), pnlTopLeft2=new JPanel(); JPanel armRight=new Arm2(), pnlTopRight2=new JPanel(); JPanel pnlBottomLeft=new JPanel(), leg=new Leg(), pnlBottomRight=new JPanel(); JPanel head=new Head(); Container c; public ExpSync(){ super("小机器人"); armLeft.setPreferredSize(new Dimension(50,50)); pnlTopLeft2.setPreferredSize(new Dimension(50,50)); pnlTopCenter.setBackground(Color.white); pnlTopCenter.setPreferredSize(new Dimension(50,50)); armRight.setPreferredSize(new Dimension(50,50)); pnlTopRight2.setPreferredSize(new Dimension(50,50)); pnlBottomLeft.setPreferredSize(new Dimension(50,50)); leg.setPreferredSize(new Dimension(50,50)); pnlBottomRight.setPreferredSize(new Dimension(50,50)); c=getContentPane(); c.setLayout(new GridLayout(3,3,0,0)); c.add(pnlBottomLeft); c.add(head); c.add(pnlBottomRight); c.add(armLeft); c.add(pnlTopCenter); c.add(armRight); c.add(pnlTopRight2); c.add(leg); c.add(pnlTopRight); setSize(167,160); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); show(); //每隔4秒为arm1线程解锁一次 Arm1Released ar=new Arm1Released(); ar.start(); } public static void main(String[] args){ ExpSync exp=new ExpSync(); } //为线程锁链中的左边胳膊解锁 class Arm1Released extends Thread{ public void run(){ while(true){ try{ this.sleep(4000); }catch(Exception ex){} //为arm1解锁 synchronized(arm1Lock) { arm1Lock.notify(); } System.out.println("--- Send arm1Lock notify..."); } } } //左边的胳膊 class Arm1 extends JPanel{ ImageIcon[] icon=new ImageIcon[2]; int step=0; JLabel lblArm1=new JLabel(); Runnable changeIt=new Runnable(){ public void run(){ lblArm1.setIcon(icon[step]); } }; Thread t=new Thread(){ public void run(){ //休眠1秒钟 while(true){ System.out.println("arm1 is locked"); //arm1等待head来解锁 synchronized (arm1Lock) { try { arm1Lock.wait(); } catch (InterruptedException ie) { System.out.println(ie.toString()); } } System.out.println("arm1 is released"); SwingUtilities.invokeLater(changeIt); try{ Thread.currentThread().sleep(1000); }catch(Exception ex){} //arm1为arm2解锁 synchronized(arm2Lock) { arm2Lock.notify(); } System.out.println("--- Send arm2Lock notify..."); step++; if(step>1) step=0; } } }; public Arm1(){ icon[0]=new ImageIcon(Arm1.class.getResource("image/armLeft1.GIF")); icon[1]=new ImageIcon(Arm1.class.getResource("image/armLeft2.GIF")); setLayout(new FlowLayout(FlowLayout.RIGHT)); lblArm1.setIcon(icon[step]); add(lblArm1); t.start(); } public Dimension getPreferredSize(){ return new Dimension(50,50); } } class Arm2 extends JPanel{ ImageIcon[] icon=new ImageIcon[2]; int step=0; JLabel lblArm2=new JLabel(); Runnable changeIt=new Runnable(){ public void run(){ lblArm2.setIcon(icon[step]); } }; Thread t=new Thread(){ public void run(){ //休眠1秒钟 while(true){ System.out.println("arm2 is locked"); //arm2等待arm1来解锁 synchronized (arm2Lock) { try { arm2Lock.wait(); } catch (InterruptedException ie) {} } System.out.println("arm2 is released"); SwingUtilities.invokeLater(changeIt); try{ Thread.currentThread().sleep(1000); }catch(Exception ex){} //arm2为leg解锁 synchronized(legLock) { legLock.notify(); } System.out.println("--- Send legLock notify..."); step++; if(step>1) step=0; } } }; public Arm2(){ icon[0]=new ImageIcon(Arm2.class.getResource("image/armRight1.GIF")); icon[1]=new ImageIcon(Arm2.class.getResource("image/armRight2.GIF")); setLayout(new FlowLayout(FlowLayout.RIGHT)); lblArm2.setIcon(icon[step]); add(lblArm2); t.start(); } public Dimension getPreferredSize(){ return new Dimension(50,50); } } class Leg extends JPanel{ ImageIcon[] icon=new ImageIcon[2]; int step=0; JLabel lblLeg=new JLabel(); Runnable changeIt=new Runnable(){ public void run(){ lblLeg.setIcon(icon[step]); } }; Thread t=new Thread(){ public void run(){ //休眠1秒钟 while(true){ System.out.println("leg is locked"); //leg等待arm2来解锁 synchronized (legLock) { try { legLock.wait(); } catch (InterruptedException ie) {} } System.out.println("leg is released"); SwingUtilities.invokeLater(changeIt); try{ Thread.currentThread().sleep(1000); }catch(Exception ex){} //leg为head解锁 synchronized(headLock) { headLock.notify(); } System.out.println("--- Send headLock notify..."); step++; if(step>1) step=0; } } }; public Leg(){ icon[0]=new ImageIcon(Leg.class.getResource("image/leg1.GIF")); icon[1]=new ImageIcon(Leg.class.getResource("image/leg2.GIF")); setLayout(new FlowLayout(FlowLayout.RIGHT)); lblLeg.setIcon(icon[step]); add(lblLeg); t.start(); } public Dimension getPreferredSize(){ return new Dimension(50,50); } } class Head extends JPanel{ ImageIcon[] icon=new ImageIcon[2]; int step=0; JLabel lblHead=new JLabel(); Runnable changeIt=new Runnable(){ public void run(){ lblHead.setIcon(icon[step]); } }; Thread t=new Thread(){ public void run(){ while(true){ System.out.println("head is locked"); //head等待leg来解锁 synchronized (headLock) { try { headLock.wait(); } catch (InterruptedException ie) {} } System.out.println("head is released"); SwingUtilities.invokeLater(changeIt); try{ Thread.currentThread().sleep(1000); }catch(Exception ex){} //head为arm1解锁 synchronized(arm1Lock) { arm1Lock.notify(); } System.out.println("--- Send arm1Lock notify..."); step++; if(step>1) step=0; } } }; public Head(){ icon[0]=new ImageIcon(Head.class.getResource("image/head1.GIF")); icon[1]=new ImageIcon(Head.class.getResource("image/head2.GIF")); setLayout(new FlowLayout(FlowLayout.RIGHT)); lblHead.setIcon(icon[step]); add(lblHead); t.start(); } public Dimension getPreferredSize(){ return new Dimension(50,50); } } } |
从代码可以看出。左胳膊Arm1先是阻塞自己,等待Arm1Released线程或Head线程释放自己,当自己被释放以后,则释放Arm2。右胳膊Arm2先是阻塞自己,等待Arm1线程释放自己,当自己被释放以后,则释放leg。腿Leg先是阻塞自己,等待Arm2线程释放自己,当自己被释放以后,则释放head。头Head先是阻塞自己,等待Leg线程释放自己,当自己被释放以后,则释放Arm1。
因为左胳膊,右胳膊,腿,头三个线程之间不存在先后执行的问题,所以你在实际运行中会看到以下的顺序:
arm1 is locked
arm2 is locked
leg is locked
head is locked
--- Send arm1Lock notify --- 1、 Arm1Released线程释放Arm1
arm1 is released 2、 arm1被Arm1Released释放
--- Send arm2Lock notify... 3、 arm1释放arm2
arm1 is locked
arm2 is released 4、 arm2被arm1释放
--- Send legLock notify... 5、 arm2释放leg
arm2 is locked
leg is released 6、 leg被arm2释放
--- Send headLock notify... 7、 leg释放head
leg is locked
head is released 8、head被leg释放
--- Send arm1Lock notify... 9、head释放arm1
head is locked
arm1 is released 10、arm1被head释放
--- Send arm1Lock notify --- 11、Arm1Released线程释放Arm1
--- Send arm2Lock notify... 12、arm1释放arm2
arm1 is locked
arm2 is released 13、……
--- Send legLock notify...
arm2 is locked
程序开始运行构造函数中几个机器人的部件都被初始化,四个部件均被自己阻塞,如果Arm1Released不运行,则四个线程都无法继续工作。即无法执行释放别人的操作而造成整个程序死锁。