美文网首页
Java基础(12)——多线程

Java基础(12)——多线程

作者: 让时间走12138 | 来源:发表于2020-04-25 19:05 被阅读0次

本节内容

  • 1.线程&进程&主线程&子线程概念

  • 2.使用继承Thread开启线程

  • 3.使用Runnable接口开启线程

  • 4.线程的生命周期

  • 5.线程的终止

  • 6.线程礼让yield()

  • 7.多线程导致的问题

  • 8.使用lock和synchronized

  • 9.线程间通信

一、线程&进程&主线程&子线程概念

1.进程:正在运行/执行的一个程序,进程是用于管理所有的资源的,不进行实际任务
2.线程:完成具体任务(一个进程里面可以有多个线程)。例如QQ运行起来,可以和一个人聊天,又可以和另一个人视频,同时还可以刷空间,这些任务都可以同时进行,这都是由线程来操作的。
3.程序:静态的代码,如果不运行起来就没有意义。而一个程序运行起来就是一个进程,其中又包含多种线程。
4.主线程:在Java中,main方法里面的代码就在主线程中跑。在Android和IOS中,主线程就是启动程序,开到的UI界面,也叫UI主线程
5.子线程:除了主线程之外的都是子线程
6.为什么使用多线程:在主线程里面,任务的执行顺序是从上至下的。如果其中一个任务需要花费大量时间(下载数据等),那么这个任务后面的任务就会被阻塞,必须等这个任务结束才能被执行。这样用户的体验效果不好,这个时候就需要将耗时的任务放在另外一个子线程里面执行。这个就叫多线程。
7.注:不管是主线程还是子线程,都有自己独立的内存空间(执行路径/生命周期)。子线程也要从上至下执行。一个线程只完成一个任务,但是一个进程里面有多个线程。

二、使用继承Thread开启线程

1.获取当前线程信息,调用Thread.currentThread()即可
System.out.println(Thread.currentThread());
2.如何开启线程:①写一个类继承于Thread ②写一个类实现Runnable接口
3.使用继承Thread开启线程的具体过程:首先创建类继承于Thread,具体执行的任务放在run方法里面
class TestThread extends Thread {
  public TestThread(String s) {
        super(s);
    }
    @Override
    public void run() {
        //线程需要执行的任务
        for (int i = 0; i < 100; i++) {
                System.out.println((i+1)+ " ");
        }
    }
}
接着在main函数里面创建具体对象,然后就可以通过start()启动线程了。不能调用run方法,不然它还在主线程里面。调用start方法是因为,系统会自动将这个任务放到队列中,等待调度
     TestThread t =new TestThread("子线程");
      t.start();
注:可以给我们自己创建的类添加一个构造方法,然后创建对象的时候可以自己给这个线程命名。
4.可以创建多个对象,然后调用start()方法。但是没有一定的先后顺序。线程是通过抢占时间片来获取运行机会的,谁抢到时间片,谁就可以运行。时间片是由操作系统来分配的。所以每一次执行结果大部分是不一致的。
三、使用runnabld接口开启线程
1.创建一个类,实现runnable接口(这个类只是一个任务,并不能创建线程)
class TestRunnable implements Runnable{
    @Override
    public void run() {
   System.out.println(Thread.currentThread().getName());
      for (int i=1;i<=100;i++){
          System.out.println(i);
      }
    }
}
2.创建一个自定义类的一个具体对象,然后创建一个Thread对象(它可以创建线程),让这个线程去执行testRunnable的任务,线程名字为子线程。然后启动线程。
     TestRunnable t= new TestRunnable();
      Thread t1= new Thread(t,"子线程1");
      t1.start();
注:只创建一个TestRunnable类的对象无法开启子线程,必须依赖于Thread类
3.如果想要两个线程,那么可以再定义一个Thread类
      TestRunnable t= new TestRunnable();
      Thread t1= new Thread(t,"子线程1");
      Thread t2 =new Thread(t,"子线程2");
      t1.start();
      t2.start();
