多线程(不全)

多线程

程序、进程、线程的概念

程序:硬盘上的一个静态的可执行文件

进程:

正在内存中运行的程序(是动态的), 一个程序可以启动若干进程(像一个类创建若干对象)

进程间通信很复杂, 不好控制.

线程:进程中的子任务(通过多线程实现的多任务, 好处是线程间通信非常容易)

ps:

每个Java程序都有一个隐含的主线程: main 方法

因为线程直接可以被CPU调度, 所以多线程是最大化利用CPU, 并提升任务效率.

何时需要多线程:
  • 程序需要同时执行两个或多个任务。

  • 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。

  • 需要一些后台运行的程序时。

    多线程也称为高并发

Java中多线程的创建和使用

Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来实现。

  • Thread类的特性

    • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体

    • 通过该Thread对象的start()方法来调用这个线程

  • 构造方法

    • Thread():创建新的Thread对象

    • Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法

    • Thread(Runnable target, String name):创建新的Thread对象

创建并启动线程(), 只学前2种
*     1) 实现的方式, 实现Runnable接口, 这种方式好, 灵活, 避免单继承
*         1) 写一个具体类, 实现Runnable接口, 并实现接口中的run()方法, 这个方法就是线程入口(线程体)
*         2) 创建上面的具体类对象, 并把它作为实参, 再创建Thread对象, 这是最关键的线程对象(是一个栈)
*         3) 调用Thread对象的start()方法, 启动子线程.
*
*     2) 继承的方式, 继承Thread类, 不好, 虽然简单, 但是结构不灵活.
*         1) 写具体类, 继承Thread, 并且必须重写run(), 因为父类Thread中的run什么也没有做.
*         2) 创建具体类对象, 是一个子类对象, 所以就相当于创建了Thread对象
*         3) 调用对象的start.
#### 线程中的常用方法


创建并启动线程(), 只学前2种

线程的停止:通过通知的方式将子线程停止

  1. 实现Runnable接口 (线程的一个规范):不确定

    这种方式好, 灵活, 避免单继承

    • 1) 写一个具体类, 实现Runnable接口, 并实现接口中的run()方法, 这个方法就是线程入口(线程体)

    public class HelloRunner implements Runnable {
       private int count = 500;
       @Override
       public void run() { // 无返回值, 无参, 简单
           for (int i = 0; i < count; i++) {
               System.out.println(Thread.currentThread().getName() + " : " + i);
          }
      }
    • 2) 创建上面的具体类对象, 并把它作为实参, 再创建Thread对象, 这是最关键的线程对象(是一个栈)

    • 3) 调用Thread对象的start()方法, 启动子线程.

    public static void main(String[] args) {
           //2) 创建上面的具体类对象, 并把它作为实参, 再创建Thread对象, 这是最关键的线程对象(是一个栈)
           Runnable runner = new HelloRunner();
           Thread thread = new Thread(runner); // 创建子栈
           thread.setName("子线程"); //设计线程的名称
           //3) 调用Thread对象的start()方法, 启动子线程.
           //thread.run(); //如果调用了run就是一个普通的方法调用, 不会多线程
           thread.start(); // 激活子栈, 并把run()压入子栈的栈中.

           //Thread curr = Thread.currentThread(); // 此方法压入哪个栈, 就返回哪个栈的线程对象 用来获取栈的名称
           //curr.setName("主线程");
           // 主线程继续
           /*
           for (int i = 0; i < 500; i++) {
               System.out.println(curr.getName() + " : " + i);
           }*/
           HelloRunner2 r2 = new HelloRunner2();
           Thread thread2 = new Thread(r2);
           thread2.setName("子线程2");
           thread2.start();
      }

     

  2. 继承Thread类 线程的实体类

    不好, 虽然简单, 但是结构不灵活

    Thread实现图片

 

Thread类的主要方法

  • void start(): 启动线程(激活子栈),并执行对象的run()方法

  • void run() : 线程体, 它必须是被动调用.

  • void setName(String name)

  • String getName()

    // 静态方法, 就是类方法, 和调用者无关.

  • static Thread currentThread(); 返回把此方法压入的栈的线程对象

  • int getPriority() :返回线程优先值

  • void setPriority(int newPriority) :改变线程的优先级

  • join(): 线程进行插队行为

    • 在线程A中, 调用B线程的join 方法, 会导致线程A阻塞 , 直到线程B结束

  • static void sleep(long millis) 让线程进入睡眠状态

  • 解除sleep睡眠有2种方式 :

    • 1) 时间到了自然醒

    • 2) 必须由别的线程调用我的interrupt(), 打断我的睡眠, 我被打断时会抛出异常

