zoukankan      html  css  js  c++  java
  • java 多线程

    线程基础概念

      线程从创建到最终的消亡,要经历若干个状态。一般来说,线程包括以下这几个状态:创建(new)、就绪(runnable)、运行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)。

      当需要新起一个线程来执行某个子任务时,就创建了一个线程。但是线程创建之后,不会立即进入就绪状态,因为线程的运行需要一些条件(比如内存资源,在前面的JVM内存区域划分一篇博文中知道程序计数器、Java栈、本地方法栈都是线程私有的,所以需要为线程分配一定的内存空间),只有线程运行需要的所有条件满足了,才进入就绪状态。

      当线程进入就绪状态后,不代表立刻就能获取CPU执行时间,也许此时CPU正在执行其他的事情,因此它要等待。当得到CPU执行时间之后,线程便真正进入运行状态。

      线程在运行状态过程中,可能有多个原因导致当前线程不继续运行下去,比如用户主动让线程睡眠(睡眠一定的时间之后再重新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待一定的事件)、waiting(等待被唤醒)、blocked(阻塞)。

      当由于突然中断或者子任务执行完毕,线程就会被消亡。

      下面这副图描述了线程从创建到消亡之间的状态:

      图转自: http://zy19982004.iteye.com/blog/1626916

      在有些教程上将blocked、waiting、time waiting统称为阻塞状态,这个也是可以的,只不过这里我想将线程的状态和Java中的方法调用联系起来,所以将waiting和time waiting两个状态分离出来。

     

    1、实现线程的两种形式

    a/ 继承thread类

     继承Thread类的话,必须重写run方法,在run方法中定义需要执行的任务。

    class MyThread extends Thread{
        private static int num = 0;
         
        public MyThread(){
            num++;
        }
         
        @Override
        public void run() {
            System.out.println("主动创建的第"+num+"个线程");
        }
    }

      创建好了自己的线程类之后,就可以创建线程对象了,然后通过start()方法去启动线程。注意,不是调用run()方法启动线程,run方法中只是定义需要执行的任务,如果调用run方法,即相当于在主线程中执行run方法,跟普通的方法调用没有任何区别,此时并不会创建一个新的线程来执行定义的任务。

    public class Test {
        public static void main(String[] args)  {
            MyThread thread = new MyThread();
            thread.start();
        }
    }
     
     
    class MyThread extends Thread{
        private static int num = 0;
         
        public MyThread(){
            num++;
        }
         
        @Override
        public void run() {
            System.out.println("主动创建的第"+num+"个线程");
        }
    }

    在上面代码中,通过调用start()方法,就会创建一个新的线程了。为了分清start()方法调用和run()方法调用的区别,请看下面一个例子:

     

    public class Test {
        public static void main(String[] args)  {
            System.out.println("主线程ID:"+Thread.currentThread().getId());
            MyThread thread1 = new MyThread("thread1");
            thread1.start();
            MyThread thread2 = new MyThread("thread2");
            thread2.run();
        }
    }
     
     
    class MyThread extends Thread{
        private String name;
         
        public MyThread(String name){
            this.name = name;
        }
         
        @Override
        public void run() {
            System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
        }
    }

    运行结果:

    从输出结果可以得出以下结论:

      1)thread1和thread2的线程ID不同,thread2和主线程ID相同,说明通过run方法调用并不会创建新的线程,而是在主线程中直接运行run方法,跟普通的方法调用没有任何区别;

      2)虽然thread1的start方法调用在thread2的run方法前面调用,但是先输出的是thread2的run方法调用的相关信息,说明新线程创建的过程不会阻塞主线程的后续执行。

    b/ 实现runable接口

     在Java中创建线程除了继承Thread类之外,还可以通过实现Runnable接口来实现类似的功能。实现Runnable接口必须重写其run方法。

    下面是一个例子:

    public class Test {
        public static void main(String[] args)  {
            System.out.println("主线程ID:"+Thread.currentThread().getId());
            MyRunnable runnable = new MyRunnable();
            Thread thread = new Thread(runnable);
            thread.start();
        }
    }
     
     
    class MyRunnable implements Runnable{
         
        public MyRunnable() {
             
        }
         
        @Override
        public void run() {
            System.out.println("子线程ID:"+Thread.currentThread().getId());
        }
    }

      Runnable的中文意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。注意,这种方式必须将Runnable作为Thread类的参数,然后通过Thread的start方法来创建一个新线程来执行该子任务。如果调用Runnable的run方法的话,是不会创建新线程的,这根普通的方法调用没有任何区别。

      事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。

      在Java中,这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。

     Runnable 并不一定是新开一个线程,比如下面的调用方法就是运行在UI主线程中的:

     Handler mHandler = new Handler();
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG, "run: "+Thread.currentThread().getName());
                }
            });

    2、 线程的同步

     线程同步的关键字:

    未加入线程同步的计数程序:

    public class ThreadSafeTest implements Runnable {
        int num = 10; // 设置当前总票数
        
        public void run() {
            while (true) {
                if (num > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("current thread"+Thread.currentThread().getId()+"    tickets" + num--);
                }
            }
        }
        
        public static void main(String[] args) {
            ThreadSafeTest t = new ThreadSafeTest(); // 实例化类对象
            Thread tA = new Thread(t); // 以该类对象分别实例化4个线程
            Thread tB = new Thread(t);
            Thread tC = new Thread(t);
            Thread tD = new Thread(t);
            tA.start(); // 分别启动线程
            tB.start();
            tC.start();
            tD.start();
        }
    }
    View Code

    输出结果为: 

    current thread8    tickets10
    current thread10    tickets9
    current thread9    tickets8
    current thread11    tickets7
    current thread8    tickets6
    current thread10    tickets5
    current thread9    tickets4
    current thread11    tickets3
    current thread8    tickets1
    current thread10    tickets2
    current thread11    tickets0
    current thread9    tickets-1

    产生了结果为-1的值(多个线程同时进入run()方法,发现num>0,但是某个进程正在睡眠,所以两个进程同时对num进行了减法操作)

    加入线程同步后的计数程序 :

    //加入线程同步后的计数
    public class ThreadSafeTest implements Runnable {
        int num = 10;    
        public void run() {
            while(num>0){
                doit();
            }
            System.out.println("priority is : "+Thread.currentThread().getPriority()+"    "+Thread.currentThread().getId());
        }
        public synchronized void doit(){
            if(num>0){
                try{
                    Thread.sleep(100);
                }catch(Exception e){
                    e.printStackTrace();
                }
                System.out.println("tickets: "+num--+"    current id: "+Thread.currentThread().getId());
            }
        }
        public static void main(String[] args) {
            ThreadSafeTest t = new ThreadSafeTest();
            Thread tA = new Thread(t);
            Thread tB = new Thread(t);
            Thread tC = new Thread(t);
            Thread tD = new Thread(t);
            tD.setPriority(10);
            
            tA.start();
            tB.start();
            tC.start();
            tD.start();
        }
    }
    View Code

    运行结果为:

    tickets: 10    current id: 8
    tickets: 9    current id: 10
    tickets: 8    current id: 10
    tickets: 7    current id: 10
    tickets: 6    current id: 10
    tickets: 5    current id: 10
    tickets: 4    current id: 10
    tickets: 3    current id: 10
    tickets: 2    current id: 10
    tickets: 1    current id: 10
    priority is : 5    10
    priority is : 10    11
    priority is : 5    9
    priority is : 5    8

    代码段中 调用了 setPriority();方法去设置 线程的优先级,优先级高的线程回优先执行。

    线程的默认优先级为5

    当四个线程的优先级相同时,执行结果  仍然为 10 递减到1 

    Synchronized(Object) {} 该同步块相当于一个临界区

    或者 Synchronized void f(){} 

    通过同步块的代码为:

        public void run() {
            while (true) {
                synchronized ("") {
                    if (num > 0) {
                        try {
                            Thread.sleep(1000);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        System.out.println("tickets" + --num);
                    }
                }
            }
        }

    效果相同

    3、操作线程的方法

     start() : start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

    如果要设置一个 守护进程: 必须在 start() 之前将 setDaemon(true);  start之后进程即为普通进程不能,在设置为守护进程会报错。 

     run() : run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

      sleep():

      sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。

      但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。看下面这个例子就清楚了:

    public class Test {
         
        private int i = 10;
        private Object object = new Object();
         
        public static void main(String[] args) throws IOException  {
            Test test = new Test();
            MyThread thread1 = test.new MyThread();
            MyThread thread2 = test.new MyThread();
            thread1.start();
            thread2.start();
        } 
         
         
        class MyThread extends Thread{
            @Override
            public void run() {
                synchronized (object) {
                    i++;
                    System.out.println("i:"+i);
                    try {
                        System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");
                        Thread.currentThread().sleep(10000);
                    } catch (InterruptedException e) {
                        // TODO: handle exception
                    }
                    System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束");
                    i++;
                    System.out.println("i:"+i);
                }
            }
        }
    }

    运行结果为:

      从上面输出结果可以看出,当Thread-0进入睡眠状态之后,Thread-1并没有去执行具体的任务。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行。

      注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。

      join()

     join方法有三个重载版本:

    join()
    join(long millis)     //参数为毫秒
    join(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

       假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。如果调用的是无参join方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的事件。

      看下面一个例子:

    public class Test {
         
        public static void main(String[] args) throws IOException  {
            System.out.println("进入线程"+Thread.currentThread().getName());
            Test test = new Test();
            MyThread thread1 = test.new MyThread();
            thread1.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();
            }
        } 
         
        class MyThread extends Thread{
            @Override
            public void run() {
                System.out.println("进入线程"+Thread.currentThread().getName());
                try {
                    Thread.currentThread().sleep(5000);
                } catch (InterruptedException e) {
                    // TODO: handle exception
                }
                System.out.println("线程"+Thread.currentThread().getName()+"执行完毕");
            }
        }
    }

       输出结果:

      

      可以看出,当调用thread1.join()方法后,main线程会进入等待,然后等待thread1执行完之后再继续执行。

      实际上调用join方法是调用了Object的wait方法,这个可以通过查看源码得知:

      

      wait方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。

      由于wait方法会让线程释放对象锁,所以join方法同样会让线程释放对一个对象持有的锁。具体的wait方法使用在后面文章中给出。

          调用join()的进程等待其他进程执行,执行完毕之后再执行当前进程该方法需要捕获异常

      yeild()

       调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

      注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

     interrupt()

       interrupt,顾名思义,即中断的意思。单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。

      下面看一个例子:

    public class Test {
         
        public static void main(String[] args) throws IOException  {
            Test test = new Test();
            MyThread thread = test.new MyThread();
            thread.start();
            try {
                Thread.currentThread().sleep(2000);
            } catch (InterruptedException e) {
                 
            }
            thread.interrupt();
        } 
         
        class MyThread extends Thread{
            @Override
            public void run() {
                try {
                    System.out.println("进入睡眠状态");
                    Thread.currentThread().sleep(10000);
                    System.out.println("睡眠完毕");
                } catch (InterruptedException e) {
                    System.out.println("得到中断异常");
                }
                System.out.println("run方法执行完毕");
            }
        }
    }

    输出结果:

      

      从这里可以看出,通过interrupt方法可以中断处于阻塞状态的线程。那么能不能中断处于非阻塞状态的线程呢?看下面这个例子:

    public class Test {
         
        public static void main(String[] args) throws IOException  {
            Test test = new Test();
            MyThread thread = test.new MyThread();
            thread.start();
            try {
                Thread.currentThread().sleep(2000);
            } catch (InterruptedException e) {
                 
            }
            thread.interrupt();
        } 
         
        class MyThread extends Thread{
            @Override
            public void run() {
                int i = 0;
                while(i<Integer.MAX_VALUE){
                    System.out.println(i+" while循环");
                    i++;
                }
            }
        }
    }

    运行该程序会发现,while循环会一直运行直到变量i的值超出Integer.MAX_VALUE。所以说直接调用interrupt方法不能中断正在运行中的线程。

      但是如果配合isInterrupted()能够中断正在运行的线程,因为调用interrupt方法相当于将中断标志位置为true,那么可以通过调用isInterrupted()判断中断标志是否被置位来中断线程的执行。比如下面这段代码:

    public class Test {
         
        public static void main(String[] args) throws IOException  {
            Test test = new Test();
            MyThread thread = test.new MyThread();
            thread.start();
            try {
                Thread.currentThread().sleep(2000);
            } catch (InterruptedException e) {
                 
            }
            thread.interrupt();
        } 
         
        class MyThread extends Thread{
            @Override
            public void run() {
                int i = 0;
                while(!isInterrupted() && i<Integer.MAX_VALUE){
                    System.out.println(i+" while循环");
                    i++;
                }
            }
        }
    }

    运行会发现,打印若干个值之后,while循环就停止打印了。

    使用Executor管理Thread对象

    public class CacheThreadPool {  
        public static void main(String[] args) {  
            ExecutorService exec=Executors.newCachedThreadPool();  
            for(int i=0;i<5;i++)  
                exec.execute(new LiftOff());  
            exec.shutdown();//并不是终止线程的运行,而是禁止在这个Executor中添加新的任务  
        }  
    }  
    public class LiftOff implement Runnable{}

    ExecutorService 使用静态的方法 newCachedThreadPool()创建对象,

      还可以通过 newFixedThreadPool(int ) ,这种方法初始化的对象Thread数量是有限的。

      newSingleThreadExecutor() ; 一次只能运行一个线程,后提交的线程需要等待前面的线程运行结束后 再 运行

    当需要从任务中得到返回值时,通过下面这种方法,返回的类型 通过future对象进行存储

    public class FutureTest {
        public static class Task implements Callable<String> {
            @Override
            public String call() throws Exception {
                String tid = String.valueOf(Thread.currentThread().getId());
                System.out.printf("Thread#%s : in call
    ", tid);
                return tid;
            }
        }
    public static void main(String[] args) throws InterruptedException, ExecutionException {
         List<Future<String>> results = new ArrayList<Future<String>>();
         ExecutorService es = Executors.newCachedThreadPool();
         for(int i=0; i<100;i++)
         results.add(es.submit(new Task()));
         for(Future<String> res : results)
            System.out.println(res.get());
        }
    }
    

      

    补充: Thread中的方法:

    1)getId

      用来得到线程ID

      2)getName和setName

      用来得到或者设置线程名称。

      3)getPriority和setPriority

      用来获取和设置线程优先级。

      4)setDaemon和isDaemon

    转载: http://www.cnblogs.com/dolphin0520/p/3920357.html

        http://zy19982004.iteye.com/blog/1626916

          http://iteye.blog.163.com/blog/static/1863080962012111424544215/

  • 相关阅读:
    C++ 黑白棋AI minimax+alphabeta剪枝
    BZOJ2839 集合计数 容斥
    BZOJ2287 消失之物
    CF235B Let's Play Osu! 期望DP
    线性基
    [HAOI2008]糖果传递 结论题
    [HAOI2007]上升序列
    线性筛及线性递推欧拉函数
    Codeforces 1064D/1063B Labyrinth
    洛谷P2120 [ZJOI2007]仓库建设 斜率优化DP
  • 原文地址:https://www.cnblogs.com/NeilZhang/p/6831636.html
Copyright © 2011-2022 走看看