首先回顾一下JDK1.5之前的线程相关的知识:
1 线程的入门.
什么是线程,线程就是程序执行的线索,Java是面向对象的语言什么类来表示这样一个东西呢?Thread.
通过start()方法启动它,线程所要执行的任务放在run()方法里面,下面可以看一下run()方法里面的源码
创建线程的两种传统方式(注: Runnable类并不是一个线程,它只是线程一个执行单元):
打开Thread的构造方法,
然后可以跟进看到init()方法具体的实现.其中有一行代码就是对target(Runnable类型)的赋值,因为线程所执行的任务都在run()方法里面,那么在run()方法里面,target就不为null,然后就调用了Runnale的run()方法.因为我们重写了Runnable的run()方法,那么最终执行的就是我们所覆写的run()方法.具体代码如下:
如果我们同时实现了Thread的run()方法又同时覆盖了Runnable的run()方法.那么到底会执行哪个的run()方法呢?
根据Java的多态,肯定执行的是Thread的run()方法.因为我们覆写了Thread的run()方法,那么所执行的就是我们run()方法,而不是
2 传统的定时器:
定时器通过Timer这个类来描述,通过schedule()方法来调度,定时执行的任务通过TimerTask来定义.
下面来实现一个简单的定时器,功能如下,每隔2秒执行一次,之后隔4秒执行一次,然后又隔2秒,就这样轮循下去.具体用法可以查看API里面有详细介绍.
-
publicstaticvoidmain(String[]args){
-
newTimer().schedule(newMyTimerTask(),2000);
-
try{
-
while(true){
-
System.out.println(newDate().getSeconds());
-
Thread.sleep(1000);
-
}
-
}catch(InterruptedExceptione){
-
e.printStackTrace();
-
}
-
}
-
}
-
classMyTimerTaskextendsTimerTask{
-
staticintcount=0;
-
@Override
-
publicvoidrun(){
-
count=(count+1)%2;
-
System.out.println("boming");
-
Timertimer=newTimer();
-
timer.schedule(newMyTimerTask(),2000+(2000)*count);
-
}
3 线程之间的互斥和同步通信
当两个线程去同时操作一个字符串,那么可能会出现线程安全问题.这样的情况可以用银行转帐来解释.
下面的代码就会出现问题,
-
publicstaticvoidmain(String[]args){
-
finalOutputeroutputer=newOutputer();
-
newThread(){
-
@Override
-
publicvoidrun(){
-
while(true){
-
try{
-
Thread.sleep(100);
-
}catch(InterruptedExceptione){
-
e.printStackTrace();
-
}
-
outputer.print("zhangsan");
-
}
-
}
-
}.start();
-
newThread(){
-
@Override
-
publicvoidrun(){
-
while(true){
-
try{
-
Thread.sleep(100);
-
}catch(InterruptedExceptione){
-
e.printStackTrace();
-
}
-
outputer.print("zhangxiaoxiang");
-
}
-
}
-
}.start();
-
}
-
}
-
classOutputer{
-
publicvoidprint(Stringname){
-
for(inti=0;i<name.length();i++){
-
System.out.print(name.charAt(i));
-
}
-
System.out.println();
-
}
-
}
我们使用两个线程去调用print(String name)方法,当第一个方法还没有执行完毕,第二个方法来执行,那么打印出来的name就会出现为问题.如下图所示,
现在我们要实现的是,只有当第一个线程执行完毕后,第二个线程才能执行print(String name)方法,这就必须互斥或者说同步.
我们知道实现同步可以使用同步代码块或者同步方法,想到同步(Synchronized)那么自然而然就想到同步监视器.
这是两个很重要的概念.
现在我们来改造上面Outputer的print(String name)方法.
-
publicvoidprint(Stringname){
-
-
-
-
-
-
-
synchronized(name){
-
for(inti=0;i<name.length();i++){
-
System.out.print(name.charAt(i));
-
}
-
System.out.println();
-
}
-
}
执行结果如下所示:
我们可以通过this关键字作为同步监视器,因为从上面定义两个线程的代码来看,我们只new了一次Outputer对象,所以this代表同一个对象.
现在来通过同步方法来实现同步,
-
-
publicsynchronizedvoidprint2(Stringname){
-
for(inti=0;i<name.length();i++){
-
System.out.print(name.charAt(i));
-
}
-
System.out.println();
-
}
把第二个线程改成使用print2(String name)方法.这样的话就需要print2和print这两个方法互斥.这个怎么理解呢?
上面我们是对print()这个一个方法进行互斥,现在呢?需要对两个方法进行互斥.
我们可以这样比喻(对一个方法进行互斥):假设一个茅坑(print(String name)),上面有一把锁(this对象),现在一个人(Thread)来上厕所,它把钥匙放进了口袋,第二个人(Thread2)来上厕所,因为没有钥匙,必须要等第一个人出来,把钥匙放上去,第二个人才能拿着钥匙进去.这是对一个方法进行同步,
(对两个方法或者更多进行同步)),现在有多个茅坑(print(String name),print2(String name)),只有一个钥匙(同步监视器),那么当一个人(Thread)进去后,拿了那仅有的一个钥匙,就算其他人(Thread)想进入的没有人占的茅坑也不行,因为没有钥匙.
这样的话,打印name的时候就不会出现问题.
现在还有一种情况:
-
-
publicstaticsynchronizedvoidprint3(Stringname){
-
for(inti=0;i<name.length();i++){
-
System.out.print(name.charAt(i));
-
}
-
System.out.println();
-
}
这样的话要想互斥就必须把同步监视器改成Outputer.class了,在内存中只有一份.
线程之间的同步通信
通过一道面试提来解释.
子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。
-
-
publicstaticsynchronizedvoidprint3(Stringname){
-
for(inti=0;i<name.length();i++){
-
System.out.print(name.charAt(i));
-
}
-
System.out.println();
-
}
-
这样的话要想互斥就必须把同步监视器改成Outputer.class了,在内存中只有一份.
-
-
线程之间的同步通信
-
通过一道面试提来解释.
-
子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。
-
publicstaticvoidmain(String[]args){
-
newThread(newRunnable(){
-
@Override
-
publicvoidrun(){
-
for(intk=1;k<=50;k++){
-
for(inti=1;i<=10;i++){
-
System.out.println("subthreadsequence"+i
-
+"loopof"+k);
-
}
-
}
-
}
-
}).start();
-
for(intk=1;k<=50;k++){
-
for(inti=1;i<=100;i++){
-
System.out
-
.println("mainthreadsequence"+i+"loopof"+k);
-
}
-
}
-
}
这样主要的程序逻辑是实现了,但是执行的次序乱来,子线程执行10次不应该别打断,主线程执行100次也不应该被打断.
所以我们自然就想到了同步,只需要把子循环使用同步代码块,但是用什么作为同步监视器呢?this显然不行的.当然该类的字节码class是可以的,但是这样有2个问题,
第一,虽然实现了同步,但是,不是子线程一次,主线程一次,所以在子/主(线程)次序上还是乱了.
第二,使用class作为同步监视器不好,如果程序逻辑很复杂,需要多组需要互斥,使用class作为同步监视器,那么就成了一组了.所以这也不好.(关于多组互斥可以查看博客http://blog.csdn.net/johnny901114/article/details/7854666)
经验:要用到共同数据(包括同步锁)或共同算法的若干个方法,应该归在同一个类上,这种设计体现了高内聚和程序的健壮性.
比如:
据此,我们可以这样设计
classBusiness {
publicsynchronizedvoidsub(intk)
{
for(inti = 1; i <= 10; i++) {
System.out.println("sub thread sequence "+ i +" loop of
"+ k);
}
}
publicsynchronizedvoidmain(intk)
{
for(inti = 1; i <= 100; i++) {
System.out.println("main thread sequence "+ i +" loop of
"+ k);
}
}
}
这样就把相关的方法写到一个类里面了.但是这里还是没有解决通信问题. 最终代码如下:
publicstaticvoidmain(String[]
args) {
finalBusiness business =newBusiness();
newThread(newRunnable() {
@Override
publicvoidrun() {
for(intk = 1; k <= 50; k++) {
business.sub(k);
}
}
}).start();
for(intk = 1; k <= 50; k++) {
business.main(k);
}
}
}
classBusiness {
//默认子线程先执行
booleanisShouldSub=true;
publicsynchronizedvoidsub(intk)
{
if(!isShouldSub){//此处用while最好,因为可能出现假唤醒,//用while的话还会重新判断,这样程序更加严谨和健壮
try{
this.wait();//this表示同步监视器对象
}catch(InterruptedException e) {
e.printStackTrace();
}
}
for(inti = 1; i <= 10; i++) {
System.out.println("sub thread sequence "+ i +" loop
of "+ k);
}
//子线程做完了,把它置为false
isShouldSub=false;
//并且唤醒主线程
this.notify();
}
publicsynchronizedvoidmain(intk)
{
if(isShouldSub){){//此处用while最好,因为可能出现假唤醒(API文档里有介绍),//用while的话还会重新判断,这样程序更加严谨和健壮
try{
this.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
for(inti = 1; i <= 100; i++) {
System.out.println("main thread sequence "+ i +" loop of
"+ k);
}
//主线程做完了,把它置为true
isShouldSub=true;
//并且唤醒子线程
this.notify();
}
}
4,线程范围内共享数据.(ThreadLocal)
下面通过一个简单的示例来描述线程之间非共享数据.
-
privatestaticintk=0;
-
publicstaticvoidmain(String[]args){
-
for(inti=0;i<2;i++){
-
newThread(newRunnable(){
-
@Override
-
publicvoidrun(){
-
k=newRandom().nextInt();
-
System.out.println(Thread.currentThread().getName()
-
+"putvaluetoi"+k);
-
newA().get();
-
newB().get();
-
try{
-
Thread.sleep(10);
-
}catch(InterruptedExceptione){
-
e.printStackTrace();
-
}
-
}
-
}).start();
-
}
-
}
-
-
staticclassA{
-
publicvoidget(){
-
System.out.println("Afrom"+Thread.currentThread().getName()+"getvalue"+k);
-
}
-
}
-
-
staticclassB{
-
publicvoidget(){
-
System.out.println("Afrom"+Thread.currentThread().getName()+"getvalue"+k);
-
}
-
}
现在我们需要这样的效果,假设线程0给i赋值为1,那么当线程0取的时候也是1,也就是说线程之间取各自放进去的值.而上面的程序达不到这样的要求. 这就需要线程范围内的数据共享.
那么我们可以这样来实现,这也是线程范围内数据共享的原理.
定义一个Map集合key和value分别为Thread和Integer.
把给i赋值的代码替换为
intk =newRandom().nextInt();
map.put(Thread.currentThread(), k);
get()方法内的代码改为
System.out.println("A from "+ Thread.currentThread().getName()
+" get value "+map.get(Thread.currentThread()));
这样的话就实现了线程范围内的数据共享了,线程取得值是各自放进去的.
这有什么用呢?比如事务,所谓事务的回滚和提交指的是在一个线程上的,如果是在不同的线程上,那么逻辑就乱了.这不是我们想要的,这样的话我们就可以通过线程范围内共享数据,也就是把连接绑定到该线程上,那么在该线程获取的连接是同一个连接.
下面通过ThreadLocal来实现这样的功能.
-
publicclassThreadLocalTest{
-
publicstaticvoidmain(String[]args){
-
for(inti=0;i<2;i++){
-
newThread(newRunnable(){
-
@Override
-
publicvoidrun(){
-
intk=newRandom().nextInt();
-
ThreadShareData.getThreadShareData().setAge(k);
-
ThreadShareData.getThreadShareData().setName("name"+k);
-
-
System.out.println(Thread.currentThread().getName()
-
+"putvaluetoi"+k);
-
newA().get();
-
newB().get();
-
try{
-
Thread.sleep(10);
-
}catch(InterruptedExceptione){
-
e.printStackTrace();
-
}
-
}
-
}).start();
-
}
-
}
-
-
-
staticclassA{
-
publicvoidget(){
-
ThreadShareDatadata=ThreadShareData.getThreadShareData();
-
System.out.println("Afrom"+Thread.currentThread().getName()
-
+"getvalue"+data.getName()+"--"+data.getAge());
-
}
-
}
-
-
-
staticclassB{
-
publicvoidget(){
-
ThreadShareDatadata=ThreadShareData.getThreadShareData();
-
System.out.println("Bfrom"+Thread.currentThread().getName()
-
+"getvalue"+data.getName()+"--"+data.getAge());
-
}
-
}
-
}
-
-
classThreadShareData{
-
privatestaticThreadLocal<ThreadShareData>local=newThreadLocal<ThreadShareData>();
-
privateThreadShareData(){
-
}
-
publicstaticThreadShareDatagetThreadShareData(){
-
ThreadShareDatadata=local.get();
-
if(data==null){
-
data=newThreadShareData();
-
local.set(data);
-
}
-
returndata;
-
}
-
privateStringname;
-
privateintage;
-
publicStringgetName(){
-
returnname;
-
}
-
publicvoidsetName(Stringname){
-
this.name=name;
-
}
-
publicintgetAge(){
-
returnage;
-
}
-
publicvoidsetAge(intage){
-
this.age=age;
-
}
-
}
上面的例子,对于线程范围内共享对象是一个比较优雅的设计方案,ThreadShareData有name和age两个属性,这个类的实例是与每个线程相关的.那么这个设计就交给这个类自己吧,其他用户在任意线程调用我这个类的方法,自然而然就是与线程相关的实例.因为里面我们封装了一个ThreadLocal对象.
那么我们是否考虑到如果成千上万的线程来访问,那么是不是可能会导致内存溢出呢?
其实当一个线程死亡,那么系统会把该线程在ThreadLocal产生的数据清除掉,
5,多个线程访问共享对象和数据的方式:
1>如果每个线程执行的代码相同,额可以使用相同的Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统可以这么来实现
-
publicstaticvoidmain(String[]args){
-
MyRunnablemyRunnable=newMyRunnable();
-
newThread(myRunnable).start();
-
newThread(myRunnable).start();
-
newThread(myRunnable).start();
-
newThread(myRunnable).start();
-
}
-
staticclassMyRunnableimplementsRunnable{
-
intcount=100;
-
@Override
-
publicvoidrun(){
-
synchronized(this){
-
while(true){
-
if(count>0){
-
try{
-
-
Thread.sleep(10);
-
}catch(InterruptedExceptione){
-
e.printStackTrace();
-
}
-
count--;
-
}else{
-
break;
-
}
-
System.out.println(count);
-
}
-
}
-
}
-
}
2>如果每个线程执行的代码不同,比如一个线程对一个整形执行加操作,另一个线程对该整形进行减操作.
这时候需要用不同的Runnable对象,有如下三种方式来实现这些Runnable对象的数据共享.
①将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象,每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行各个操作的互斥和通信.
-
publicstaticvoidmain(String[]args){
-
ShareDatashareData=newShareData();
-
newThread(newMyRunnable(shareData)).start();
-
newThread(newMyRunnable2(shareData)).start();
-
}
-
-
staticclassMyRunnableimplementsRunnable{
-
privateShareDatashareData;
-
-
publicMyRunnable(ShareDatashareData){
-
this.shareData=shareData;
-
}
-
-
@Override
-
publicvoidrun(){
-
shareData.increase();
-
}
-
}
-
staticclassMyRunnable2implementsRunnable{
-
privateShareDatashareData;
-
-
publicMyRunnable2(ShareDatashareData){
-
this.shareData=shareData;
-
}
-
-
@Override
-
publicvoidrun(){
-
shareData.decrease();
-
}
-
}
-
staticclassShareData{
-
intcount=100;
-
-
publicvoidincrease(){
-
count++;
-
}
-
-
publicvoiddecrease(){
-
count--;
-
}
-
}
②将这些Runnable对象作为某一类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法.
-
staticShareDatashareData=newShareData();
-
publicstaticvoidmain(String[]args){
-
-
newThread(newRunnable(){
-
@Override
-
publicvoidrun(){
-
shareData.decrease();
-
}
-
}).start();
-
newThread(newRunnable(){
-
@Override
-
publicvoidrun(){
-
shareData.increase();
-
}
-
}).start();
-
}
-
staticclassShareData{
-
intcount=100;
-
-
publicvoidincrease(){
-
count++;
-
}
-
-
publicvoiddecrease(){
-
count--;
-
}
-
}
③上面两种方式的组合:将共享数据封装在另一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或者方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或者局部内部类.
-
publicclassThreadTest1
-
{
-
privateintj;
-
publicstaticvoidmain(Stringargs[]){
-
ThreadTest1tt=newThreadTest1();
-
Incinc=tt.newInc();
-
Decdec=tt.newDec();
-
for(inti=0;i<2;i++){
-
Threadt=newThread(inc);
-
t.start();
-
t=newThread(dec);
-
t.start();
-
}
-
}
-
privatesynchronizedvoidinc(){
-
j++;
-
System.out.println(Thread.currentThread().getName()+"-inc:"+j);
-
}
-
privatesynchronizedvoiddec(){
-
j--;
-
System.out.println(Thread.currentThread().getName()+"-dec:"+j);
-
}
-
classIncimplementsRunnable{
-
publicvoidrun(){
-
for(inti=0;i<100;i++){
-
inc();
-
}
-
}
-
}
-
classDecimplementsRunnable{
-
publicvoidrun(){
-
for(inti=0;i<100;i++){
-
dec();
-
}
-
}
-
}
-
}
总之,要同步互斥的几段代码最好分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现他们之间的同步互斥和通信.
转载请注明出处:http://blog.csdn.net/johnny901114/article/details/8695668
分享到:
相关推荐
随着现代处理器的生产工艺从提升...《Java多线程编程实战指南(核心篇)》适合有一定Java语言基础的读者作为入门多线程编程之用,也适合有一定多线程编程经验的读者作为重新梳理知识结构以提升认知层次和参考之用。
本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。
第2篇为Java语言高级语法,包括类、对象、方法、继承、多态、修饰符、接口、抽象类、内部类、Java异常处理和多线程编程。第3篇为Java语言编程进阶,包括Java编程常用知识、Java文件编程、Java文件I/O编程、Java TCP...
第2篇为Java语言高级语法,包括类、对象、方法、继承、多态、修饰符、接口、抽象类、内部类、Java异常处理和多线程编程。第3篇为Java语言编程进阶,包括Java编程常用知识、Java文件编程、Java文件I/O编程、Java TCP...
对Java语言的每个语法都提供了一个或多个例程讲解 大量使用流程图表示程序的执行过程,使用结构图表示程序的内部状态 每章最后都给出了典型的练习题,让读者及时练习,巩固提高,并提供了参考答案 目录 第1篇 ...
java版飞机大战源码 concurrency-practice Java并发学习 这是Java并发研究的书籍和示例集合,还有源码姊妹篇源码分析集合。...《Java多线程编程实战指南》-第一版-设计模式篇 - 黄文海 另一种角度看并发
对Java语言的每个语法都提供了一个或多个例程讲解 大量使用流程图表示程序的执行过程,使用结构图表示程序的内部状态 每章最后都给出了典型的练习题,让读者及时练习,巩固提高,并提供了参考答案 目录 第1篇 ...
多线程,介绍Java如何对多线程提供支持,以及如何使用Java编写多线程应用;网络编程,介绍如何通过HTTP协议访问Web应用,如何通过Socket编程实现C/S结构的应用程序;GUI,介绍如何编写图形用户界面。最后给出了3个...
java8 集合源码分析 java-demos other collect github ...多线程 mock junit 监控 prometheus java代码质量分析工具 java动态追踪 java log guava编程 mybatis mybatis plus jps dsl code review/sonar
本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。
其中第1课到15课是编程基础篇,依次介绍Java与运行环境、Java开发利器、Java数据类型、变量和常量、Java运算符、表达式和字符串、Java基本结构和条件控制语句、Java循环语句和跳转语句、数组、Java的面向对象编程、...
本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。
本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。
本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。
本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。
本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。
│ 164个完整Java代码.zip │ J2EE综合--Struts常见错误的全面汇总.txt │ java程序员面试资料.zip │ JAVA笔试题(上海释锐).pdf │ MIME简介.txt │ SCJP试题详解.pdf │ SQL面试题_心灵深处.htm │ Struts+...
│ 164个完整Java代码.zip │ J2EE综合--Struts常见错误的全面汇总.txt │ java程序员面试资料.zip │ JAVA笔试题(上海释锐).pdf │ MIME简介.txt │ SCJP试题详解.pdf │ SQL面试题_心灵深处.htm │ Struts+...
│ 164个完整Java代码.zip │ J2EE综合--Struts常见错误的全面汇总.txt │ java程序员面试资料.zip │ JAVA笔试题(上海释锐).pdf │ MIME简介.txt │ SCJP试题详解.pdf │ SQL面试题_心灵深处.htm │ Struts+...
本专栏主要为Java程序设计(基础)实验报告和Java程序设计(进阶)...进阶篇有反射、泛型、注解、网络编程、多线程、序列化、数据库、Servlet、JSP、XML解析、单例模式与枚举。本专栏主要为Java入门者提供实验参考。