JAVA--高级基础开发

流过昼夜 提交于 2019-12-04 23:04:47

Day06【线程】

第一章

1.1 线程的基本概念

  1. 我们在之前,学习的程序在没有跳转语句的前提下,都是由上而下依次执行,只能做一件事 情,这样的程序我们也称为单线程程序;那么如果想要设计一个程序,边写代码边听歌,怎么设计呢?要解决上述问题,咱们得使用多进程或者多线程来解决。
  2. 并行:指两个或多个事件在同一时刻发生。(同时发生)。
  3. 并发:指两个或多个事件在同一个段时间内发生。

 

  1. 在操作系统中,安装了多个程序,并发指的是在一段时间内,宏观上有多个程序同时运行,这在CPU系统中,每一时刻只能有一个程序在执行,微观上这些程序是分时交替运行,只不过给人感觉是同时运行的,那是因为分时交替运行的时间是非常短的。
  2. 而在多个CPU操作系统中,则这些可以并发执行的程序可以分配到多个处理器上(CPU),实现多个任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样就可以完成多个程序同时执行,目前电脑市场上说的多核CPU,便是多核处理器,核数越多,并行处理的程序就越多,能大大提高电脑运行的效率。
  1. [注意] 单核处理器的计算机肯定是不能并行处理多个任务的,只能是多个任务在cpu上并发运行,同理,线程也是一样的,从宏观角度上理解,线程是并行运行的,但是从微观角度上理解,线程是串行运行的,即一个线程一个线程的去运行,当系统只用一个cpu时,线程会以某种顺序执行多个线程,我们把这种情况叫做线程调度。

 

1.2 线程与进程

  1. 进程: 指的是一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个线程,进程也是程序的一次执行过程。是系统运行的基本单位,系统运行一个程序,就是一个进程从系统中创建,运行到消亡的过程。
  2. 线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,一个进程中是可以有多个线程的,这个应用程序也可以成为多线程程序。
  3. 进程与线程的区别:
  • 进程:有独立的内存空间,进程中的数据存放空间是独立的。
  • 线程:共享进程的内存空间,线程中堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。

4、知识点扩展

因为一个进程中的多个线程是并发执行的,那么从微观角度上看,也是有先后顺序的,那个线程执行完全取决于CPU 的调度,程序猿是干涉不了的,这也就造成了多线程的随机性。

Java程序的进程里面至少包含两个线程,主线程也就是 main()方法线程,另外一个是垃圾

回收器线程,每当使用 Java 命令执行一个类时,实际都会启动一个JVM,每个JVM实际上就是在操作系统中启动了一个线程,java本身具备了垃圾的回收机制,所以在java运行时至少会启动两个线程,由于创经济一个线程的开销要比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑多线程,而不是创建多进程。

5、线程调度:计算机通常只有一个cpu时,在任意时刻只能执行一个计算机指令,每一个进程只有获取CPU 的使用权才能执行指令,所谓的多进程并发运行,从宏观角度上讲,其实就是多个进程轮流获得CPU 的使用权,分别执行各自的任务,所有可运行的线程都会在线程池中处于就绪状态等待CPU,JVM就负责线程的调度,JVM采用的是抢占模式调度,没有采用分时调度,因此造成采用多线程执行结果的随机性。

抢占模式:就是根据多个线程的优先级,计算每一个线程的总优先级,线程调度器就会让总优先级最高的这个线程来执行。

分时间片模式:所有的线程排成一个队列,线程调度器按照他们的顺序,给每个线程分配一段时间。

如果在时间片结束时,线程还没有执行完毕,则它的执行权会被调度器剥夺并分配给另一个线程。

如果线程在时间片前阻塞或者结束,则调度器立即进行切换。

 

1.3创建线程类

  1. Java使用java.lang.Thread类代表线程,所有的线程对象必须都是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序,java中通过继承Thread类来创建并启动多个线程。
  2. 步骤:
  • 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此把run方法称为线程的执行体。
  • 创建Thread子类的实例,即创建了线程对象。
  • 调用线程对象的start方法来启动线程,只有启动了线程,线程才有机会或的CPU 的使用权。

 

//Thread 线程的示例

public class Test01 {

