Java线程详解

来源:互联网 发布:java做网络爬虫 编辑:程序博客网 时间:2024/06/06 03:44

引子:由于对线程掌握的不是很扎实,每次在使用到线程的时候都有种心惊胆战的感觉;所以这次决定系统的把线程相关的知识总结一下,一方面巩固基础,另一方面也可用把自己的理解抛出来大家一起探讨。

学习提纲

这里写图片描述

生命周期

Thread的生命周期有5种:

(1)新建(new)–通过new创建后,Thread thread = new Thread();

(2)就绪(runnable)调用了start方法,进入等待池后的状态,注意调用了start只是开始等待cpu调度,并不一定马上开始运行了。

(3)运行(running)线程开始运行。

(4)阻塞(blocked)wait,sleep,join,synchronized,IO阻塞等都会使线程进入阻塞状态。

(5)死亡(dead)线程执行完毕或者异常终止,结束生命周期。

图:生命周期图例(此图百度所得,非原创):
这里写图片描述

基本使用

(1)继承Thread:

class newThread extends Thread{        @Override        public void run(){            //TODO        }    }

(2)实现Runnable

class newRunnable implements Runnable{        @Override        public void run(){            //TODO        }    }Thread thread = new Thread(new newRunnable());

(3)sleep()

说明:用法比较简单,Thread.sleep(1000); 使当前正处于运行阶段的线程暂停,不会释放锁。可以被interrupt唤醒(关于interrupt,下面会详细介绍)。它是一个Thread的static方法。其控制范围是当前线程。

