Java 复习之多线程


  • 什么是线程

    • 线程是程序执行的一条路径, 一个进程中可以包含多条线程,一个进程可以理解成一个程序集,比如电脑中运行的 QQ 就是一个是一个进程,我们可以同时和多个人视频聊天,每一个聊天可以看做是一个线程路径;再比如迅雷下载文件,我们可以同时下载多个文件,每个下载任务也可以看做一条线程。
    • 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
  • 多线程并发和并行

  • 并行就是两个任务同时运行,比如甲任务进行的同时,乙任务也在进行,我们玩游戏的同时也在听歌,这两个进程是同时运行的,就叫并行(需要多核CPU)
  • 并发是指两个任务都请求运行,而处理器(单核)只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,让我们感觉两个任务都在运行,比如我们用QQ先和甲朋友聊天,又和乙朋友聊天,让甲乙朋友都感觉我在和他们聊天,这就是并行。
  • Java程序运行原理
    • Java程序运行时,Java命令会启动Java虚拟机,启动了JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
  • JVM的启动是多线程的么?
    • JVM启动至少启动了垃圾回收线程和主线程,所以虚拟机启动是多线程的。
  • 实现多线程程序

    • 继承Thread类
/**
 * 
 * @author 毛麒添
 * 多线程程序实现的方式1 继承Thread
 */
public class Demo_Thread {

    public static void main(String[] args) {
        MyTread mythread=new MyTread();
        mythread.start();//必须调用start方法 子线程才能跑起来
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程运行了");
        }
    }

}
 
class MyTread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("子线程运行了");
        }
    }
}
  • 继承Runnable接口
/**
 * 
 * @author 毛麒添
 * 多线程程序实现的方式2 继承Runnable接口
 */
public class Demo_Runnable {

    public static void main(String[] args) {
        MyRunnable mr=new MyRunnable();
        
        new Thread(mr).start();//将Runnable传入Thread中启动线程 ,因为Runnable接口中没有start()方法
        
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程运行了");
        }
    }
}
class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("子线程运行了");
        }   
    }
}
  • Runnable 实现原理
    上面的程序中我们new MyRunnable()得到了一个Runnable对象,查看new Thread(Runnable rn)的构造,最终调用的是如下的 init() 方法
 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
      
    ...................
        this.target = target;
    ..................
    }


 @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

init()方法中将我们传入的Runnable传递给了成员变量target,最终执行的还是Thread的run()方法 , 所以由此我们可以想到,Runnable接口其实是对于Thread类的补充,当我们一个要实现子线程类已经继承了一个类,由于Java的单继承性质,该类就可以通过实现Runnable接口来实现多线程。

  • 使用匿名内部类来实现多线程
//匿名内部类实现多线程 Thread
        new Thread(){public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("匿名内部类实现了多线程 Thread");
            }
        };}.start();
        
//匿名内部类实现多线程 Runnable
        new Thread(new Runnable() { 
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("匿名内部类实现了多线程 Runnable");
                }   
            }
        }).start();
  • 获取当前线程
/**
 * 
 * @author 毛麒添
 * 获取当前线程并给其设置名字
 */
public class GetCurrentThread {
    public static void main(String[] args) {
        //获取主线程
        Thread.currentThread().setName("我是主线程");
        System.out.println(Thread.currentThread().getName());
        
        //获取子线程并给其设置名字 Thread
        new Thread("我是子线程1"){public void run() {
            System.out.println(getName());
        };}.start();
        
        //获取子线程并给其设置名字 Runnable
        new Thread(new Runnable() {
            @Override
            public void run() {
                //runnable 
                Thread.currentThread().setName("我是子线程2");
            System.out.println(Thread.currentThread().getName());   
            }
        }).start();
    }
}

运行结果:


获取当前线程.png
  • 休眠线程

线程休眠我们可以使用Thread的静态方法sleep(long millis),该方法在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),指定的休眠时间过了之后线程会自动恢复执行。