    public static void main(String[] args) {

        //static Thread currentThread()返回对当前正在执行的线程对象的引用。

        System.out.println("获取当前正在执行的线程的名字:"+MytHread.currentThread().getName());

        //创建当前类的对象

        MytHread   my=new MytHread("李文杰");

        my.setName("wziy");

        my.start();

        //Thread.State getState()返回此线程的状态。

        System.out.println("该线程的状态:"+my.getState());//正在运行RUNNABLE

        System.out.println(my.getName());

        //boolean isAlive()测试这个线程是否活着。

        System.out.println("线程活着吗:"+my.isAlive());

 

        //创建一个线程

        for(int  j=0;j<10;j++){

            System.out.println("main主方法中的线程"+j);

        }

    }

}

//自定义一个类

class MytHread extends Thread{

    public MytHread(String name){

        super(name);

    }

    //重写run方法。来完成多个线程

    @Override

    public void run() {

       for(int i=0;i<10;i++){

           System.out.println("自定义类线程:"+i);

       }

    }

}

1.4多线程的原理

  1. 程序启动main方法的时候,java虚拟机启动一个进程,主方法main() 在main方法调用时被创建,随着调用线程的start 方法,另外一个新的线程也将启动,这样整个应用程序就存在多个线程。
  2. 多线程为什么可以完成并发执行?
  3. 多线程执行时,在栈内存中,其实每一个执行线程都有自己所属的栈内存空间,进行方法的压栈和弹栈。
  4. 当执行线程的任务结束了,线程自动在栈内存中释放,当所有的执行线程都结束了,那么进程也就结束了。

1.5 Thread类

  1. java.lang.Thread 类 API 中该类定义了有关线程的一系列方法,具体如下:
  2. 构造方法
  • public Thread():分配一个新的线程对象
  • public Thread(String name):分配一个指定名字的新的线程对象
  • public Thread(Runnable   target):分配一个带有指定目标新的线程对象
  • public Thread(Runnable target,String name):分配一个带有指定目标的线程对象并指定名字。
  1. 常用的方法:
  • Public  String  getName():获取当前线程的名称
  • public  void  start():启动线程,java 虚拟才有机会调用此线程的 run 方法
  • public  void  run():线程体。(父类方法)
  • public  static Thread  currentThread():返回当前正在执行的线程的对象引用

  //static Thread currentThread()返回对当前正在执行的线程对象的引用。

        System.out.println("获取当前正在执行的线程的名字:"+MytHread.currentThread().getName());

        //创建当前类的对象

        MytHread   my=new MytHread("李文杰");

        my.setName("wziy");

        my.start();

        //Thread.State getState()返回此线程的状态。

        System.out.println("该线程的状态:"+my.getState());//正在运行RUNNABLE

        System.out.println(my.getName());

        //boolean isAlive()测试这个线程是否活着。

        System.out.println("线程活着吗:"+my.isAlive());

 

        //创建一个线程

        for(int  j=0;j<10;j++){

            System.out.println("main主方法中的线程"+j);

        }

    }

}

//自定义一个类

class MytHread extends Thread{

    public MytHread(String name){

        super(name);

    }

    //重写run方法。来完成多个线程

    @Override

    public void run() {

       for(int i=0;i<10;i++){

           System.out.println("自定义类线程:"+i);

       }

    }

}

1.6 创建线程的第二种方式

  1. 采用java.lang.Runnable接口 也是非常常见的一种,我们只需要重写 run 方法即可。
  2. 步骤如下:
  • 定义Runnable接口的实现类,并重写run()方法.
  • 创建Runnable实现类的对象,并以对象作为Thread的targer来创建Thread对象。
  • 调用线程start方法来启动线程。

 

//创建线程的第二种用法

public class Test03 {

    public static void main(String[] args) {

        //创建子线程的对象

        Mylife   ff=new Mylife();

        //创建线程的对象,给子线程起个名字

        Thread  ss=new Thread(ff,"李文杰");

        //启动线程

        ss.start();

        for(int  j=0;j<10;j++){

            System.out.println("主线程:"+Thread.currentThread().getName()+j);

        }

    }

}

class Mylife implements Runnable{

    @Override

    public void run() {

        for(int  k=0;k<10;k++){

            System.out.println("子线程:"+Thread.currentThread().getName()+k);

        }

    }

}

【总结】