    public static void main(String[] args) {        // TODO Auto-generated method stub        //testIo();        Thread thread = new Thread(new Runnable(){            @Override            public void run(){                try {                    System.out.println(Thread.currentThread().getName()+"开始睡觉");                    Thread.sleep(5000);                    System.out.println("睡了5s");                } catch (InterruptedException e) {                    // TODO Auto-generated catch block                    System.out.println("被打断了:"+e.toString());                }            }        });        thread.start();        System.out.println(Thread.currentThread().getName()+"要打断你睡觉了");        try {            Thread.sleep(2000);            thread.interrupt();        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }

(4)wait()

wait方法是Object的方法,不是属于Thread的方法,只能用在Synchronized中,包括唤醒它的notify()和notifyAll()也必须用在Synchronized中,因为必须拿到Object的锁,否则会报错 java.lang.IllegalMonitorStateException 。
我们把上面的代码修改一下:

    public static void main(String[] args) {        // TODO Auto-generated method stub        //testIo();        final Object object = new Object();        Thread thread = new Thread(new Runnable(){            @Override            public void run(){                synchronized(object){                try {                    object.wait();                    System.out.println(Thread.currentThread().getName()+"开始睡觉");                    Thread.sleep(5000);                    System.out.println("睡了5s");                } catch (InterruptedException e) {                    // TODO Auto-generated catch block                    System.out.println("被打断了:"+e.toString());                }                }            }        });        thread.start();        System.out.println(Thread.currentThread().getName()+"要打断你睡觉了");        synchronized(object){        object.notify();        }        try {            Thread.sleep(2000);            thread.interrupt();        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }

(5)yield()

这个方法是属于Thread的,作用是暂时让出CPU使用权,让自己和其他的线程重新去让cpu调度,自己还是有可能被调度到。
个人觉得这个方法没什么意思啊,也没有释放锁的功能,说一下可能会用到的地方吧,当你觉得一个线程不那么重要,优先级很低,然后又不希望它占用太多的cpu资源的时候就可以用这个方法稍微暗示一下,只是暗示,不是控制啊,控制不了
It is rarely appropriate to use this method。
虽然比较简单,还是上个代码:

    public static void main(String[] args) {        // TODO Auto-generated method stub        //testIo();        Thread thread1 = new Thread(){            @Override            public void run(){                while(i<10){                    System.out.println("i:"+i);                    i++;                    if(i==5)                        yield();                }            }        };        Thread thread2 = new Thread(){            @Override            public void run(){                while(j<10){                    System.out.println("j:"+j);                    j++;                }            }        };        thread1.start();        thread2.start();    }

(6)join()

这个方法什么意思呢?这个方法是Thread的方法,当你调用了这个方法后,当前正在运行的线程会等调用了该方法的线程完成后才会继续运行。场景的场景是A线程的运行需要B线程的运行结果,那么A就要等B运行完,这个时候就可以用join()方法了。
注意了,在start()后调用才有意义。
上代码:

    public static void main(String[] args) {        // TODO Auto-generated method stub        //testIo();        final Thread thread1 = new Thread(){            @Override            public void run(){                while(i<10){                    System.out.println("i:"+i);                    i++;                    if(i==5)                        yield();                }            }        };        Thread thread2 = new Thread("2"){            @Override            public void run(){                while(j<10){                    System.out.println("j:"+j);                    j++;                    if(j==5)                    try {                        System.out.println(Thread.currentThread().getName()+"开始等待");                        thread1.join();                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }            }        };        thread1.start();        thread2.start();        try {            System.out.println(Thread.currentThread().getName()+"开始等待");            thread1.join();            System.out.println(Thread.currentThread().getName()+"等待完成了");        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }

说明:上面这段代码还有个彩蛋,在join之前,thread1已结运行完了呢?

join()还有个重载方法thread1.join(50000);参数是毫秒,意思是虽然我要等你的运行结果,但是你也不能让我等太久了,等超过了我设置的时间,我就不等了。

(7)interrupt

现在我们来谈一谈interrupt,嗯,打断线程,让线程抛异常从而退出。你会说了,简单啊,但是让我们先来看看下面的代码:

    public static void main(String[] args) {        // TODO Auto-generated method stub        Thread thread1 = new Thread("thread01"){            @Override            public void run(){                while(i<5){                    i++;                     long time = System.currentTimeMillis();                        while((System.currentTimeMillis()-time < 1000)) {                                   //什么也不做,让程序飞一会儿                             }                     System.out.println("i:"+i);                }            }        };        thread1.start();        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        //1s后打断线程1        System.out.println("打断前");        thread1.interrupt();        System.out.println("打断完成");    }

别说话,看结果:

打断前i:1打断完成i:2i:3i:4i:5

是不是很意外,为什么没有被打断?好了,我也不卖关子了,我们都知道,当我们用sleep(),join(),wait()的时候都必须try catch,那么有没有注意到catch里面捕获的是什么异常呢?没错,就是InterruptedException,聪明的你现在知道为什么了嘛?
来来来,我们将上面让程序飞的代码换成sleep(),好了,在运行,你熟悉的错误回来了。

打断前java.lang.InterruptedException: sleep interrupted    at java.lang.Thread.sleep(Native Method)    at wangliang.Test$1.run(Test.java:36)打断完成i:1i:2i:3i:4i:5

当调用interrupt方法时并不是去中断线程,而是改变线程的中断标志位,Thread.currentThread().isInterrupted()可以查看这个标志位,当线程处于被阻塞时,调用interrupt方法才有效。

多线程

上面说了这么多,重点还是多线程,说到多线程,那重点还是线程同步。

(1)Synchronized

每个对象都有monitor,这就是我们常说的锁。使用Synchronized,保证了对对象访问的互斥性。

三条规则:

第一条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

第二条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。

第三条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

很多情况下,可以使用synchronized(this){…}来同步代码块,这种情况和用synchronized修饰方法一样。但需要注意的是,使用this作为同步对象的话,如果同一个类中存在多个synchronized(this){…}代码块,其中任何一个synchronized(this)代码块处于被执行状态,则其它线程对其他synchronized(this)代码块的访问也会受到阻塞。

在静态方法中时,可以用XX.class来锁。

(2)volatile

在讲volatile之前,我们先了解一下并发正确执行的三要素:原子性,可见性,有序性。

原子性就是一个操作要么全部完成,要么就不完成,比如i++我们知道是分了三步的,所以它不是一个原子操作。可见性是指一个变量在一个线程中改变了,其他线程可以知道。有序性是指代码执行是有序的,你要知道,因为指令重排序的存在,JVM执行代码的顺序并不一定是你写的顺序。

volatile能保证可见性和部分有序性。为什么说是部分有序性呢,因为volatile可以保证在它前面的一定在它前面运行,它后面的一定在它后面运行。所以,如果我们要使用volatile,就需要保证操作的原子性。

通常来说,使用volatile必须具备以下2个条件:

  1)对变量的写操作不依赖于当前值,这个比如像i++这种,i的下一个值依赖于当前值的。

  2)该变量没有包含在具有其他变量的不变式中
  
举个栗子:

class Singleton{    private volatile static Singleton instance = null;    private Singleton() {    }    public static Singleton getInstance() {        if(instance==null) {                //语句1            synchronized (Singleton.class) {   //语句2                if(instance==null)              //语句3                    instance = new Singleton();     //语句4            }        }        return instance;    }}

上面的程序,假设两个线程都运行到了语句1,然后由于语句2的存在,只有一个线程进入到了语句3,此时,如果instance没有volatile修饰的话,那么另外一个线程可能不知道此时 instance != null 而又new了一个出来。有了volatile修饰,instance的改变立刻回通知到另外一个线程。
这篇博客讲的很详细 http://www.cnblogs.com/dolphin0520/p/3920373.html

(3)Lock
这个我目前阶段不打算深入了解,一方面是熟练掌握有一定难度,如果使用不当容易出错;另一方面在Android开发中上面介绍的内容已足以应对大部分情况。
如果有需要的小伙伴,推荐一个不错的博客:https://my.oschina.net/xianggao/blog/88477

总结,以上算是比较系统的介绍了java线程的知识点,下一篇会就具体的案例来学习,在实战中跟深入了解。

0 0
原创粉丝点击