线程的调度与设置优先级

线程的调度

调度策略:

时间片(大锅饭式):理想主义每个线程都是平等的,在给定的时间内,每个线程都能参与,并且与优先级无关,大家将时间平分

java调度方法:

同优先级线程组成先进先出队列(先到先服务),仍然使用抢占式策略 对高优先级,使用优先调度的抢占式策略

线程的优先级

线程的优先级控制

  • MAX_PRIORITY(10);

  • MIN _PRIORITY (1);

  • NORM_PRIORITY (5);

  • 涉及的方法:

    • getPriority() :返回线程优先值

    • setPriority(int newPriority) :改变线程的优先级

    • 线程创建时继承父线程的优先级

综合练习

/*
编写程序,在main方法中创建一个线程。线程每隔一定时间(200ms以内的随机时间)产生一个0-100之间的随机整数,
打印后将该整数放到集合中;
共产生100个整数,全部产生后,睡眠30秒,然后将集合内容打印输出;
在main线程中,唤醒上述睡眠的线程,使其尽快打印集合内容。(打断子线程的30秒睡眠)
*/
//线程每隔一定时间(200ms以内的随机时间)产生一个0-100之间的随机整数,
public class RandomRunner implements Runnable {

   private boolean flag = false;

   public boolean isFlag() {
       return flag;
  }

   @Override
   public void run() {
       List<Integer> list = new ArrayList<>();
       //共产生100个整数,
       for (int i = 0; i < 100; i++) {
           int rand = (int)(Math.random() * 100);
           System.out.println(Thread.currentThread().getName() + " : " + rand);
           //打印后将该整数放到集合中;
           list.add(rand);
           int time = (int)(Math.random() * 200)
           // 全部产生后,睡眠30秒
           try {
               Thread.sleep(time);
          } catch (InterruptedException e) {
               System.out.println("在睡[" + time + "]毫秒时被打断");
          }
      }
       //观察者的判断依据
       flag = true;
       System.out.println("要睡30秒了....");
       try {
           Thread.sleep(30 * 1000);
      } catch (InterruptedException e) {
           System.out.println("在长睡30秒时被打断, 严重不爽");
      }
//然后将集合内容打印输出;;
       Iterator<Integer> iterator = list.iterator();
       while (iterator.hasNext()) {
           System.out.println(iterator.next());
      }
  }
}

 

线程的生命周期

image-20210121155235522

  • 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态

  • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件

  • 运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能

  • 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态

  • 死亡:线程完成了它的全部工作或线程被提前强制性地中止

线程分类::

一种是守护线程,一种是用户线程

  • 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。

  • 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。

  • Java垃圾回收就是一个典型的守护线程。

  • 若JVM中都是守护线程,当前JVM将退出。

线程同步(关键字:synchronized。一种同步锁)

不同步产生的问题:并发:同一个对象被多个线程同时操作,例如:火车的最后一张票,银行取钱

synchronized重点:需要再整理

同步方法:

关键字:synchronized 保证数据对象只能被方法访问

包括synchronized方法和synchronized块

synchronized 控制对对象的访问 每个对象对应一把锁 方法一旦执行 就独占该锁

锁的对象应该是变化的量,需要增添改

注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

Synchronized的用法(简单介绍)

同步块:synchronized(obj){}

obj称之为同步监视器

  • obj可以是任何对象,但是推荐使用共享资源作为同步监视器

  • 同步方法中无需指定同步监视器 因为同步方法的同步监视器就是这个对象本身,就是this,或者class

  • 同步监视器执行过程:

    1.第一个线程访问,锁定同步监视器,执行其中代码

    2.第二个线程访问,发现同步监视器被锁定,无法访问

    3.第一个线程访问完毕,解锁同步监视器

    4.第二个线程访问。发现同步监视器没有锁,然后锁定并访问

JUC

死锁

Lock(锁)

Lock对象 reentrantlock

不安全

线程的通信

posted @ 2021-01-25 13:48  WZZR  阅读(127)  评论(0)    收藏  举报