1、通过实现 Runnable 接口,使得该类有了多线程类的特征,run()方法是多线程程序的一个执

行目标,所有的多线程代码都在 run 方法里面,Thread 类实际上也是实现了 Runnable 接口的类。在启动多线程的时候,需要先通过 Thread 类的构造方法 Thread(Runnable target)来创建对象,然后调用 Thread 对象的 start()方法来运行多线程程序。实际上所有的多线程代码都是通过运行 Thread 类的 start()方法来运行的。因此,不管是继承Thread 类还是实现 Runnable 接口,最终还是通过 Thread 的对象的 API 来控制线程的。

2、Runnable 对象仅仅作为 Thread 对象的 targetRunnable 实现类包含 run()方法作为线程执 行体,而实际上的线程对象依然是 Thread,只是该 Thread 线程负责执行 target run 方法】

1.7 创建线程的第三种方式

  1. 通过匿名内部类的方式创建

//使用匿名内部类创建线程

public class Test02 {

    public static void main(String[] args) {

        //使用匿名内部类new接口实现类的对象

        Runnable r=new Runnable() {

            @Override

            public void run() {

                for(int i=0;i<10;i++){

                    System.out.println(Thread.currentThread().getName()+""+i);

                }

            }

        };

        // 把runnable接口对象传递过来,启动线程

        new Thread(r).start();

        for(int k=0;k<10;k++){

            System.out.println(Thread.currentThread().getName()+""+k);

        }

    }

}

1.8  Thread 和 Runnable 的区别

  1. 如果一个类继承了Thread类则不适合资源的共享,如果一个类实现了Runnable接口,则很容易实现资源的共享。
  2. 实现 Runnable 接口比继承 Thread 类所具有的优势:
  • 适合多个相同的程序线程去共享同一个资源。
  • 可以避免java中单继承的局限性。
  • 增加程序的健壮性,实现解耦的操作,代码可以被多个线程共享,代码和线程独立。
  • 线程池只能放入实现 Runnable 或 Callable 类的线程,不能直接放入继承 Thread 的类。

第二章  线程安全(线程同步)

2.1 线程安全

  1. 如果有多个线程同时运行,而这些线程可能会同时运行某一段代码,程序每次运行结果和单线程运行的结果是一样的,而且其它的变量的值也和预期的是一样的,这就是线程安全的。通过案例,演示线程安全问题:
  2. 用程序模拟铁路售票系统:实现通过四个售票点发售某日某次列车的 1000 张车票,一个售票点用一个线程表示。

//模拟铁路售票系统,实现通过4个售票点,发售某日某次列车的1000张车票,

// 一个售票点用一个线程表示

//线程不安全的示例

public class Test06 {

    public static void main(String[] args) {

        //创建当前线程类的对象

        Ticket3  t=new Ticket3();

        //创建线程,start 启动线程对象

        Thread  t1=new Thread(t,"窗口一");

        t1.start();

 

        Thread  t2=new Thread(t,"窗口二");

        t2.start();

 

        Thread  t3=new Thread(t,"窗口三");

        t3.start();

 

        Thread  t4=new Thread(t,"窗口四");

        t4.start();

    }

}

class Ticket3 implements Runnable{

    int  count=0;

    @Override

    public void run() {

        while (true){

            if(count<1000){

                System.out.println(Thread.currentThread().getName()+" 正在售出第:"+(count+1)+"张车票");

                count++;

            }else{

                break;

            }

        }

    }

}

 

发现程序出现了一个问题:

  1. 相同的票数,比如第 1 张票被卖出了四次,这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全
  2. 【线程安全问题都是由全局变量及静态变量引起的,若每个线程中对全局变量、静态变量只有读操作,一般来说,这个全局变量是线程安全的;
  3. 若有多个线程同时执行操作,一般需要考虑线程同步,否则的话就可能影响线程安全。】

2.2 线程同步

  1. 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。要解决上述多线程并发访问同一个资源的安全性问题,java 中提供了同步机制(synchronized)解决。
  2. 为了保证每个线程都能正常执行操作,java 引入了线程同步机制。
  3. 那么怎么使用呢?有三种方式完成同步操作。
    • 1 同步代码块
    • 2 同步方法
    • 3 锁机制

以上三个方法主要用来解决线程同步的问题。