/**
 * @author 毛麒添
 * 模拟CPU执行两个线程任务
 */
public class Sleep_Thread {

    public static void main(String[] args) {
        
        new Thread("线程1"){
            public void run() {
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(getName()+"执行了");
                }
            };
        }.start();
        
        new Thread("线程2"){
            public void run() {
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(getName()+"执行了");
                }
            };
        }.start();

    }
}

运行结果:


休眠线程程序运行截图.png
  • 守护线程

setDaemon(boolean b), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出,你可能看完这句话还是不知道什么是守护线程,下面看一个模拟QQ程序关闭的例子

/**
 * @author 毛麒添
 * 当我们使用QQ聊天的时候,如果关闭QQ 则所有的聊天窗口也会关闭
 */
public class ThreadDaemon {

    public static void main(String[] args) {
        Thread thread1 = new Thread("QQ非守护线程"){
            @Override
            public void run() {
                for (int i = 0; i < 2; i++) {
                    System.out.println(getName()+"正在运行");
                }
                System.out.println("QQ程序关闭了");
            }
        };
        Thread thread2 = new Thread("QQ守护线程(聊天窗口)"){
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println(getName()+"正在运行");
                }
            }
        };
        thread2.setDaemon(true);//设置守护线程
        thread1.start();
        thread2.start();
    }
}

运行结果:


守护线程程序运行截图.png

从程序结果我们可以这样理解,正在运行的QQ程序为非守护线程,而我们的聊天窗口为守护线程,不管你有多少个聊天窗口,只要非守护线程QQ程序关闭,我们聊天窗口也会随之关闭,运行截图中QQ程序关闭之后还运行了几次守护线程是因为守护的线程的关闭需要缓冲,就相当于你要打电话给一个人,拨打号码和等待对方接听也是需要一定的时间。

  • 其他方法

    • Thread.join() : 加入指定的线程执行,当前正在运行的线程暂停, 等待指定的线程执行结束后,当前线程再继续
    • Thread.join(int i), 可以等待指定的毫秒之后继续
    • Thread.yield() 让出cpu,让其他线程执行
    • Thread.setPriority()设置线程的优先级(1-10 范围),可以优先级高的线程优先执行多几次
/**
 * 
 * @author 毛麒添
 * 加入线程(插队)
 */

public class ThreadJoin {

    public static void main(String[] args) {
        Thread thread1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(getName()+"正在运行"+i);
                }
            }
        };
        Thread thread2 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    if(i%2==0){
                        try {
                            //thread1.join();//插队,加入
                            thread1.join(30);//插队,有固定的时间,过了固定时间,继续交替执行
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                    System.out.println(getName()+"正在运行"+i);
                }
            }
        };
        
        
        thread1.start();
        thread2.start();
    }
}
  • 同步代码块(关键字 synchronized)

    • 什么是同步代码块?

      • 使用synchronized关键字加上一个锁对象(锁对象可以是任意对象,两段代码块同步锁对象必须一样,否则不能实现同步)来定义一段代码, 这就叫同步代码块
      • 多个同步代码块如果使用相同的锁对象, 那么我们就可以理解这些代码块是同步的
    • 为什么需要同步

      • 多线程并发的时候,有多个代码块同时执行,我们希望某一段代码块执行的过程中CPU不要随意切换到其他线程工作,而是让这段代码块执行完之后再切换到其他线程,这时候就需要同步代码块
      • 如果两段代码块是同步的,那么同一时间只能执行一段代码块,在这一段代码块没有执行完之前是不会执行另一段代码的,比如两个人去上厕所,但是厕所只有一个坑,这个坑就好比CPU,只能有一个人使用,而另一个人必须等待正在蹲坑这个人上完厕所才能上,而厕所的门把手就是同步代码块的关键字 synchronized 英文意思是同步的(锁对象可以认为是门把手),锁上说明这个人在蹲坑,必须等他蹲完另一个人才能蹲,而且这把锁必须是指向同一个对象
