多线程同步:彻底明白Java的多线程-实现多线程及线程的同步来源: 发布时间:星期三, 2008年12月17日 浏览:95次 评论:0
. 实现多线程
1. 虚假多线程
例1:
public TestThread { i=0, j=0; public void go( flag){ while(true){ try{ Thread.sleep(100); } catch(InterruptedException e){ .out.prln("Interrupted"); } (flag0) i; .out.prln("i=" + i); } { j; .out.prln("j=" + j); } } } public void (String args){ TestThread.go(0); TestThread.go(1); }}
上面运行结果为:
i=1
i=2
i=3
结果将直打印出I值我们意图是当在while循环中sleep时另个线程就将起动打印出j值但结果却并不是这样有关sleep为什么不会出现我们预想结果在下面将讲到
2. 实现多线程
通过继承 Thread或实现Runnable接口我们可以实现多线程
2.1 通过继承 Thread实现多线程
Thread中有两个最重要run和start
1) run必须进行覆写把要在多个线程中并行处理代码放到这个中
2) 虽然run实现了多个线程并行处理但我们不能直接run而是通过start来run在start时候start会首先进行和多线程相关化(这也是为什么不能直接run原因)然后再run
例2:
public TestThread extends Thread{ private threadCount = 0; private threadNum = threadCount; private i = 5; public void run{ while(true){ try{ Thread.sleep(100); } catch(InterruptedException e){ .out.prln("Interrupted"); } .out.prln("Thread " + threadNum + " = " + i); (--i0) ; } } public void (String args){ for( i=0; i<5; i) TestThread.start; }}
运行结果为:
Thread 1 = 5
Thread 2 = 5
Thread 3 = 5
Thread 4 = 5
Thread 5 = 5
Thread 1 = 4
Thread 2 = 4
Thread 3 = 4
Thread 4 = 4
Thread 1 = 3
Thread 2 = 3
Thread 5 = 4
Thread 3 = 3
Thread 4 = 3
Thread 1 = 2
Thread 2 = 2
Thread 5 = 3
Thread 3 = 2
Thread 4 = 2
Thread 1 = 1
Thread 2 = 1
Thread 5 = 2
Thread 3 = 1
Thread 4 = 1
Thread 5 = 1
从结果可见例2能实现多线程并行处理
**:在上面例子中我们只用产生Thread对象并没有用reference来记录所产生Thread对象根据垃圾回收机制当个对象没有被reference引用时它将被回收但是垃圾回收机制对Thread对象“不成立”每个Thread都会进行注册动作所以即使我们在产生Thread对象时没有指定个reference指向这个对象实际上也会在某个地方有个指向该对象reference所以垃圾回收器无法回收它们
3) 通过Thread子类产生线程对象是区别对象线程
TestSynchronized extends Thread{ public TestSynchronized(String name){ super(name); } public synchronized void prt{ for( i=10; i<20; i){ .out.prln(Thread.currentThread.getName + " : " + i); try{ Thread.sleep(100); } catch(InterruptedException e){ .out.prln("Interrupted"); } } } public synchronized void run{ for( i=0; i<3; i){ .out.prln(Thread.currentThread.getName + " : " + i); try{ Thread.sleep(100); } catch(InterruptedException e){ .out.prln("Interrupted"); } } }}public TestThread{ public void (String args){ TestSynchronized t1 = TestSynchronized("t1"); TestSynchronized t2 = TestSynchronized("t2"); t1.start; t1.start; //(1) //t2.start; (2) }}
运行结果为:
t1 : 0
t1 : 1
t1 : 2
t1 : 0
t1 : 1
t1 : 2
由于是同个对象启动区别线程所以run实现了synchronized如果去掉(2)注释把代码(1)注释掉结果将变为:
t1 : 0
t2 : 0
t1 : 1
t2 : 1
t1 : 2
t2 : 2
由于t1和t2是两个对象所以它们所启动线程可同时访问run
2.2 通过实现Runnable接口实现多线程
如果有个类它已继承了某个类又想实现多线程那就可以通过实现Runnable接口来实现
1) Runnable接口只有个run
2) 把个实现了Runnable接口对象作为参数产生个Thread对象再Thread对象start就可执行并行操作如果在产生个Thread对象时以个Runnable接口实现类对象作为参数那么在start时start会Runnable接口实现类中run
例3.1:
public TestThread implements Runnable{ private threadCount = 0; private threadNum = threadCount; private i = 5; public void run{ while(true){ try{ Thread.sleep(100); } catch(InterruptedException e){ .out.prln("Interrupted"); } .out.prln("Thread " + threadNum + " = " + i); (--i0) ; } } public void (String args){ for( i=0; i<5; i) Thread( TestThread).start; //(1) }}
运行结果为:
Thread 1 = 5
Thread 2 = 5
Thread 3 = 5
Thread 4 = 5
Thread 5 = 5
Thread 1 = 4
Thread 2 = 4
Thread 3 = 4
Thread 4 = 4
Thread 4 = 3
Thread 5 = 4
Thread 1 = 3
Thread 2 = 3
Thread 3 = 3
Thread 4 = 2
Thread 5 = 3
Thread 1 = 2
Thread 2 = 2
Thread 3 = 2
Thread 4 = 1
Thread 5 = 2
Thread 1 = 1
Thread 2 = 1
Thread 3 = 1
Thread 5 = 1
例3是对例2修改它通过实现Runnable接口来实现并行处理代码(1)处可见要TestThread中并行操作部分要把个TestThread对象作为参数来产生Thread对象再Thread对象start
3) 同个实现了Runnable接口对象作为参数产生所有Thread对象是同对象下线程
例3.2:
package mypackage1;public TestThread implements Runnable{ public synchronized void run{ for( i=0; i<5; i){ .out.prln(Thread.currentThread.getName + " : " + i); try{ Thread.sleep(100); } catch(InterruptedException e){ .out.prln("Interrupted"); } } } public void (String args){ TestThread testThread = TestThread; for( i=0; i<5; i) // Thread(testThread, "t" + i).start; (1) Thread( TestThread, "t" + i).start; (2) }}
运行结果为:
t0 : 0
t1 : 0
t2 : 0
t3 : 0
t4 : 0
t0 : 1
t1 : 1
t2 : 1
t3 : 1
t4 : 1
t0 : 2
t1 : 2
t2 : 2
t3 : 2
t4 : 2
t0 : 3
t1 : 3
t2 : 3
t3 : 3
t4 : 3
t0 : 4
t1 : 4
t2 : 4
t3 : 4
t4 : 4
由于代码(2)每次都是用个新TestThread对象来产生Thread对象所以产生出来Thread对象是区别对象线程所以所有Thread对象都可同时访问run如果注释掉代码(2)并去掉代码(1)注释结果为:
t0 : 0
t0 : 1
t0 : 2
t0 : 3
t0 : 4
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 4
t2 : 0
t2 : 1
t2 : 2
t2 : 3
t2 : 4
t3 : 0
t3 : 1
t3 : 2
t3 : 3
t3 : 4
t4 : 0
t4 : 1
t4 : 2
t4 : 3
t4 : 4
由于代码(1)中每次都是用同个TestThread对象来产生Thread对象所以产生出来Thread对象是同个对象线程所以实现run同步
2. 共享资源同步
1. 同步必要性
例4:
Seq{ private number = 0; private Seq seq = Seq; private Seq {} public Seq getInstance{ seq; } public get{ number; //(a) number; //(b) }}public TestThread{ public void (String args){ Seq.getInstance.get; //(1) Seq.getInstance.get; //(2) }}
上面是个取得序列号单例模式例子但get时可能会产生两个相同序列号:
当代码(1)和(2)都试图get取得个唯序列当代码(1)执行完代码(a)正要执行代码(b)时它被中断了并开始执行代码(2)旦当代码(2)执行完(a)而代码(1)还未执行代码(b)那么代码(1)和代码(2)就将得到相同值
2. 通过synchronized实现资源同步
2.1 锁标志
2.1.1 每个对象都有个标志锁当对象个线程访问了对象某个synchronized数据(包括)时这个对象就将被“上锁”所以被声明为synchronized数据(包括)都不能被(当前线程取走了对象“锁标志”)只有当前线程访问完它要访问synchronized数据释放“锁标志”后同个对象其它线程才能访问synchronized数据
2.1.2 每个也有个“锁标志”对于synchronized 数据(包括)可以在整个下进行锁定避免数据同时访问
例5:
Seq{ private number = 0; private Seq seq = Seq; private Seq {} public Seq getInstance{ seq; } public synchronized get{ //(1) number; number; }}
例5在例4基础上把get声明为synchronized那么在同个对象中就只能有个线程get所以每个线程取得number值就是唯了
例6:
Seq{ private number = 0; private Seq seq = null; private Seq {}synchronized public Seq getInstance{ //(1) (seqnull) seq = Seq; seq; } public synchronized get{ number; number; }}
例6把getInstance声明为synchronized那样就保证通过getInstance得到是同个seq对象
2.2 non-synchronized数据只能在同个对象纯种实现同步访问区别对象线程仍可同时访问
例7:
TestSynchronized implements Runnable{ public synchronized void run{ //(1) for( i=0; i<10; i){.out.prln(Thread.currentThread.getName + " : " + i);/*(2)*/ try{ Thread.sleep(100); } catch(InterruptedException e){ .out.prln("Interrupted"); } } }}public TestThread{ public void (String args){ TestSynchronized r1 = TestSynchronized; TestSynchronized r2 = TestSynchronized; Thread t1 = Thread(r1, "t1"); Thread t2 = Thread(r2, "t2"); //(3) //Thread t2 = Thread(r1, "t2"); (4) t1.start; t2.start; }}
运行结果为:
t1 : 0
t2 : 0
t1 : 1
t2 : 1
t1 : 2
t2 : 2
t1 : 3
t2 : 3
t1 : 4
t2 : 4
t1 : 5
t2 : 5
t1 : 6
t2 : 6
t1 : 7
t2 : 7
t1 : 8
t2 : 8
t1 : 9
t2 : 9
虽然我们在代码(1)中把run声明为synchronized但由于t1、t2是两个对象(r1、r2)线程而run是non-synchronized数据所以仍可被同时访问(代码(2)中sleep由于在暂停时不会释放“标志锁”线程中循环很难被中断去执行另个线程所以代码(2)只是为了显示结果)
如果把例7中代码(3)注释掉并去年代码(4)注释运行结果将为:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 4
t1 : 5
t1 : 6
t1 : 7
t1 : 8
t1 : 9
t2 : 0
t2 : 1
t2 : 2
t2 : 3
t2 : 4
t2 : 5
t2 : 6
t2 : 7
t2 : 8
t2 : 9
修改后t1、t2是同个对象(r1)线程所以只有当个线程(t1或t2中个)执行run另个线程才能执行
2.3 对象“锁标志”和“锁标志”是相互独立
例8:
TestSynchronized extends Thread{ public TestSynchronized(String name){ super(name); } public synchronized void prt{ for( i=10; i<20; i){ .out.prln(Thread.currentThread.getName + " : " + i); try{ Thread.sleep(100); } catch(InterruptedException e){ .out.prln("Interrupted"); } } } public synchronized void run{ for( i=0; i<10; i){ .out.prln(Thread.currentThread.getName + " : " + i); try{ Thread.sleep(100); } catch(InterruptedException e){ .out.prln("Interrupted"); } } }}public TestThread{ public void (String args){ TestSynchronized t1 = TestSynchronized("t1"); TestSynchronized t2 = TestSynchronized("t2"); t1.start; t1.prt; //(1) t2.prt; //(2) }}
运行结果为:
: 10
t1 : 0
: 11
t1 : 1
: 12
t1 : 2
: 13
t1 : 3
: 14
t1 : 4
: 15
t1 : 5
: 16
t1 : 6
: 17
t1 : 7
: 18
t1 : 8
: 19
t1 : 9
: 10
: 11
: 12
: 13
: 14
: 15
: 16
: 17
: 18
: 19
在代码(1)中虽然是通过对象t1来prt但由于prt是静态所以它时不用经过任何对象它所属线程为线程
由于run取走是对象锁而prt取走是锁所以同个线程t1(由上面可知实际上是区别线程)run且还没完成run时它就能prt但prt只能被个线程如代码(1)和代码(2)即使是两个区别对象也不能同时prt
3. 同步优化
1) synchronized block
语法为:synchronized(reference){ do this }
reference用来指定“以某个对象锁标志”对“大括号内代码”实施同步控制
例9:
TestSynchronized implements Runnable{ j = 0; public synchronized void run{for( i=0; i<5; i){ //(1) .out.prln(Thread.currentThread.getName + " : " + j); try{ Thread.sleep(100); } catch(InterruptedException e){ .out.prln("Interrupted"); } } } } public TestThread{ public void (String args){ TestSynchronized r1 = TestSynchronized; TestSynchronized r2 = TestSynchronized; Thread t1 = Thread(r1, "t1"); Thread t2 = Thread(r1, "t2"); t1.start; t2.start; }}
运行结果为:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 4
t2 : 5
t2 : 6
t2 : 7
t2 : 8
t2 : 9
上面代码run实现了同步使每次打印出来j总是不相同但实际上在整个run中我们只关心j同步而其余代码同步和否我们是不关心所以可以对它进行以下修改:
TestSynchronized implements Runnable{ j = 0; public void run{ for( i=0; i<5; i){ //(1) synchronized(this){ .out.prln(Thread.currentThread.getName + " : " + j); } try{ Thread.sleep(100); } catch(InterruptedException e){ .out.prln("Interrupted"); } } }}public TestThread{ public void (String args){ TestSynchronized r1 = TestSynchronized; TestSynchronized r2 = TestSynchronized; Thread t1 = Thread(r1, "t1"); Thread t2 = Thread(r1, "t2"); t1.start; t2.start; }}
运行结果为:
t1 : 0
t2 : 1
t1 : 2
t2 : 3
t1 : 4
t2 : 5
t1 : 6
t2 : 7
t1 : 8
t2 : 9
由于进行同步范围缩小了所以效率将提高同时代码(1)指出当对大括号内prln语句进行同步控制时会取走当前对象“锁标志”即对当前对象“上锁”不让当前对象下其它线程执行当前对象其它synchronized数据
0
相关文章读者评论发表评论 |
|