2.3  同步代码块

  1. 同步代码块:synchronized 关键字用于方法中的某个区域,表示只对这个区域的资源实现同步安全的问题,也就是互斥访问。
  2. 格式:

Synchronized (同步锁){

  //需要同步的代码

}

  1. 同步锁:
  • 对象的同步锁只是一个概念,可以想象为对象上标识了一个锁。
  • 锁对象可以是任意类型
  • 多个线程对象,要使用同一把锁。
  1. 注意,在任何时候,只允许一个线程拥有同步锁,谁先拿到锁就进入代码块(抢占模式),其他的线程只能在外等着(Blocked);
  2. 通过以上案例,应用同步代码块 演示线程安全的问题:

//通过synchronized关键字来解决线程同步的问题 同步代码块

public class Test04 {

    public static void main(String[] args) {

      //创建当前线程类的对象

        Ticket  t=new Ticket();

        //创建线程,start 启动线程对象

        Thread  t1=new Thread(t,"窗口一");

        t1.start();

 

        Thread  t2=new Thread(t,"窗口二");

        t2.start();

 

        Thread  t3=new Thread(t,"窗口三");

        t3.start();

 

        Thread  t4=new Thread(t,"窗口四");

        t4.start();

    }

}

class Ticket implements Runnable{

    int  count=0;

    @Override

    public void run() {

        while (true){

            //通过synchronized关键字来解决线程同步的问题

            //那个代码需要同步,就要加在那个代码块中

            synchronized (this){

                if(count<1000){

                    System.out.println(Thread.currentThread().getName()+" 正在售出第:"+(count+1)+"张车票");

                    count++;

                }else{

                    break;

                }

            }

 

        }

    }

}

 

2.4  同步方法

  1. 同步方法:使用synchronized 关键字修饰的方法,叫做同步方法。 保证线程在执行该方法时,其他的线程只能在方法外候着。
  2. 同步锁是谁?
  3. 对于非 static 方法,同步锁就是 this
  4. 对于 static 方法,我们使用当前方法所在类的字节码对象(类名.class)
  5. 格式:

访问修饰符  synchronized  返回值类型  方法名(参数列表){

 

}

 

关于同步方法的示例:

//public  synchronized  void  Meth()同步方法的使用示例

 

public class Test05 {

    public static void main(String[] args) {

        //创建当前线程类的对象

        Ticket2  t=new Ticket2();

        //创建线程,start 启动线程对象

        Thread  t1=new Thread(t,"窗口一");

        t1.start();

Thread  t2=new Thread(t,"窗口二");

        t2.start();

Thread  t3=new Thread(t,"窗口三");

        t3.start();

Thread  t4=new Thread(t,"窗口四");

        t4.start();

    }

}

class Ticket2 implements Runnable{

    private int  count=0;

    private boolean  flag=true;

    @Override

    public void run() {

        while (flag){

            Meth();

        }

    }

    public  synchronized  void  Meth(){

        //通过同步方法来完成线程同步的问题

              if(count<1000){

                System.out.println(Thread.currentThread().getName()+" 正在售出第:"+(count+1)+"张车票");

                count++;

            }else{

                flag=false;

                System.out.println("票卖完了");

            }

        }

    }

 

2.5 锁机制( Lock)

  1. 位于java.util.concurrent.locks包中. Lock 锁提供了比 synchronized 代码块synchronized 方法更广泛的锁定操作,同步代码块和同步方法具有的功能 Lock 都有,除此之外更强大,更能体现面向对象。
  2. Lock 锁也称同步锁,加锁与释放锁方法化,如下:
  1. public  void  lock():加同步锁 
  2. public  void  unlock():释放同步锁

//锁机制同步锁 Lock 示例

public class Test07 {

    public static void main(String[] args) {

        //创建当前线程类的对象

        Ticket4  t=new Ticket4();

        //创建线程,start 启动线程对象

        Thread  t1=new Thread(t,"窗口一");

        t1.start();

        Thread  t2=new Thread(t,"窗口二");

        t2.start();

        Thread  t3=new Thread(t,"窗口三");

        t3.start();

        Thread  t4=new Thread(t,"窗口四");

        t4.start();

    }

}

class Ticket4 implements Runnable{

    private int  count=0;