注:当调用start方法时,这个线程会自动扔到操作系统的任务队列(线程池)中,至于这个任务什么时候被执行,我们无法确定,由操作系统来决定

两种启动线程的方式对比

方式①:使用继承Thread ,方式②:使用runnabld接口
1.创建对象的过程方式①要简便一点,方式②要复杂一点
2.方式①不能再继承于其他的类了,不能实现其他的功能,因为Java是单继承。但是方式②可以继承多个接口,还能实现更多的功能。
3.综上:方式②灵活一点,更易于扩展。所以最好用接口的方式来开启线程

四、线程的生命周期

1.线程的生命周期:从创建到结束
2.线程的五个状态:创建状态—>就绪状态—>运行状态—>死亡状态,运行状态受阻时可能出现阻塞状态。
比如在运行状态调用sleep()或join()就会进入其他阻塞(等休眠时间到或者join()线程执行完毕,IO流阻塞结束,然后就会返回就绪状态),调用wait()就会进入等待阻塞(当使用notifiy()唤醒的时候就会回到就绪状态),调用synchronized()就会进入同步阻塞(当这个锁解开了,就会进入就绪状态)
注:就绪状态得到时间片就变成运行状态,运行状态失去时间片就变回就绪状态。运行状态之后,如果run方法结束,那么就进入死亡状态。
①创建状态(NEW):new Thread()
②就绪状态:1.新的线程调用start(),2.阻塞条件结束,3.正在运行的线程时间片被其他线程抢夺
③运行状态(RUNNABLE):从就绪状态到运行状态是由操作系统实现的,外部无法干预
④死亡状态:1.run方法结束 2.手动让线程暂停 (比如调用stop),但是不建议使用stop,而建议通过其他方式让线程暂停。
⑤阻塞状态(BLOCKED):运行状态受阻时进入阻塞状态
注意:可以调用getState()方法来查看线程当前的运行状态,在创建对象前后调用显示的结果不一样
 System.out.println(t1.getState());
