A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 奋斗的青春 金牌黑马   /  2012-12-17 17:27  /  1974 人查看  /  6 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

线程Java的一大特色,从语言上直接支持线程线程对于进程来讲的优势在于创建的代价很小,上下文切换迅速,当然其他的优势还有很多,缺点也是有的,比如说对于开发人员来讲要求比较高,不容易操作,但是Java线程的操作已经简化了很多,是一个比较成熟的模型。很多时候,我们都用不到线程,但是当我们有一天不走运(或者走运)的时候,我们必须要面对这个问题的时候,应该怎么办呢?本文是我的学习笔记和一些总结,试图解决这个问题,引领还没有接触过Java 线程的开发人员进入一个Java线程的世界,其实很多东西在网路上已经有朋友总结过了,不过我感觉没有比较循序渐进,要么太基础,要么太高深,所以这边我由浅到深的总结一下。但是很显然,我的资历尚浅,能力也很有限,如果有什么错误还望不吝赐教!而且,这些大部份的都有源码,如果需要也可以发mail到这个邮箱,真的非常希望有人能指正我的错误!
(一) 基本的API介绍
1.   如何创建一个可以执行的线程
       创建一个线程有两个办法:继承Thread类或者实现Runnable接口。
       首先:继承Thread
       这里一般只需要我们来重写run这个方法。下面是代码:
public class SimpleThread extends Thread {
       public SimpleThread() {
              start();
       }
       @Override
    public void run() {
              while (true) {
                     System.out.println(this);
                     // Imply other thread can run now, but we cannot assume that it will
                     // work well every time, actually , most of time we can get the same
                     // result, but not to a certainty.
                     // yield();
                     try {
                            sleep(100);
                     } catch (InterruptedException e) {
                            e.printStackTrace();
                     }
              }
       }
}
其次:实现Runnable接口,代码如下:
       Public class Inner implements Runnable {
              private Thread thread;
              public Inner(String name) {
                     thread = new Thread(this, name);
                     thread.start();
              }
              public void run() {
                     while (true) {
                     try {
                                   Thread.sleep(10);
                            } catch (InterruptedException e) {
                                   throw new RuntimeException(e);
                            }
                     }
              }

6 个回复

倒序浏览
2.   几个常用的API
这边介绍几个常见而且重要的的线程API,这边JDK文档有更加详细的说明,其实JDK的文档就是个很好的学习资料,常备很重要哦!
方法
说明
start
使线程开始执行,实际上这个方法会调用下面的run这个方法,如果这个线程已经开始执行,则会扔出IllegalThreadStateException
sleep
是当前已经运行的线程休眠一段时间。如果当前线程已经被别的线程中断的话,将会扔出InterruptedException,而且interrupted标志也会被清空。这个方法有两个版本,具体参看JDK文档。
run
线程执行的业务逻辑应该在这里实现。
join
等待另外一个线程死亡。如果当前线程已经被别的线程中断的话,将会扔出InterruptedException,而且interrupted标志也会被清空。
yield
使当前线程临时的中断执行,来允许其他线程可以执行,因为Java线程模型实际上映射到操作系统的线程模型,所以对于不同的操作系统,这个方法的就有不同的意义。对于非抢占式Operating System,这个方法使得其他线程得到运行的机会,但是对于抢占式的OS,这个方法没有太多的意义。关于这个方法,后边还有更多的介绍。
wait
Wait方法和后边的两个方法都来自Object。看过Java源码的可以知道,这三个方法都是Native方法,使比较直接的和操作系统打交道的方法。
这个方法的作用是让当前线程等待,直到被唤醒或者等待的时间结束。当前线程进入等待队列的时候,会放弃当前所有的资源,所以当前线程必须获得这些对象的Monitor,否则会扔出IllegalMonitorStateException 关于wait方法的更多,后边会有介绍到。
notify
通知其他线程可以使用资源了。这个方法的使用要求很多,总之需要当前线程获得被调用的notify方法的对象的monitor。比如:
                                                 synchronized (person) {
                                                        person.notify();
                                                 }
其实,获得monitor的方法还有别的,这里简单介绍一下:
1.         执行这个对象的一个同步的方法
2.         执行这个对象的同步块
3.         执行一个同步的静态方法
notifyAll
除了通知所有的线程可以准备执行之外,跟上面的方法要求一样。但是只有一个线程会被选择然后执行,这个就跟优先级和其他状态有关系了。
interrupt
中断线程
这边只是介绍了几个常用的API,但是非常重要,其他的API可以查看JDK的相关文档。但是在操作系统的概念中,很显然,对于一个线程应该还有别的状态,对,确实还有,但是Java在实现的映射的时候,也实现了这些方法,只是不赞成使用,下面的主题将讨论这些方法以及这些方法的替代方法。
回复 使用道具 举报
3.   已经不赞成使用的方法
对于一些不应该再使用的东西,有时候被称为反模式antipattern。这些都是概念上的东西,对于我们开发人员来讲,需要做的就是写出好的代码。
方法
说明
stop
强制使当前的线程停止执行。实际上,作为开发人员我们会意识到,线程的复杂程度是没有边际的,而这个方法这样武断的停止一个线程,必然导致问题产生。也就是说,这个方法天生就有问题。比如说一个线程掌握了很多对象,并且改变了其中一些的状态,如果突然当前对象突然被停止,将会释放这些对象的monitor,这个时候被改变状态的对象就是被损坏的对象,其他线程再来操作的时候问题就出来了。
替代的办法就是让当前线程正常结束,不使用这个方法。就是我们设置一些标志,如果这些标志满足的时候,我们结束线程。下面用JDK的例子:
    private Thread blinker;
    public void start() {
        blinker = new Thread(this);
        blinker.start();
    }
    public void stop() {
        blinker.stop();  // UNSAFE!
    }
    public void run() {
        Thread thisThread = Thread.currentThread();
        while (true) {
            try {
                thisThread.sleep(interval);
            } catch (InterruptedException e){
            }
            //do something
        }
}
修改后:
    private volatile Thread blinker;    public void stop() {        blinker = null;    }    public void run() {        Thread thisThread = Thread.currentThread();        //Check the flag        while (blinker == thisThread) {            try {                thisThread.sleep(interval);            } catch (InterruptedException e){            }            //do something        }}
当然如果这个方法中间有wait方法的调用的话,而且正在等待,我们可以使用这个办法来结束:
Thread.currentThread().interrupt();
然后处理InterruptedException

这个我也实现了避免使用stop方法的一个类,在源码中可以看到。
回复 使用道具 举报
suspend
这个方法天生就有导致死锁的可能。如果当前线程持有很多对象的锁,但是当他suspend的时候,这些锁是不会释放的,想想就知道应该什么可能会发生,所以这个方法应该尽量不用。
这里我们有办法替代这个方法,其实根替代stop的方法差不多,就是用wait方法来实现挂起,而不是事实上的挂起。比如:
@Override
    @SuppressWarnings("static-access")
                            public void run() {
                                   while (true) {
                                          try {
                                                 Thread.currentThread().sleep(1000);
                                                 // Double check
                                                 if (isSuspended) {
                                                        synchronized (this) {
                                                               while (isSuspended) {
                                                                      wait();
                                                               }
                                                        }
                                                 }
                                          } catch (InterruptedException e) {
                                                 // null

                                          }
                                   }
                            }
    这样做可以同样实现挂起,但是仍然会释放资源。
resume
很显然,这个方法和上面的方法是对应的,所以上面用了wait方法来替代,所以这边应该用notify这个方法或者notifyAll这个方法来替代。

其实这边可以把上面的实现方式结合起来,实现一个可以安全stopsuspend线程。这个在我的源码里有实现,但是不知道是不是对的。不过感觉原则应该没有问题,那就是设置标志来结束或者挂起线程,而不是使用这些过时的方法。
回复 使用道具 举报
4.   线程相关的关键字
线程相关的关键字我能够想到的就下面两个:
关键字
说明
volatile
这个关键字告诉编译器不要对这个属性或者值进行优化,也就是为了保证这个变量的同步性,如果这个值被更新,其他线程应该可以马上访问到最新的值,而不是“脏值”。其实这个关键字是同步互斥的一个模型,但是现在没有实现。
synchronized
给对象或者Class加锁,分为对象锁和Class锁。
对象锁只是加在当前对象上,对别的对象没有影响,这种加锁一般都是把这个关键字用在方法和同步块上面。
Class锁就是加在这个Class上面,所有的其他对象想访问这个Class的对象的任何同步方法,必须获得这个锁。这种锁一般把这个关键字用在静态方法中,或者显示的这样实现:
                                   synchronized (AClass.class) {
                                          while (isSuspended) {
                                                 wait();
                                          }
                                   }
一般我们很少用Class锁。

这里简单提一下monitor,个人感觉这里把monitor认为是一把锁也可以。网络上有朋友解释的比较好:在Java中,每个对象只有一个相应的monitor,一个mutex,而每一个monitor都可以有多个“doors”可以进入,即,同一个monitor中被守护的代码可以在不同的地方,因为同一个对象可以出现在不同的代码段,只要mutex锁定的对象是同一个,每个入口都用Synchronized关键字表明,当一个线程通过了Synchronized关键字,它就所住了该monitor所有的doors
回复 使用道具 举报
其实线程的使用不在于语言的API,而在于对操作系统的理解和一些常见的调度算法,其实个人理解经验比较重要,后边介绍到线程的实现模式和设计模式。其实我还是以前的想法:对于语言的学习,首先学习语法和API,然后学习如何使用这些API在语法的框架内编写出高效的程序。很显然,模式就是实现后边的重要方法。模式常见的分类有实现模式、设计模式和架构模式。这里限于本人的能力问题,没有理解到架构上面去,所以这里只是研究了前两个。
(二) 线程实现模式
实现模式这边主要参考自Effective Java这本书,至少分类是,但是很多内容应该会很不相同,当然还有Think in JavaEffective Java短小精悍的一本书,其中有太多的Java的关于实现模式的建议,但是这边把这本书的内容归类到实现模式,是我个人的想法,如果有什么不正确,万望指正。但是,个人认为这些概念性的东西仍然不会损害到我们需要讨论的问题的实质。
1.         共享数据同步
上面有提到过synchronized关键字,这个关键字保证一段代码同时只能有一个线程在执行,保证别人不会看到对象处于不一致的状态中。对象将从一种一致的状态转变到另一种一致的状态,后来的线程将会看到后一种状态。
Java中,虚拟机保证原语类型(doublelong)的读写都是原子性的。即不需要同步,但是如果不对这样的数据读写进行同步,那么后果将很严重。可以参照Effective Java的解释,这里还要简单的提示意下,Effective Java中有提到double check这种方式,而且我的源代码中多次用到这种方法,单是需要提醒一下,如果用这种方式来实现singleton的话,就不可以了,因为这样有可能导致不完整的对象被使用,单是源码中的double check用的都是原语类型,所以OK 这边的建议是如果修改原语类型或者非可变类的属性,可以同步或者使用volatile关键字。如果是其他对象,必须同步。关于尽量少使用同步,这边的建议是,我们这样的初学者在不知道如何优化的情况下就不要优化,我们要的是正确的程序,而不是快的程序。
2.         wait方法的使用
wait方法是一个很重要的方法,前面有介绍过这个方法,不但可以使一个线程等待,而且可以作为实现suspend的替代方法的一个方法。
Wait方法的标准使用方式如下:
                                   synchronized (obj) {
                                          while (condition)
                                                 wait();
                                   }
这里,对应wait方法还有一个notifynotifyAll方法,到底我们应该如何使用这两个方法来唤醒等待的线程呢?很显然notifyAll的使用是最安全的,但是会带来性能的降低。这里又提到我们初学者,应该优先考虑这个方法,而不是notify
3.         不要依赖线程调度器,管好自己的事情
Thread.yield这个方法并不能保证线程的公平运行,所以这个方法不应该依赖。还有就是线程的优先级,Java线程优先级有10个等级,但是这个等级几乎没有什么用处,所以我们也不应该依赖这个优先级来控制程序,当然仍然可以优化一些服务,但是不能保证这些服务一定被优化了。我们应该尽量控制对critical resources的方法线程数,而不是用优先级或者yield来实现对资源的访问。
4.         不要使用线程
线程组是一个过时的API,所以不建议使用。但是也不是一无是处,“存在即合理”嘛!
回复 使用道具 举报
学习线程的确很头疼,顶一下
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马