    //添加锁机制

    private  Lock  lock=new ReentrantLock();

    @Override

    public void run() {

        while (true){

            //添加锁

            lock.lock();

            if(count<1000){

                System.out.println(Thread.currentThread().getName()+" 正在售出第:"+(count+1)+"张车票");

                count++;

            }else{

                break;

            }

            //释放锁

            lock.unlock();

        }

    }

}

第三章 线程的状态

3.1线程状态的概述

  1. 当线程被创建并启动以后,它并不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,有几种状态?在 API 中 java.lang.Thread.State 这个枚举中给出了六种线程状态。

线程的状态

 状态的状况

NEW(新建)

线程刚被创建,但是并未启动,还没有调用 start 方法

RUNNABLE(可运行)

线程可以在 Java 虚拟机中运行的状态,可能正在运行代码,也可能没有,这取决于操作系统的处理器

BLOCKED(锁阻塞)

当一个线程试图获取一个对象锁,而该对象锁被其它线程持有,则该线

程进入 Blocked 状态;当该线程持有锁时,该线程将变成 Runnable 状态。

 

WAITING

(无限限等待)

一个线程在等待另一个线程执行一个动作时,该线程进入 Waiting 状 态,进入这个状态后是不能自己唤醒的,必须等待另一个线程调用 nofiy

或者 nofiyAll 方法才能被唤醒

TIMED_WAITING

(计时等待)

同 waiting 状态,有几个方法有超时参数,调用它们将进入 Timed Waiting

状态,这一状态将一直保存到超时期满或者接收到唤醒通知。带有超时

参数的常用方法有 Thread.sleep(long mills)Object.wait()。设置时间。

TERMINATED

(被终止)

因为 run 方法正常退出而线程终止,或者因为没有捕获的异常终止 run

方法而导致线程终止。

3.2 计时等待

  1. Thread.sleep()方法,可以强制要求当前正在执行的线程休眠(暂停执行),以减慢线程。当我们调用 sleep 方法线程就进入了休眠状态(计时等待)。

/计时等待示例

public class Test09 {

    public static void main(String[] args) {

        Runnable  ss=new Runnable() {

            @Override

            public void run() {

                for(int  i=0;i<20;i++){

                    System.out.println("输出-----》"+i);

                    try{

                        Thread.sleep(1000);

                    }catch (InterruptedException ce){

                        ce.printStackTrace();

                    }

                }

            }

        };

        new  Thread(ss).start();

    }

}

  1. 进入 TIMED_WAITING 状态的一种常见情形是调用 sleep 方法,单独的线程也可以调用不一定非要有协作关系。
  2. 为了能够让其它线程有机会执行。
  3. sleep 与锁无关,线程睡眠到期自动苏醒,并返回到 Runnable 运行状态。
    1. Blockd 锁阻塞
  1. 举例说明:
  2. 线程 A 与线程 B 使用同一个锁,如果线程 A 读取到锁,线程 A 进入到 Runnable 状态,那么线程 B 就进入了 Blocked 锁阻塞状态。
    1. Waiting (无线等待)
  1. Waiting 状态在 API 中解释为:正在等待另一个线程执行特定动作的线程处于此状态。

public class Test10 {

    private static Object obj=new Object();

    public static void main(String[] args) {

          //演示 waiting

        new Thread(new Runnable() {

            @Override

            public void run() {

                while(true){

                    synchronized (obj){

                        System.out.println(Thread.currentThread().getName()+"== 获 取到锁对象,调用 wait 方法,进入 waiting 状态,释放锁对象");

                        try {

                            obj.wait();//无线等待

                        } catch (InterruptedException e) {

                            e.printStackTrace();

                        }

                        System.out.println(Thread.currentThread().getName()+"=== 从waiting 状态醒来,获取到锁对象,继续执行");

                    }

                }

            }

        },"等待线程").start();

       

        new Thread(new Runnable() {

            @Override

            public void run() {

                System.out.println(Thread.currentThread().getName()+"----等待 3 秒");

                try {

                    Thread.sleep(3000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

                synchronized (obj){

                    System.out.println(Thread.currentThread().getName()+"--- 获取到锁对象,调用 notify 方法,释放锁对象");

                            obj.notify();

                } }

        },"唤醒线程").start();

    }

}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!