如果想要进入阻塞状态,让它显示TIMED_WAITING,那么可以在自己定义的类里面添加一个sleep()方法,再调用getState()方法就可以显示这个,在网络延时的时候可能会使用sleep()
try {
              Thread.sleep(2000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }

五、线程的终止

1.如何让一个线程结束:①不要直接调用stop()方法来结束一个线程 ②自己写一个变量/标识符,用来标识线程结束的临界点
首先在自己写的类里面定义一个变量
private boolean shouldStop = true;
然后再创建一个方法,里面就是对shouldStop做一个赋值。并且在run方法里使用while循环,让它做参数
public void terminated(){
      shouldStop=false;
    }
 public void run() {
    while (shouldStop){
        System.out.println("子线程");
    }
然后在主类里面调用一下这个方法,就可以自己暂停。因为主类会不断的抢占时间片,所以有可能一直输出不了子线程
  MyThread t= new MyThread("测试线程");
     t.start();
    for(int i=0;i<100;i++){
        System.out.println("主线程"+(i));
        if(i==2){
            t.terminated();
        }
    }

六、线程礼让yield()

1.调用yield()函数可以让其他线程先执行,礼让的线程就会直接进入就绪状态,如果这个线程再次获得时间片就会执行,可能礼让失败
  TestRunnable t= new TestRunnable();
       Thread t1= new Thread(t,"小美");
       t1.start();
       for (int i=0;i<20;i++){
           System.out.println("主线程"+i);
           if(i==5){
               Thread.yield();
           }
       }
以上就是当i=5的时候,for循环就会先让t1这个线程执行,礼让给它之后,它进入就绪状态,然后和t1线程一起争夺时间片。因为它就在主线程里面,所以如果时间片争夺成功,就又会进入运行状态。这样就可能导致礼让不成功。
2.也可以调用join()方法,让其他线程插队,那么当前线程就会进入阻塞状态。只有当插队的线程执行完毕,当前线程才会进入运行状态。
  TestRunnable t= new TestRunnable();
       Thread t1= new Thread(t,"小美");
       t1.start();
       for (int i=0;i<20;i++){
           if(i==5){
               try {
                   t1.join();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           System.out.println("主线程"+i);
       }

七、多线程导致的问题

1.多线程的优点:执行效率高,不会阻塞主线程(因为耗时的任务都在子线程里面)
2.多线程的缺点:如果多个线程操作同一个资源,有可能出现不安全
public class MyClass {
    public static void main(String[] args) {
        TicketWindow t1 =new TicketWindow("重庆");
        TicketWindow t2= new TicketWindow("成都");
        t1.start();
        t2.start();
        }
}
class TicketWindow extends Thread {
    public TicketWindow(String s) {
        super(s);
    }
    private static int total = 100;
    @Override
    public void run() {
     for (int i=0;i<20;i++){
        if (total > 0) {
            System.out.println("当前出票口为:" + getName() + " 票号为:" + total);
            total--;
        }
    }
}
例如实现以上代码,结果显示为以下,他们第一张买到了一样的票
当前出票口为:重庆 票号为:20
当前出票口为:成都 票号为:20
当前出票口为:成都 票号为:18
当前出票口为:成都 票号为:17
当前出票口为:成都 票号为:16
当前出票口为:成都 票号为:15
···

八、使用lock和synchronized

两种方式保证线程安全:①锁 Lock ②线程同步
1.锁:必须使用的是同一个锁,先创建一把锁
private static Lock lock= new ReentrantLock();
然后调用lock上锁,再调用unlock解锁,把它们放在实现功能的代码前面与末尾即可,以上面买票举例

  lock.lock();
        if (total > 0) {
        System.out.println("当前出票口为:" + getName() + " 票号为:" + total);
            total--;
        }
            //解锁
            lock.unlock();
这样就不会买到一样的票了
2.线程同步synchronized,必须保证锁的是同一个对象,每一个对象都维护一把锁,可以修饰代码块,也可以修饰方法
(1)修饰代码块:使用时可以先创建一个Object类对象,然后将这个对象作为synchronized方法的参数,然后将要锁的代码用synchronized包裹起来。以下代码前后并不连续
private static Object object= new Object();

synchronized (object) {
           if (total > 0) {
        System.out.println("当前出票口为:" + getName() + " 票号为:" + total);
               total--;
           }
       }
(2)修饰方法:在定义函数时添加一个synchronized,随时都可以调用
 private synchronized void BuyTickets(){
        if (total > 0) {
        System.out.println("当前出票口为:" + getName() + " 票号为:" + total);
            total--;
        }
    }
3.synchronized的好处:当第一个线程进这个锁的时候,会执行相应的代码,当第二个线程过来的时候不能立刻执行这段代码,因为它被锁住了。只有等第一个线程解锁以后,第二个线程才会开始
注:不管是锁代码块还是锁方法,尽量让锁的范围小一点。范围太大,其他线程等的时间就越长,这样效率反而会下降。

九、线程间的通信

1.线程间的通信也就是交互,有三种方法可以实现线程间的交互:wait():让某个线程等待, notify():唤醒某个线程, notifyAll():唤醒多个线程
2.注意:这三个方法必须由同步监视器来调用,也就是大家都在抢同一个资源(或者说用同一个锁锁定的资源对象)
3.例如:我们要让两个线程分别交替输出,第一个线程输出以后,调用wait方法,等它输出结束后再调用notify()方法将其它在这同一个锁里等待的线程唤醒
4.如果某个创建后只会使用这一次,那么可以使用匿名内部类的方法,甚至连task这个对象都不用创建,直接调用start方法
public class MyClass {
    public static void main(String[] args) {
    new Thread(new Runnable() {
      @Override
      public void run() {
          for (int i = 0; i < 20; i++) {
              System.out.println(i+1);
          }
      }
  }).start();
    }
}
5.锁接收一样的参数,可以保证是同一个锁,线程在争夺同一个资源
6.让两个线程交替输出的实战:比如让数字和英文字母交替输出
public class MyClass {
    static int state=1;
    static Object obj=new Object();
    public static void main(String[] args) {
    new Thread(new Runnable() {
        int num=1;
      @Override
      public void run() {
          for (int i = 0; i < 20; i++) {
                 while (true){
                  synchronized (obj){
                      //判断当前是不是在输出字母
                      if(state!=1){
                          //当前这个线程需要等待一下
                          try {
                              obj.wait();
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                      //输出数字
                      System.out.println(num);
                      num++;
                      if(num>26){
                          break;
                      }
                      //唤醒当前obj锁上的其他等待的线程
                      state=2;
                      obj.notify();
                  }
                 }
          }
      }
  }).start();
        new Thread(new Runnable() {
           char alpha='a';
            @Override
            public void run() {
               while (true){
                    synchronized (obj){
                        //判断当前是不是在输出数字
                        if(state!=2){
                            //当前这个线程需要等待一下
                            try {
                                obj.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println(Character.toChars(alpha));
                        alpha++;
                        if(alpha>'z'){
                            break;
                        }
                        state=1;
                        obj.notify();
                    }
               }
            }
        }).start();
    }
}
7.让数字和英文字母交替输出还有另一种方法,我们可以创建一个类,然后在这个类里面添加两种方法,分别是输出数字和输出字母,并且给这两种方法加锁。然后创建一个新类的对象,通过这个对象调用相应的方法
public class MyClass {
    static Data d= new Data();
    public static void main(String[] args) {
     new Thread(new Runnable() {
         @Override
         public void run() {
            d.printNum();
         }
     }).start();

     new Thread(new Runnable() {
         @Override
         public void run() {
             d.printAlpha();
         }
     }).start();

    }
    public static void test(){

    }
}

class Data{
    int num =1;
    int alpha='a';
    int state=1;
    public  synchronized void printNum()  {
        while (true){
            if (state!=1){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(num);
            num++;
            if(num>26){
                break;
            }
            state=2;
            this.notify();
        }
    }
    public synchronized void printAlpha(){
          while (true){
              if(state!=2){
                  try {
                      this.wait();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
              System.out.println(Character.toChars(alpha));
              alpha++;
              if(alpha>'z'){
                  break;
              }
              state=1;
              this.notify();
          }
    }
}
OK,以上就是今天的全部内容,再见!

相关文章

  • android 多线程 — 线程的面试题和答案

    这里都是我从各个地方找来的资料,鸣谢: Java多线程干货系列—(一)Java多线程基础 JAVA多线程和并发基础...

  • 技术体系

    一,java核心 java基础,jvm,算法,多线程,设计模式 Java基础:java基础相关,全栈java基础 ...

  • Java多线程目录

    Java多线程目录 Java多线程1 线程基础Java多线程2 多个线程之间共享数据Java多线程3 原子性操作类...

  • Java基础

    Java基础 集合基础 集合框架 多线程基础 多线程框架 反射 代理 集合基础 ArrayList LinkedL...

  • java多线程相关

    (一) 基础篇 01.Java多线程系列--“基础篇”01之 基本概念 02.Java多线程系列--“基础篇”02...

  • Java多线程高级特性(JDK8)

    [TOC] 一、Java多线程 1.Java多线程基础知识 Java 给多线程编程提供了内置的支持。一条线程指的是...

  • Java多线程系列目录(共43篇)-转

    最近,在研究Java多线程的内容目录,将其内容逐步整理并发布。 (一) 基础篇 Java多线程系列--“基础篇”0...

  • Android中的多线程

    1. Java多线程基础 Java多线程,线程同步,线程通讯 2. Android常用线程 HandlerThre...

  • Java架构师阅读书单

    一、内功心法 Java基础: 《Java核心技术》《Java编程思想》《Effective Java》 多线程...

  • java学习路线

    javaSE java基础语法 java文件操作 java网络操作 java多线程 java数据库操作 java ...

网友评论

      本文标题:Java基础(12)——多线程

      本文链接:https://www.haomeiwen.com/subject/kkoyihtx.html