/**
 * 
 * @author 毛麒添
 * 同步代码块
 */
public class Thread_sync {
    public static void main(String[] args) {
        Printer p=new Printer();
        new Thread(){public void run() {
            while(true){
                p.print1(); 
            }   
        };}.start();
        new Thread(){public void run() {
            while(true){
                p.print2(); 
            }
        };}.start();
    }
}

class Printer {
     static Object b=new Object();//锁对象可以是任意对象
    public static void print1() {
        synchronized(b){                //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
            System.out.print("今");
            System.out.print("晚");
            System.out.print("吃");
            System.out.print("鸡");
            System.out.println();
        }
    }

    public static void print2() {       
        synchronized(b){    
            System.out.print("我");
            System.out.print("怕");
            System.out.print("是");
            System.out.print("送");
            System.out.print("快");
            System.out.print("递");
            System.out.println();
        }
    }
}

例子程序中如果不对代码块加入synchronized关键字,则会出现某段代码块没有执行完,如图


代码不同步则会出现代码块没有执行完.png
  • 使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
  • 非静态同步函数的锁是: this (也就是对象本身)
  • 静态的同步函数的锁是:字节码对象(.class)
  • 所以上面示例的中的Printer对象的中两个方法改写如下,这两段代码块也是同步的
class Printer {
     //static Object b=new Object();//锁对象可以是任意对象
    public static void print1() {
        synchronized(Printer.class){                //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
            System.out.print("今");
            System.out.print("晚");
            System.out.print("吃");
            System.out.print("鸡");
            System.out.println();
        }
    }
    public static synchronized void print2() {          
            System.out.print("我");
            System.out.print("怕");
            System.out.print("是");
            System.out.print("送");
            System.out.print("快");
            System.out.print("递");
            System.out.println();
    }
}
  • 线程安全问题

    • 多线程并发操作同一数据时, 就有可能出现线程安全问题
    • 我们知道Vector、StringBuffer、Hashtable这些类是线程安全,为什么他们是线程安全的呢,通过查看他们的源码中的 addElement() 、append()、put()、方法(大家可以自行查看),我发现这些方法都是被关键字 synchronized 修饰,从而保证了方法同步,这也解释了为什么这些类是线程安全的。
    • 使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
  • 下面来一个模拟火车售票的例子,对多线程做一些应用
/**
 * 
 * @author 毛麒添
 * 一趟火车有100张票 分四个窗口卖完 (Thread实现)
 */
public class Ticket_thread {

