Android多线程全新讲解Word文件下载.docx
《Android多线程全新讲解Word文件下载.docx》由会员分享,可在线阅读,更多相关《Android多线程全新讲解Word文件下载.docx(50页珍藏版)》请在冰豆网上搜索。
下面来实现一个简单的定时器,功能如下,每隔2秒执行一次,之后隔4秒执行一次,然后又隔2秒,就这样轮循下去.具体用法可以查看API里面有详细介绍.
[java]
viewplaincopy
1.public
static
void
main(String[]
args)
{
2.
new
Timer().schedule(new
MyTimerTask(),
2000);
3.
try
4.
while
(true)
5.
System.out.println(new
Date().getSeconds());
6.
Thread.sleep(1000);
7.
}
8.}
catch
(InterruptedException
e)
9.
e.printStackTrace();
10.
11.
12.
13.class
MyTimerTask
extends
TimerTask
14.
int
count
=
0;
15.
@Override
16.
public
run()
17.
(count
+
1)
%
2;
//count=0或1
18.
System.out.println("
boming"
);
19.
Timer
timer
Timer();
20.
timer.schedule(new
2000
(2000)
*
count);
21.
当两个线程去同时操作一个字符串,那么可能会出现线程安全问题.这样的情况可以用银行转帐来解释.
下面的代码就会出现问题,
2.final
Outputer
outputer
Outputer();
3.new
Thread()
4.@Override
5.public
6.while
7.try
8.Thread.sleep(100);
9.}
10.e.printStackTrace();
11.}
12.outputer.print("
zhangsan"
13.}
14.}
15.}.start();
16.new
17.@Override
18.public
19.while
20.try
21.Thread.sleep(100);
22.}
23.e.printStackTrace();
24.}
25.outputer.print("
zhangxiaoxiang"
26.}
27.}
28.}.start();
29.}
30.}
31.class
32.public
print(String
name)
33.for
(int
i
<
name.length();
i++)
34.System.out.print(name.charAt(i));
35.}
36.System.out.println();
//
打印完字符串换行
37.}
38.}
我们使用两个线程去调用print(Stringname)方法,当第一个方法还没有执行完毕,第二个方法来执行,那么打印出来的name就会出现为问题.如下图所示,
现在我们要实现的是,只有当第一个线程执行完毕后,第二个线程才能执行print(Stringname)方法,这就必须互斥或者说同步.
我们知道实现同步可以使用同步代码块或者同步方法,想到同步(Synchronized)那么自然而然就想到同步监视器.
这是两个很重要的概念.
现在我们来改造上面Outputer的print(Stringname)方法.
2.//synchronized()里面的参数就是同步监视器
3.//然而这里使用name作为同步监视器是不行的,
4.//因为要实现原子性(互斥)必须要使用同一个监视器对象
5.//当第一个线程来执行该代码块,name对象是一个String对象
6.//当第二个线程来执行,name对象又是另一个String对象,
7.//这样就不能实现同步
8.synchronized
(name)
9.for
10.System.out.print(name.charAt(i));
12.System.out.println();
执行结果如下所示:
我们可以通过this关键字作为同步监视器,因为从上面定义两个线程的代码来看,我们只new了一次Outputer对象,所以this代表同一个对象.
现在来通过同步方法来实现同步,
1.//同步方法也同样也有同步监视器,它是this
2.public
synchronized
print2(String
for
System.out.print(name.charAt(i));
System.out.println();
7.}
把第二个线程改成使用print2(Stringname)方法.这样的话就需要print2和print这两个方法互斥.这个怎么理解呢?
上面我们是对print()这个一个方法进行互斥,现在呢?
需要对两个方法进行互斥.
我们可以这样比喻(对一个方法进行互斥):
假设一个茅坑(print(Stringname)),上面有一把锁(this对象),现在一个人(Thread)来上厕所,它把钥匙放进了口袋,第二个人(Thread2)来上厕所,因为没有钥匙,必须要等第一个人出来,把钥匙放上去,第二个人才能拿着钥匙进去.这是对一个方法进行同步,
(对两个方法或者更多进行同步)),现在有多个茅坑(print(Stringname),print2(Stringname)),只有一个钥匙(同步监视器),那么当一个人(Thread)进去后,拿了那仅有的一个钥匙,就算其他人(Thread)想进入的没有人占的茅坑也不行,因为没有钥匙.
这样的话,打印name的时候就不会出现问题.
现在还有一种情况:
1.//静态的同步方法同样也有同步监视器,它是class
print3(String
3.for
4.System.out.print(name.charAt(i));
5.}
6.System.out.println();
这样的话要想互斥就必须把同步监视器改成Outputer.class了,在内存中只有一份.
线程之间的同步通信
通过一道面试提来解释.
子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。
8.这样的话要想互斥就必须把同步监视器改成Outputer.class了,在内存中只有一份.
10.线程之间的同步通信
11.通过一道面试提来解释.
12.子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。
13.public
14.new
Thread(new
Runnable()
15.@Override
16.public
17.for
k
1;
50;
k++)
18.for
10;
19.System.out.println("
sub
thread
sequence
"
20.+
loop
of
k);
21.}
23.}
24.}).start();
25.for
26.for
100;
27.System.out
28..println("
main
31.}
1.
这样主要的程序逻辑是实现了,但是执行的次序乱来,子线程执行10次不应该别打断,主线程执行100次也不应该被打断.
所以我们自然就想到了同步,只需要把子循环使用同步代码块,但是用什么作为同步监视器呢?
this显然不行的.当然该类的字节码class是可以的,但是这样有2个问题,
第一,虽然实现了同步,但是,不是子线程一次,主线程一次,所以在子/主(线程)次序上还是乱了.
第二,使用class作为同步监视器不好,如果程序逻辑很复杂,需要多组需要互斥,使用class作为同步监视器,那么就成了一组了.所以这也不好.(关于多组互斥可以查看博客
经验:
要用到共同数据(包括同步锁)或共同算法的若干个方法,应该归在同一个类上,这种设计体现了高内聚和程序的健壮性.
比如:
据此,我们可以这样设计
class
Business{
publicsynchronizedvoid
sub(int
k){
i=1;
i<
=10;
i++){
subthreadsequence"
+i+"
loopof"
+k);
}
main(int
=100;
mainthreadsequence"
这样就把相关的方法写到一个类里面了.但是这里还是没有解决通信问题.最终代码如下:
publicstaticvoid
main(String[]args){
final
Businessbusiness=new
Business();
Runnable(){
@Override
publicvoid
run(){
k=1;
k<
=50;
k++){
business.sub(k);
}).start();
business.main(k);
//默认子线程先执行
booleanisShouldSub
=true;
if(!
isShouldSub){//此处用while最好,因为可能出现假唤醒,//用while的话还会重新判断,这样程序更加严谨和健壮
{
this.wait();
//this表示同步监视器对象
(InterruptedExceptione){
//子线程做完了,把它置为false
isShouldSub
=false;
//并且唤醒主线程
this.notify();
if(isShouldSub){){//此处用while最好,因为可能出现假唤醒(API文档里有介绍),//用while的话还会重新判断,这样程序更加严谨和健壮
//主线程做完了,把它置为true
//并且唤醒子线程
下面通过一个简单的示例来描述线程之间非共享数据.
1.private
Random().nextInt();
8.
System.out.println(Thread.currentThread().getName()
put
value
to
A().get();
B().get();
13.
Thread.sleep(10);
20.}
21.//模块A
22.static
A
23.public
get()
24.
from
+Thread.currentThread().getName()
get
25.
27.//模块B
28.static
B
29.
30.
31.
32.}
现在我们需要这样的效果,假设线程0给i赋值为1,那么当线程0取的时候也是1,也就是说线程之间取各自放进去的值.而上面的程序达不到这样的要求.这就需要线程范围内的数据共享.
那么我们可以这样来实现,这也是线程范围内数据共享的原理.
定义一个Map集合key和value分别为Thread和Integer.
把给i赋值的代码替换为
k=new
map.put(Thread.currentThread(),k);
get()方法内的代码改为
Afrom"
+Thread.currentThread().getName()
getvalue"
map.get(Thread.currentThread()));
这样的话就实现了线程范围内的数据共享了,线程取得值是各自放进去的.
这有什么用呢?
比如事务,所谓事务的回滚和提交指的是在一个线程上的,如果是在不同的线程上,那么逻辑就乱了.这不是我们想要的,这样的话我们就可以通过线程范围内共享数据,也就是把连接绑定到该线程上,那么在该线程获取的连接是同一个连接.
下面通过ThreadLocal来实现这样的功能.
ThreadLocalTest