• Java 多线程


    Java多线程

    1、什么是进程

    百度百科:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

    2、什么是线程

    百度百科:线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位(线程是进程中的一个实体)。

    3、进程与线程之间的关系

    • 线程属于进程的实体,也就是说明线程是必须依赖于进程

    • 进程里面至少有一个线程, 没有线程此进程也无法运行

    • 一个进程允许有多个线程

    4、进程的特点

    • 独立性:进程是系统进行资源分配和调度的一个独立单位

    • 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的

    • 并发性(支持多进程):任何进程都可以同其他进程一起并发执行

    • 结构性:进程由程序(控制进程执行的指令集),数据(数据集合是程序在执行时所需要的数据和工作区)和进程控制块(程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志)三部分组成

    5、线程的特点

    • 允许跨线程资源共享

    • 创建线程的开销要比进程小(进程要分配一大部分的内存,而线程只需要分配一部分栈就可以了)

    • 提高进程运行的效率

    6、创建线程的三种方式

    a、继承 Thread 类

    class MyThread extends Thread{  //继承Thread类,作为线程的实现类 
         private String name ; // 表示线程的名称
         public MyThread(String name){
             this.name = name ; // 通过构造方法配置name属性
        }
         public void run(){  // 覆写run()方法,作为线程的操作主体
             for(int i=0;i<10;i++){
                 System.out.println(name + "运行,i = " + i) ;
            }
        }
    public class ThreadDemo02{
        public static void main(String args[]){
            // 实例化对象
            MyThread mt1 = new MyThread("线程A ") ;    
            MyThread mt2 = new MyThread("线程B ") ;
             //启动线程
            mt1.start() ;  
            mt2.start() ;  
        }
    }

    b、实现 Runnable 接口

    class MyThread implements Runnable{ // 实现Runnable接口,作为线程的实现类 
         private String name ;       // 表示线程的名称
         public MyThread(String name){
            this.name = name ;      // 通过构造方法配置name属性
        }
         public void run(){  // 覆写run()方法,作为线程的操作主体
             for(int i=0;i<10;i++){
                 System.out.println(name + "运行,i = " + i) ;
            }
        }
       public class RunnableDemo01{
        public static void main(String args[]){
                // 实例化对象
                MyThread mt1 = new MyThread("线程A ") ;    
                MyThread mt2 = new MyThread("线程B ") ;  
                 // 实例化Thread类对象
                Thread t1 = new Thread(mt1) ;      
                Thread t2 = new Thread(mt2) ;      
                // 启动线程
                t1.start() ;    
            t2.start() ;  
        }
    }

    实现Runnable接口创建线程也可以通过匿名内部类来完成

     public static void main(String[] args) {
           new Thread(new Runnable() {
               @Override
               public void run() {
                   for (int i = 0; i < 10; i++) {
                       System.out.println(i);
                  }
              }
          }).start();
      }

    实现Runnable接口创建线程还可以通过Lambda表达式来完成

    public static void main(String[] args) {
           new Thread(()->{
               for (int i = 0; i < 10; i++) {
              System.out.println(i);
              }
          }).start();
      }

    注意事项

    • Thread类本身也实现了Runnable接口(看源码)

    • 不能反复调用同一个线程的start()方法,会抛异常

    • 线程启动虽然调用的是 start() 方法,但实际上调用的却是 run() 方法定义的主体

    • 不调用start()方法,直接调用run()方法并没有创建线程,不具备多线程效果(https://www.cnblogs.com/heqiyoujing/p/11355264.html参考资料

    c、实现Callable接口

    直接继承Thread和实现Runnable接口都有一个缺陷就是:在执行完任务之后无法获取执行结果。但是实现Callable接口方法可以获取执行结果

    首先,我们需要知道以下类或接口:

    • Future接口

      • 对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。

    • RunnableFuture接口

      • RunnableFuture继承了Runnable接口和Future接口

    • FutureTask

      • FutureTask实现了RunnableFuture接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

    代码实现

    public class MyThread implements Callable<Long> {
       
       @Override
       public Long call() throws Exception {
           //输出线程名和线程id
           System.out.println(Thread.currentThread().getName()+" "+Thread.currentThread().getId());
           //返回线程id
           return Thread.currentThread().getId();
      }
    }
    public class MyThreadTest {
       
       public static void main(String[] args) throws ExecutionException, InterruptedException {
           MyThread myThread=new MyThread();
           FutureTask futureTask=new FutureTask(myThread);
           //创建线程
           Thread thread=new Thread(futureTask);
           //启动线程
           thread.start();
           //得到线程返回结果并输出(如果没有启动线程而直接获取返回值,程序陷入类似死循环)
           System.out.println(futureTask.get());
      }

    }

    也可以写成匿名内部类

    public static void main(String[] args) throws ExecutionException, InterruptedException {
         
           new Thread(new FutureTask<Long>(new Callable<Long>() {
               @Override
               public Long call() throws Exception {
                   //输出线程名和线程id
               System.out.println(Thread.currentThread().getName()+" "+Thread.currentThread().getId());
                   //返回线程id
                   return Thread.currentThread().getId();
              }
          })).start();

      }

    还可以写成Lambda表达式

    public static void main(String[] args) throws ExecutionException, InterruptedException {

           new Thread(new FutureTask<Long>(()->{
               //输出线程名和线程id
               System.out.println(Thread.currentThread().getName()+" "+Thread.currentThread().getId());
               //返回线程id
               return Thread.currentThread().getId();
          })).start();
       
      }

    7、继承 Thread 类和实现Runnable接口两种方式的区别

    • 实现Runnable 接口,可以方便实现资源的共享

    image-20200806145221250

    8、volatile关键字

    如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的(简单提一下,具体内容不过多阐述,还需要自己查阅资料)

    9、线程的生命周期

    • 线程从创建到销毁的过程,包含五个状态:

      • 新建(new一个线程)

      • 就绪:有执行资格,没有执行权

      • 运行:有执行资格,有执行权

      • 阻塞:由于一些操作让线程处于该状态,没有执行资格,没有执行权,但另一些操作可以使它激活,激活后处于就绪状态

      • 死亡:线程对象变成垃圾,等待回收

    • 线程状态转换图解

      image-20200806155959385

    在此提出一个问题,Java 程序每次运行至少启动几个线程?

    回答:至少启动两个线程,每当使用 Java 命令执行一个类时,实际上都会启动一个 JVM,每一个JVM实际上就是在操作系统中启动一个线程,Java 本身具备了垃圾的收集机制。所以在 Java 运行时至少会启动两个线程,一个是 main 线程,另外一个是垃圾收集线程。

    10、控制线程的方法

    a、sleep()和wait()的区别

    • sleep是线程中的方法,但是wait是Object中的方法。

    • sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中(重点)

    • sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。

    • sleep不需要被唤醒(休眠之后推出阻塞),但是wait需要(不指定时间需要被别人中断)。

      注意一点:阻塞被唤醒后还是从原来被阻塞的点开始往下执行

    b、join()

    join在线程里面意味着“插队”,哪个线程调用join代表哪个线程插队先执行——但是插谁的队是有讲究了,不是说你可以插到队头去做第一个吃螃蟹的人,而是插到在当前运行线程的前面,比如系统目前运行线程A,在线程A里面调用了线程B.join方法,则接下来线程B会抢先在线程A面前执行,等到线程B全部执行完后才继续执行线程A。

    • 代码体验

    /** 定义的线程类 * */ 
    class MyThread extends Thread {
       
       @Override
       public void run() {
           for(int i=0;i<10;i++) {
               System.out.println("thread id="+Thread.currentThread().getId()+" i="+i);
               //睡眠1秒
               try {
                   Thread.sleep(1000);
              } catch (InterruptedException e) {
                   // TODO Auto-generated catch block e.printStackTrace();
              }
          }
      }
    }
    public class MainTest {

       public static void main(String[] args) {
           System.out.println("主线程的main开始执行");
           //创建及启动子线程
           MyThread t=new MyThread();
           t.start();
           try {
               //等待子线程执行完成,才继续执行主线程
               t.join();
          } catch (InterruptedException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
          }
           System.out.println("主线程的main结束执行");
      }
    }

    当系统中正在运行多个线程时,join()到底是暂停了哪些线程?t.join()方法会使所有线程都暂停并等待t的执行完毕吗

    答:t.join()方法只会使主线程(或者说调用t.join()的线程)进入等待池并等待t线程执行完毕后才会被唤醒。并不影响同一时刻处在运行状态的其他线程。

    c、yield()

    由运行态到就绪态,停止一下后再由就绪态到运行态

    代码体验

    //创建一个线程类
    class MyThread extends Thread {
       
       @Override
       public void run() {
           for(int i=0;i<100;i++) {
              System.out.println("子线程ID="+Thread.currentThread().getId()+" i="+i);
               if(i==30) {
                   System.out.println("子线程调用yield方法");
              Thread.yield();
                }
          }
      }
    }
    public class MainTest {
       
    public static void main(String[] args) {
           //创建线程
           MyThread t1=new MyThread();
           //启动线程
           t1.start();
           for(int j=0;j<100;j++) {
               if(j==60) {  
              System.out.println("主线程调用yield方法");
                   //当前线程暂停(从运行态---->就绪态)
                   Thread.yield();
              }
    System.out.println("主线程ID="+Thread.currentThread().getId()+" j="+j);
          }
      }
    }

    d、中断线程

    参考资料https://www.cnblogs.com/liyutian/p/10196044.html

    e、设置线程优先级

    java 中的线程优先级的范围是1~10,默认的优先级是5。10最高。

    可以通过getPriority()获取线程优先级,setPriority()设置线程优先级。设置线程的优先级应该在线程启动之前。

    f、后台线程

    参考资料https://blog.csdn.net/staticFinal_shuaibi/article/details/84865688

    10、解决线程同步问题

    当一个类中的属性被多个线程共享,那么就会造成一种问题,如果这多个线程要操作同一个资源时就有可能出现资源同步问题。

    实现线程同步有很多种方法,这里讲三种(文末资料有补充)

    a、同步方法

    • 用synchronized关键字修饰方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

    • 代码体现

    public class Bank {
       private int count = 0;// 账户余额

       // 存钱
       public synchronized void addMoney(int money) {
           count += money;
           System.out.println(System.currentTimeMillis() + "存进:" + money);
      }

       // 取钱
       public synchronized void subMoney(int money) {
           if (count - money < 0) {
               System.out.println("余额不足");
               return;
          }
           count -= money;
           System.out.println(+System.currentTimeMillis() + "取出:" + money);
      }

       // 查询
       public void lookMoney() {
           System.out.println("账户余额:" + count);
      }
    }

    b、同步代码块

    • 用synchronized关键字修饰语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

    • 同步代码块格式

      synchronized(同步对象)
       需要同步的代码
    • 代码体现

      public class Bank {
         private int count = 0;// 账户余额

         // 存钱
         public  void addMoney(int money) {
             synchronized(this){
                 count += money;
            }
             
             System.out.println(System.currentTimeMillis() + "存进:" + money);
        }

         // 取钱
         public  void subMoney(int money) {
             synchronized(this){
                 if (count - money < 0) {
                     System.out.println("余额不足");
                     return;
                }
                 count -= money;
            }
             
             System.out.println(+System.currentTimeMillis() + "取出:" + money);
        }

         // 查询
         public void lookMoney() {
             System.out.println("账户余额:" + count);
        }
      }

    注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。

    c、显示加锁

    • 在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法具有相同的基本行为和语义,并且扩展了其能力。

      • ReenreantLock类的常用方法有:

        • ReentrantLock() : 创建一个ReentrantLock实例

        • lock() : 获得锁

        • unlock() : 释放锁

      • 注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用

    • 代码体验

      public class Bank {
         private int count = 0;// 账户余额

         // 需要声明这个锁
         private Lock lock = new ReentrantLock();

         // 存钱
         public void addMoney(int money) {
             lock.lock();
             try {
                 count += money;
                 System.out.println(System.currentTimeMillis() + "存进:" + money);
            } finally {
                 lock.unlock();
            }
        }

         // 取钱
         public void subMoney(int money) {
             lock.lock();
             try {

                 if (count - money < 0) {
                     System.out.println("余额不足");
                     return;
                }
                 count -= money;
                 System.out.println(+System.currentTimeMillis() + "取出:" + money);
            } finally {
                 lock.unlock();
            }
        }

         // 查询
         public void lookMoney() {
             System.out.println("账户余额:" + count);
        }
      }

      1、ReentrantLock()还可以通过public ReentrantLock(boolean fair)构造方法创建公平锁,即,优先运行等待时间最长的线程,这样大幅度降低程序运行效率。 2、关于Lock对象和synchronized关键字的选择: (1)、最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。 (2)、如果synchronized关键字能够满足用户的需求,就用synchronized,他能简化代码。 (3)、如果需要使用更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally中释放锁。

    11、线程同步之死锁

    同步可以保证资源共享操作的正确性,但是过多同步也会产生问题。例如,现在张三想要李四的画,李四想要张三的书,张三对李四说“把你的画给我,我就给你书”,李四也对张三说“把你的书给我,我就给你画”两个人互相等对方先行动,就这么干等没有结果,这实际上就是死锁的概念。

    所谓死锁,就是两个线程都在等待对方先完成,造成程序的停滞,一般程序的死锁都是在程序运行时出现的。

    • 代码体验

    class Zhangsan{ // 定义张三类
       public void say(){
           System.out.println("张三对李四说:“你给我画,我就把书给你。”") ;
      }
       public void get(){
           System.out.println("张三得到画了。") ;
      }
    }
    class Lisi{ // 定义李四类
       public void say(){
           System.out.println("李四对张三说:“你给我书,我就把画给你”") ;
      }
       public void get(){
           System.out.println("李四得到书了。") ;
      }
    }
    public class ThreadDeadLock implements Runnable{
        // 实例化static型对象(注意这里必须是static修饰s,因为静态成员依赖类存在只加载一次,不是静态就不止加载一次,对象不同,锁不同)
       private static Zhangsan zs = new Zhangsan() ;
        // 实例化static型对象(注意这里必须是static修饰,因为静态成员依赖类存在只加载一次,不是静态就不止加载一次,对象不同,锁不同)
       private static Lisi ls = new Lisi() ;      
       private boolean flag = false ;  // 声明标志位,判断那个先说话
       public void run(){  // 覆写run()方法
           if(flag){
               synchronized(zs){// 同步张三
                   //张三说话
                   zs.say() ;
                   try{
                       //线程睡眠
                       Thread.sleep(500) ;
                  }catch(InterruptedException e){
                       e.printStackTrace() ;
                  }
                   synchronized(ls){//同步李四
                       //张三得到画
                       zs.get() ;
                  }
              }
          }else{
               synchronized(ls){//同步李四
                   //李四说话
                   ls.say() ;
                   try{
                       //线程睡眠
                       Thread.sleep(500) ;
                  }catch(InterruptedException e){
                       e.printStackTrace() ;
                  }
                   synchronized(zs){
                       //李四得到书
                       ls.get() ;
                  }
              }
          }
      }
       public static void main(String args[]){
           // 控制张三
           ThreadDeadLock t1 = new ThreadDeadLock() ;
           // 控制李四
           ThreadDeadLock t2 = new ThreadDeadLock() ;      
           t1.flag = true ;
           t2.flag = false ;
           //创建线程
           Thread thA = new Thread(t1) ;
           Thread thB = new Thread(t2) ;
           //启动线程
           thA.start() ;
           thB.start() ;
      }
    }
    • 程序进入死锁状态

    12、线程通信

    当线程在系统内运行时,线程的调度具有一定的透明性,程序通常无法准确控制线程的轮换执行,但Java也提供了一些机制来保证线程协调运行

    线程通信通常用于生产者/消费者模式

    a、传统的线程通信

    假设现在系统中有两个线程,这两个线程分别代表存款者和取钱者----现在有一种特殊的要求:存款者和取钱者不断地重复存款、取款动作,而且要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。不允许连续存款和取款。

    为了实现这种功能,可以借助于Object类提供的wati()、notify()、notifyAll()三个方法。下面是关于这三个方法的解释:

    • wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()或notufyAll()来唤醒该线程(带参数则等待指定时间后自动苏醒)

    • notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则随机选择一个线程唤醒

    • notifyAll():唤醒在此同步监视器上等待的所有线程

    代码体验

    /**
    *账户类
    */
    public class Account {
       private double balance;//存款
       private int id;//编号
       private boolean flag=true;//判断账户是否有存款

       public Account() {
      }

       public Account(double balance, int id) {
           this.balance = balance;
           this.id = id;
      }

       public int getId() {
           return id;
      }

       public void setId(int id) {
           this.id = id;
      }

       public double getBalance() {
           return balance;
      }

       /**
        * 取钱
        */
       public synchronized void draw(int money){
           if (flag){//账户有存款
               System.out.println(DrawThread.currentThread().getName()+"取钱"+money);
               //取钱
               balance-=money;
               System.out.println("账户余额为"+balance);
               //账户无存款
               flag=false;
               //唤醒存钱线程
               notifyAll();
          }else{//账户没存款
               //线程等待
               try {
                   wait();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      }
       /**
        * 存钱
        */
       public synchronized void deposit(int money){
           if (!flag){//账户没存款
               System.out.println(DrawThread.currentThread().getName()+"存钱"+money);
               //存钱
               balance+=money;
               System.out.println("账户余额为"+balance);
               //账户有存款
               flag=true;
               //唤醒取款
               notifyAll();
          } else{//账户有存款
               //线程等待
               try {
                   wait();
              } catch (InterruptedException e) {
                   e.printStackTrace();
              }
          }
      }
    }
    /**
    * 存钱线程
    */
    public class DepositThread extends Thread{
       private Account account;//账户
       private String name;//线程名
       private int money;//存款金额

       public DepositThread(String name, Account account, int money) {
           super(name);
           this.account = account;
           this.money = money;
      }

       @Override
       public void run() {
           //存钱十次
           for (int i = 0; i < 10; i++) {
               account.deposit(money);
          }
      }
    }
    /**
    * 取钱线程
    */
    public class DrawThread extends Thread{
       private Account account;//账户
       private String name;//线程名
       private int money;//取款金额

       public DrawThread(String name, Account account, int money) {
           super(name);
           this.account = account;
           this.money = money;
      }

       @Override
       public void run() {
           //取款十次
           for (int i = 0; i < 10; i++) {
               account.draw(money);
          }
      }
    }
    /**
    *测试类
    */
    public class ThreadTest {

       public static void main(String[] args) {
           //账户
           Account account=new Account(1000,1);
           //创建线程
           DepositThread depositThread=new DepositThread("lsy",account,1000);
           DrawThread drawThread=new DrawThread("yl",account,1000);
           //启动线程
           depositThread.start();
           drawThread.start();
      }

    }

    b、使用Condition控制线程通信

    如果程序不使用synchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用wait()、notify()、notifyAll()方法进行线程通信了。(这三个方法是依赖于同步方法或同步代码块存在)

    当使用Lock对象来保证同步时,Java提供了一个Condition类来保持协调

    Condition将同步监视器方法(wait()、notify()、notifyAll())分解成截然不同的对象与Loc对象组合使用,Lock替代了同步方法或同步代码块,Condition替代了同步监视器的功能

    Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()即可。Condition提供了如下三个方法:

    • await():类似wait(),导致当前线程等待,直到其他线程调用该Condition的signal()或signalAll()来唤醒该线程

    • signal():唤醒在此Lock对象上等待的单个线程。如果所有线程都在此Lock对象上等待,则随机选择一个线程唤醒

    • signalAll():唤醒在此Lock对象上等待的所有线程

    代码体验

    /**
    * 账户
    */
    public class Account {
       private double balance;//存款
       private int id;//编号
       private boolean flag=true;//判断账户是否有存款
       private final Lock lock=new ReentrantLock();//锁
       private final Condition condition=lock.newCondition();//获取锁对应的condition

       public Account() {
      }

       public Account(double balance, int id) {
           this.balance = balance;
           this.id = id;
      }

       public int getId() {
           return id;
      }

       public void setId(int id) {
           this.id = id;
      }

       public double getBalance() {
           return balance;
      }

       /**
        * 取钱
        */
       public void draw(int money){
           //加锁
           lock.lock();
           try {
               //账户没存款
               while (!flag){
                   //线程等待
                   wait();
              }
               //账户有存款
               System.out.println(DrawThread.currentThread().getName()+"取钱"+money);
               //取钱
               balance-=money;
               System.out.println("账户余额为"+balance);
               //账户无存款
               flag=false;
               //唤醒存钱线程
               notifyAll();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }finally {
               //释放锁
               lock.unlock();
          }
      }
       /**
        * 存钱
        */
       public synchronized void deposit(int money){
           //加锁
           lock.lock();
           try {
               while (flag){//账户有存款
                   //线程等待
                   wait();
              }
               //账户没存款
               System.out.println(DrawThread.currentThread().getName()+"存钱"+money);
               //存钱
               balance+=money;
               System.out.println("账户余额为"+balance);
               //账户有存款
               flag=true;
               //唤醒取款
               notifyAll();
          } catch (Exception e) {
               e.printStackTrace();
          } finally {
               //释放锁
               lock.unlock();
          }
      }
    }
    /**
    * 存钱线程
    */
    public class DepositThread extends Thread{
       private com.yl.threadCommunication.Account account;//账户
       private String name;//线程名
       private int money;//存款金额

       public DepositThread(String name, Account account, int money) {
           super(name);
           this.account = account;
           this.money = money;
      }

       @Override
       public void run() {
           //存钱十次
           for (int i = 0; i < 10; i++) {
               account.deposit(money);
          }
      }
    }
    /**
    * 取钱线程
    */
    public class DrawThread extends Thread{
       private com.yl.threadCommunication.Account account;//账户
       private String name;//线程名
       private int money;//取款金额

       public DrawThread(String name, Account account, int money) {
           super(name);
           this.account = account;
           this.money = money;
      }

       @Override
       public void run() {
           //取款十次
           for (int i = 0; i < 10; i++) {
               account.draw(money);
          }
      }
    }
    /**
    * 测试
    */
    public class ThreadTest {

       public static void main(String[] args) {
           //账户
           com.yl.threadCommunication.Account account=new Account(1000,1);
           //创建线程
           com.yl.threadCommunication.DepositThread depositThread=new DepositThread("lsy",account,1000);
           com.yl.threadCommunication.DrawThread drawThread=new DrawThread("yl",account,1000);
           //启动线程
           depositThread.start();
           drawThread.start();
      }

    }

    c、使用阻塞队列(BlockingQueue)控制线程通信(了解)

    Java5提供了一个BlockingQueue接口,虽然BlockingQueue也是Queue的子接口,但它的主要用途并不是作为容器,而是作为线程同步的工具。BlockingQueue具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞

    程序的两个线程通过交替向BlockingQueue中放入元素,取出元素,即可很好地控制线程的通信。BlockingQueue提供如下两个支持阻塞的方法

    • put(E e):尝试把E元素放入BlockingQueue中,如果队列已满,则阻塞

    • take():尝试从BlockingQueue的头部取出元素,如果队列已空,则阻塞

    13、线程池

    a、线程池的好处

    系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互。在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池

    与数据库连接池类似的是,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个线程来执行它们的run()或call();当run()或call()执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()

    除此之外,使用线程池可以有效地控制系统中并发线程的数量,当系统中包含大量并发线程时,会导致系统性能剧烈下降,甚至导致jvm崩溃,而线程池的最大线程参数可以控制系统中并发线程数不超过此数

    b、线程池工作原理

    1、如果线程池中的线程数量未达到核心线程的数量,那么会直接启动-一个核心线程来执行任 2、如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中 排队等待执行。 3、如果在步骤2中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线 程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行。 4、如果步骤3中线程数量已经达到线程池规定的最大值,那么就拒绝执行此任务。

    c、线程池执行线程任务步骤

    • 调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池

    • 创建Runnable实现类或Callable实现类的实例,作为线程执行任务

    • 调用ExecutorService对象的submit()提交Runnable实例或Callable实例

    • 当不想提交任何任务时,调用ExecutorService对象的shutdown()关闭线程池

    代码体验

    public static void main(String[] args) {
           //保存等待线程的队列
           BlockingQueue<Runnable> blockingQueue=new ArrayBlockingQueue<>(3);
           //线程阻止处理程序
           ThreadPoolExecutor.AbortPolicy abortPolicy=new ThreadPoolExecutor.AbortPolicy();
           //线程池
           ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(3,3,2000,TimeUnit.SECONDS,                                    blockingQueue,abortPolicy);
           //Runnable
           MyThread myThread1=new MyThread();
           MyThread myThread2=new MyThread();
           MyThread myThread3=new MyThread();
           //提交线程
           threadPoolExecutor.submit(myThread1);
           threadPoolExecutor.submit(myThread2);
           threadPoolExecutor.submit(myThread3);
           //关闭线程池
           threadPoolExecutor.shutdown();
      }

    14、ThreadLocal类

    ThreadLocal为每一个使用该变量的线程都提供了一个变量值的副本,使每一个线程都可以独立的改变自己的副本,而不会和其他线程的副本冲突。从线程的角度看,就好像每个线程都完全拥有该变量一样

    • 用法

      • T get():返回此线程局部变量中当前线程副本的值

      • void remove():删除此线程局部变量中当前线程的值

      • void set(T value):设置此线程局部变量中当前线程副本的值

    代码体验

    class MyRunnable implements Runnable {
       private int i = 0; //ThreadLocal默认初始化数据值0
       ThreadLocal<Integer> threadId = new ThreadLocal<Integer> () {
    @Override
       protected Integer initialValue() {
       return 0;
    }
    };
    @Override public void run() {
       for (int j = 0; j < 10; j++) {
           //每个线程都有一份threadId变量的副本
           //针对副本+1操作
    threadId.set(threadId.get()+1);
           try {
               Thread.sleep(200);
          } catch (InterruptedException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
          }
           //针对副本-1操作
           threadId.set(threadId.get()-1);
           System.out.println("thread id=" +Thread.currentThread().getId() + " i=" + threadId.get());
      }
    }
    }
    public class MainTest {
       public static void main(String[] args) {
           MyRunnable r = new MyRunnable();
           // 创建线程一
           new Thread(r).start();
           // 创建线程二
           new Thread(r).start();
      }
    }

    参考资料

    本文参考了以下资料

     

    记得快乐
  • 相关阅读:
    codevs 1766 装果子
    codevs 1415 比那名居天子
    codevs 1388 砍树
    codevs 1373 射命丸文
    codevs 2867 天平系统3
    codevs 2866 天平系统2
    codevs 2865 天平系统1
    codevs 2832 6个朋友
    广搜优化题目总结
    Codeforces Round #578 (Div. 2)
  • 原文地址:https://www.cnblogs.com/Y-wee/p/13463587.html
Copyright © 2020-2023  润新知