    public static void main(String[] args) {

        Ticket t1=new Ticket();
        Ticket t2=new Ticket();
        Ticket t3=new Ticket();
        Ticket t4=new Ticket();
        
        t1.setName("火车售票窗口1");
        t2.setName("火车售票窗口2");
        t3.setName("火车售票窗口3");
        t4.setName("火车售票窗口4");
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

class Ticket extends Thread{
    
    private static int tickets=100;
    @Override
    public void run() {
        
            while(true){
                synchronized (Ticket.class) {//锁对象必须使用线程类本身,保证处理的是同一个数据
                if(tickets <= 0){
                    break;
                }
                try {
                    Thread.sleep(6);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(getName()+":这是第"+tickets--+"号票卖出");
            }
        }
        
    }
}

/**
 * @author 毛麒添
 * 一趟火车有100张票 分四个窗口卖完 (Runnable实现)
 */
public class Ticket_Runnable {

    public static void main(String[] args) {
        Ticketrn tn=new Ticketrn();
        Thread t1 = new Thread(tn);
        Thread t2=new Thread(tn);
        Thread t3=new Thread(tn);
        Thread t4=new Thread(tn);
        
        t1.setName("火车售票窗口1");
        t2.setName("火车售票窗口2");
        t3.setName("火车售票窗口3");
        t4.setName("火车售票窗口4");
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

}
class Ticketrn implements Runnable{
    
    private  int tickets=100;
    @Override
    public void run() {
        
            while(true){
                synchronized (this) {//只需要把runnable对象传给Thread,都是本身对象,传递的锁对象无所谓,都可以保证操作的是同一个数据
                if(tickets <= 0){
                    break;
                }
                try {
                    Thread.sleep(6);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":这是第"+tickets--+"号票卖出");
            }
        }
    }
}
  • 死锁

    • 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁

    • 什么是死锁?

      • 举个例子: 四个车队从东、南、西、北四个方向开来,行至一个井型的马路(如图所示),这时就可能出现四个车队无法前进的状态:东路车要等北路车开车才能前进;北路车要等西路车开走之后才能前进;西路车要等南路车开走后才能前进,而南路车要却要等东路车开走之后才能前进。显然,各个车队等待的事件都不会发生,这就是死锁,每条路相对于一个线程,都在请求不能使用的资源,也就产生了死锁,程序就卡住了。


        交通死锁问题.png

上面例子我们用程序表示:

/** 
 * @author 毛麒添
 * 死锁问题
 */
public class Thread_deadlock {
    private static String s1="西路";
    private static String s2="南路";
    private static String s3="东路";
    private static String s4="北路";
public static void main(String[] args) {    
        new Thread("西路车"){
            public void run() {
                while(true){
                    synchronized (s1) {
                    System.out.println(getName()+":进入"+s1 );
                    synchronized (s2) {
                        System.out.println(getName()+"等待"+s2+"车");  
                    }
                  }
                }
            };
        }.start();
        new Thread("南路车"){
            public void run() {
                while(true){
                    synchronized (s2) {
                    System.out.println(getName()+":进入"+s2 );
                    synchronized (s3) {
                        System.out.println(getName()+"等待"+s3+"车");  
                      }
                    }
                }
            };
        }.start();
        new Thread("东路车"){
            public void run() {
                while(true){
                    synchronized (s3) {
                    System.out.println(getName()+":进入"+s3 );
                    synchronized (s4) {
                        System.out.println(getName()+"等待"+s4+"车");  
                    }
                  }
                }
            };
        }.start();
        new Thread("北路车"){
            public void run() {
                while(true){
                    synchronized (s4) {
                    System.out.println(getName()+":进入"+s4 );
                    synchronized (s1) {
                        System.out.println(getName()+"等待"+s1+"车");  
                    }
                  }
               }
            };
        }.start();
    }
}
死锁例子运行截图.png

由上程序运行截图我们可以看出,程序还在运行,但是无法继续执行,已经出现死锁,所以当我们使用同步代码块的时候,一定不要让同步代码块嵌套,也就不会发生死锁的情况

  • Timer类

该类也就是我们常说的定时器,他的构造需要传入一个定时任务TimerTask,该类是一个抽象类,实现了Runnable接口,我们可以继承TimerTask这个类并在 run() 方法中做我们需要定时的操作,最后由Timer.schedule() 来实现我们定时操作

/**
 * 
 * @author 毛麒添
 * Timer 应用
 */
public class Demo_Timer {
    public static void main(String[] args) {    
        Timer timer=new Timer();
        //从现在开始,每三秒响一次闹铃
        timer.schedule(new MyTimer(),new Date(System.currentTimeMillis()),3000);

    }
}
class MyTimer extends TimerTask{
    @Override
    public void run() {
        System.out.println("大懒虫,起床啦");  
    }   
}
  • 两个线程间的通信(等待唤醒机制)

    • 等待唤醒,说白了就是两个方法 wait() 和 notify() ,它们都是Object类的方法(为什么是Object类的呢,待会解释)。

    • 什么时候需要线程间的通信?

      • 多个线程并发执行的时候,在默认的情况下CPU是随机切换线程的,如果我们希望线程有顺序或者规律执行,就需要用到线程间的通信,比如我们希望两个线程交替执行
    • 线程间如何通信?

      • 也就是使用我上面所说的两个方法,如果希望线程等待, 就调用wait(), 如果希望唤醒等待的线程, 就调用notify();这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用 ,下面以我上面的一个例子来做补充说明,然两个线程互相通知,交替执行
/**
 * @author 毛麒添
 * 等待唤醒机制  两个线程交替执行
 */
public class Two_Thread_call {
        public static void main(String[] args) {
            Printer p=new Printer();
            new Thread(){public void run() {
                while(true){
                    try {
                        p.print1();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } 
                }   
            };}.start();
            new Thread(){public void run() {
                while(true){
                    try {
                        p.print2();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } 
                }
            };}.start();
        }
    }
    class Printer {
        private boolean flag=true;
        public void print1() throws InterruptedException {
            synchronized(this){
                if(!flag){
                    this.wait();  //当前线程进入等待
                }
                System.out.print("今");
                System.out.print("晚");
                System.out.print("吃");
                System.out.print("鸡");
                System.out.println();
                flag=false;
                this.notify();   //随机唤醒单个等待的线程
            }
        }
        public  void print2() throws InterruptedException {       
            synchronized(this){
                if(flag){
                    this.wait();
                }
                System.out.print("我");
                System.out.print("怕");
                System.out.print("是");
                System.out.print("送");
                System.out.print("快");
                System.out.print("递");
                System.out.println(); 
                flag=true;
                this.notify();
            }
        }
    }

运行结果:

两个线程通信运行结果.png
  • 上面程序只是两个线程之间的通信,如果是三个线程呢,或者多个线程,我们知道 notify() 方法唤醒的是随机的一个线程,这样线程就不能按照我们想的顺序执行,所以三个线程该怎么进行通信呢,没关系我们还有 notifyAll()方法来通知所有线程,但是这里判断不能再用 if ,而是必须使用while ;if 判断是在哪里等待就在哪里启动 ,而我们是随机唤醒一个线程,如果执行的线程拥有执行权,唤醒这个线程 if 不会进入判断,则又继续执行一次,不是我们想要,而使用 while 循环来判断则会每次都判断条件是否满足
  • 下面我们来改造上面例子变成三个线程之间通信
class Printer2 {
    private int flag=1;
    public void print1() throws InterruptedException {
        synchronized(this){
            while(flag!=1){
                this.wait();  //当前线程进入等待
            }
            System.out.print("今");
            System.out.print("晚");
            System.out.print("吃");
            System.out.print("鸡");
            System.out.println();
            flag=2;
            this.notifyAll();  //随机唤醒单个等待的线程
        }
    }
    public  void print2() throws InterruptedException {       
        synchronized(this){
            while(flag!=2){
                this.wait();
            }
            System.out.print("我");
            System.out.print("怕");
            System.out.print("是");
            System.out.print("送");
            System.out.print("快");
            System.out.print("递");
            System.out.println(); 
            flag=3;
            this.notifyAll();
        }
    }
    public  void print3() throws InterruptedException {       
        synchronized(this){
            while(flag!=3){
                this.wait();
            }
            System.out.print("那");
            System.out.print("我");
            System.out.print("就");
            System.out.print("当");
            System.out.print("医");
            System.out.print("疗");
            System.out.print("兵");
            System.out.println(); 
            flag=1;
            this.notifyAll();
        }
    }
}

小结

  • 在例子中我们可以看到,在使用同步代码块中,线程进行通信,锁用什么对象,就用什么对象来调用 wait() 、 notify() 、notifyAll() 方法

  • 为什么wait() 、 notify() 方法会在Object类中?

    • 答案很简单: 锁传入的对象可以是任意的,而Object类是所有对象的父对象,则无论锁传入什么对象都可以调用这两个线程通信的方法
  • 这样我们还可以顺便复习一道很常见的面试题:sleep() 方法和 wait() 方法的区别?

    • sleep() 方法必须传入参数,传入的参数是睡眠时间,时间到了就会自动醒来继续执行,而 wait() 方法可以传入参数也可以不传入参数,当我们传入参数就是在制定参数的时间结束后进入线程等待,不传入参数就是直接线程等待
    • sleep() 方法在同步函数或同步代码块中不会把锁释放;而 wait() 方法在同步函数或者同步代码块中会将锁释放
  • 而在JDK 1.5 新特性中,也提供了线程之间通信的一个类 ReentrantLock,也叫互斥锁,我们看看API 文档的描述


    ReentrantLock类文档说明.png
  • 该类给我们提供了同步和通信的方法:

    • 使用该类,我们就不用synchronized 关键字来同步代码块了,而是使用 lock() 和unlock() 方法进行同步
    • 而线程之间的通信呢,则是使用 ReentrantLock 类的 newCondition() 方法可以获取 Condition 对象,相对于一个监视器,需要等待的时候使用Condition的await()方法,唤醒的时候用 signal() 方法,不同的线程使用不同的Condition对象, 这样就能区分唤醒的时候找哪个线程,下面再次将上面的例子改写使用 ReentrantLock 来进行线程间通信
class Printer3 {
    private int flag=1;
    private ReentrantLock rtl =new ReentrantLock();
    private Condition c1=rtl.newCondition();
    private Condition c2=rtl.newCondition();
    private Condition c3=rtl.newCondition();    
    public void print1() throws InterruptedException {
        rtl.lock();         //获取锁
        if(flag!=1){
            c1.await();
        }
            System.out.print("今");
            System.out.print("晚");
            System.out.print("吃");
            System.out.print("鸡");
            System.out.println();
            flag=2;
            c2.signal();
            rtl.unlock(); //释放锁      
    }
    public  void print2() throws InterruptedException {       
        rtl.lock();
            if(flag!=2){
                c2.await();
            }
            System.out.print("我");
            System.out.print("怕");
            System.out.print("是");
            System.out.print("送");
            System.out.print("快");
            System.out.print("递");
            System.out.println(); 
            flag=3;
            c3.signal();
           rtl.unlock();
    }
    public  void print3() throws InterruptedException {       
        rtl.lock();
            if(flag!=3){
                c3.await();
            }
            System.out.print("那");
            System.out.print("我");
            System.out.print("就");
            System.out.print("当");
            System.out.print("医");
            System.out.print("疗");
            System.out.print("兵");
            System.out.println(); 
            flag=1;
            c1.signal();
           rtl.unlock();
    }
}
  • 线程的五种状态

    线程的生命周期

    • 新建:创建线程对象
    • 就绪:线程对象已经启动,等待被执行,但是还没获得CPU的执行权
    • 运行:获取了CPU的执行权,线程执行
    • 阻塞:线程没有执行资格,失去CPU的执行权,回到就绪状态
    • 死亡:线程中的操作代码执行完毕,线程对象变为垃圾,线程消亡
    • 下面来看一张图


      线程的生命周期图.png
  • 线程池

线程池描述

  • 程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。由上面我们对线程的五种状态分析,每一个线程代码结束后,就进入死亡状态;而线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
  • 通俗的说,我们可以把线程池看做是一个养鱼的池子,每一条鱼相当于一个线程,如果你吊上来鱼,只能用于观赏,用完之后还要放回池子里面。
  • 内置线程池的使用概述
    • JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
      • public static ExecutorService newFixedThreadPool(int nThreads)
      • public static ExecutorService newSingleThreadExecutor()
      • 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
      • Future<?> submit(Runnable task)
      • <T> Future<T> submit(Callable<T> task)
/**
 * @author 毛麒添
 * JDK 1.5 Executor
 */
public class MyExecutorService {

    public static void main(String[] args) {
        //创建线程池 
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
        
        newFixedThreadPool.submit(new MyRunnable()); //将线程放入线程池
        newFixedThreadPool.submit(new MyRunnable());
        
        newFixedThreadPool.shutdown();//关闭线程池
        
    }

}

class MyRunnable implements Runnable{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        
    }
    
}
  • 而在我们开发中一般使用ThreadPoolExecutor来完成我们的线程池,有关该类的详细研究可以看这篇文章
    ThreadPoolExecutor机制
  • 下面贴出一个我在开发中使用过的线程池
/**
 * Created by 毛麒添 on 2017/2/10 0010.
 * 线程管理类,线程池为单例
 */
public class ThreadManager {
    private static ThreadPool mThreadPool;

    public static ThreadPool getmThreadPool(){
        if (mThreadPool==null){
            synchronized (ThreadManager.class){
                if(mThreadPool==null){//线程安全
                    mThreadPool=new ThreadPool(10,10,1L);
                }
            }
        }
        return mThreadPool;
    }
    //线程池
    public static class ThreadPool{

        private int corePoolSize;//核心线程数 10
        private int maximumPoolSize;//最大线程数 10
        private long keepAliveTime;//线程休眠时间 1秒

        private ThreadPoolExecutor executor;

        private ThreadPool(  int corePoolSize, int maximumPoolSize,long keepAliveTime){
              this.corePoolSize=corePoolSize;
              this.maximumPoolSize=maximumPoolSize;
              this.keepAliveTime=keepAliveTime;
        }


        public void execute(Runnable runnable){
            /**
             * int corePoolSize, 核心线程数
             * int maximumPoolSize, 最大线程数
             * long keepAliveTime, 线程休眠时间
             * TimeUnit unit, 时间单位
             * BlockingQueue<Runnable> workQueue, 线程队列
             * ThreadFactory threadFactory, 生成线程的工厂
             * RejectedExecutionHandler handler 线程一次处理
             */
            if(executor==null){
                executor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,
                        TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(),
                        Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
            }
            executor.execute(runnable);
        }
        //取消任务,从任务队列中将其移除
        public void cancelTask(Runnable runnable){
            if(runnable!=null){
                executor.getQueue().remove(runnable);
            }
        }
    }
}
//该类使用
 ThreadManager.getmThreadPool().execute(new Runnable() {
                @Override
                public void run() {
                  //执行耗时操作   
                }
            });

最后

好记性不如烂笔头,我希望通过这一篇文章,可以在我忘记了这些基础知识的时候,通过回顾这个复习笔记,找回忘掉的知识,如果你看到这篇文章,也希望可以帮助到你。文章中如果有错误,请大家给我提出来,大家一起学习进步,如果觉得我的文章给予你帮助,也请给我一个喜欢或者关注。

本系列其他文章:
Java复习之集合框架
Java复习之IO流(上)
Java复习之IO流(下)
Java 复习之多线程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 开篇副歌: 五月临来时夏天盛开 阳光撒野啊本性难改 凉风乘着专列匆匆赶来 她穿过校园也迈进心田 主...
    Fairo阅读 192评论 0 0
  • 过去两周,我莫名其妙地病了。 椎间盘突出压迫坐骨神经,疼得死去活来,整条左腿刀割和针刺样疼痛交替。 躺在车上去医院...
    减易官博阅读 917评论 0 0
  • 汇总下编写SpringMVC项目时遇到的问题,省的以后再次碰到忘记再去百度 1、异常如下: ERROR: org....
    Allenrui阅读 652评论 0 1
  • ——周一感言 我从不拉严窗帘 留点缝隙 等天亮那第一抹光 然后出发 并无神的启示 一路走去 涉水而过时 也会沾湿衣...
    第一闲人阅读 81评论 0 0
  • 2017 面临的事情很多 实习 毕业 就业 新的一年 我希望 你能够过一场兵荒马乱的生活 有怯懦难过失落退缩 也有...
    陈辰沉谌晨阅读 208评论 0 0