关于Java多线程基础的回顾

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_25859403/article/details/51628428
 因为想深入的对Java线程进行进一步的了解,所以,线程的基础部分做了一下回顾。
  • 一、基本概念

1.程序:程序是指对指令、数据以及它们之间组织形式的描述。
2.进程:进程是指是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。
3.线程:线程是程序中一个单一的顺序控制流程,简单理解就是程序中的一个执行单元。

问题1:程序和进程的区别与联系?

   1)区别:a. 进程是程序的一次运行活动,属于一种动态的概念。程序是一组有序的静态指令,是一种静态的概 念。
            b.程序可以作为一种软件资源长期保持着,而进程则是一次执行过程,它是暂时的,是动态地产生和终止的。
   2)联系:a: 进程为应用程序的运行实例,是应用程序的一次动态执行。进程可理解为正在运行的程序。
            b.一个进程可能对应着一个或多个程序;同样,一个程序的启动可能伴随着一个或多个进程的产生。

问题2:进程和线程的区别和联系?

   1)区别:一个进程是一个独立的运行环境,它可以被看作一个程序或者一个应用。 线程是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。

   2)联系:线程是进程的一个子集,一个进程中可能有多个进程。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源(这些资源是什么呢?),但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

  • 二、线程的实现

    在Java中线程实现主要有两种方式:
    **继承Thread类,重写run()方法
    **实现Runnable接口

    第一种方式:继承Thread

    用到构造方法:
    public Thread() :无参构造
    public Thread(String name):带参构造,参数为线程对象的name

      步骤:
         1.定义Thread类的子类,并重写run()方法。run()方法的方法体就是该线程的执行体。
         2.创建Thead子类的实例,即创建线程对象。
         3.线程调用start()启动线程。
    

代码:略


第二种方式:实现

用到构造方法:
public Thread(Runnable target) :传入Runnable的实现类对象

步骤:
 1.定义Runnable的实现类,并重写run()方法。run()方法的方法体就是该线程的执行体。
 2.创建Runnable的实现类的对象,在使用上述构造方法创建线程对象时作为参数传入。
 3.调用start()启动线程。

代码:略略


问题3:到底使用两种实现方式中的哪一种呢?

 答:
    首先,从实现的难易程度上讲,继承Thread类的方式要比实现Runnable接口的方式简单,
         也比较容易理解。
    其次,从Java语言“单继承,多实现”的角度来说。一旦让一个类通过继承Thread类的方式,
               那么这个类将不能继承其他类;而通过实现Runable接口来实现,则该类可以继承一个其他的类。
                    从这一角度来说,第二种方式更为灵活。

    那么,到底使用哪一种呢?一般来说,通常使用第二种方式来实现线程的创建。

三、线程的调度

1.相关概念
线程调度: 指系统为线程分配CPU使用权的过程,主要分为两种协同式线程调度和抢占式线程调度。
协同式线程调度的多线程操作系统: 线程的执行时间有线程本身来控制,只有当线程自身执行完毕时,才会通知系统切换到其他线程。
抢占式线程调度的多线程操作系统: 线程有系统来分配执行的时间,线程的切换不由线程自身决定。

2 . Java使用的线程调度方式是抢占式,虽然Java采用抢占式线程调度方式,线程不能决定自己的执行时间,但有的时候,我们有确实需要让某个线程执行的时间多一点,怎么呢?我们可以通过为线程设置优先级来实现。

Java总共为线程提供了十个优先级别(1-10,事实上,API只提供了三个常量分别对应1、5和10),我们通过setPriority(int)方法为线
程设置优先级。

  ***一般情况,同为就绪状态的多个线程,优先级越高的线程越容易被系统选择先执行;如果优先级相同,则会随机选择执行。

问题4:同为就绪状态的多个线程,优先级高的线程一定会先执行吗?

    答:不一定。原因如下:
       对某些操作系统而言,我们通过setPriority()为线程设置优先级可能是无效的。
    **Java线程是通过映射到系统的原生线程上来实现的,Java线程的优先级可能并不能和系统提供的线程优先级一一对应。
    ** 优先级可能被操作系统改变,大多数操作系统的线程调度器实际上执行的是在战略的角度上对线程的优先级做临时操作。
    ** Java的setPriority()方法只应用于局部的优先级。换句话说,你不能在整个可能的范围内设定优先级。
      归根结底,java线程的调度还得靠操作系统。不要假定高优先级的线程一定先于低优先级的线程执行,不要有逻辑依赖于线程优先级,否则可能产生意外结果

  • 四、线程的控制

1.join(加入)
public final void join()throws InterruptedException
等待该线程终止,也就是说等待该线程执行完,其他线程才能执行。
需要注意的是,此方法需要在start()方法后调用;否则,无效。

2.yield(让步)
public static void yield()
暂停当前正在执行的线程对象,并执行其他线程。其目的是让线程互相让步,以达到交互执行的目的。

3.sleep(休眠)
public static void sleep(long millis)throws InterruptedException
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行), 此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。

4.interrupt(中断)
public void interrupt()
中断线程。

5.setDaemon
public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用

说明:守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。

问题5:sleep()和yield()方法的区别(参考线程的生命周期图)?

    两者都会让线程让出CPU的使用权,但是有两点不同:
      调用sleep()方法会让线程在指定的时间内处于阻塞状态 ,处于阻塞(休眠)中的线程,在休眠结束之前一定不能再次获取CPU的执行权,即使休眠结束也只是重新回到就绪状态。
      调用yield()后,线程会回到就绪状态,重新等待系统的调度,假如,这个线程的优先级特别高(高于同处于就绪状态的其它线程) 根据我们前面说的,这个线程极有可能立即被执行,这样就没有达到调用yield()的目的。            

五、线程的生命周期
1)新建:当使用new关键字创建一个线程对象时,该线程就处于新建状态。
2)就绪:线程对象调用start()方法后,处于就绪状态。
3)运行: 当就绪的线程获得cpu,开始执行run()方法时,处于运行状态。
4)堵塞:
*线程调用sleep()方法主动放弃其占用的cpu资源
*线程调用一个堵塞式I/O方法,在该方法返回之前,处于堵塞状态。
*该线程正试图获得同步监视器,但该同步监视器正被其它线程持有。
*线程正在等待某个通知—notify()

5)死亡:
*run()方法或者call()方法执行完成,线程正常结束。
*线程抛出一个未捕获的Exception或者Error。
*直接调用线程的stop()或interrupt()中断线程


  • 六、线程安全

    1.什么是线程安全问题?

 当多个线程访问同一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方代码不必作其它的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。---摘自《Java并发编程实践》

2.一般一下几个角度判断程序是否存在线程安全问题:
a.当前程序是否存在多个线程。
b.这多个线程之间是否共享数据。

3.同步代码块和同步方法

1)同步代码块

  synchronized(锁对象){
      .....

     }


2)同步方法
   将一个方法用  synchronized修饰

需要注意的是:

  a.同步代码块的锁对象可以使适合对象
  b.同步代码块中的代码越少越好。
  c.同步方法分为非静态方法和静态方法,前者的锁对象的是this,后者的多对象的是  ClassName.class

4.ReentrantLock
一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

```
   class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() { 
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
}

5.wait()、nitify()和nitifyAll()

**wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
等待会让此线程处于阻塞状态,并释放掉同步监视器(锁对象)。
该方法只能在同步方法或者同步代码块中调用。

* nitify()*
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。直到当前线程放弃此对象上的锁定,才能继续执行被唤醒 的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方 面没有可靠的特权或劣势。
此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:

    通过执行此对象的同步实例方法。 
    通过执行在此对象上进行同步的 synchronized 语句的正文。 
    对于 Class 类型的对象,可以通过执行该类的同步静态方法。 
一次只能有一个线程拥有对象的监视器。

**nitifyAll()
唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前线程放弃此对象上的锁 定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此 对象的下一个线程方面没有可靠的特权或劣势。


问题6:wait()和sleep() 的区别

  a.wait()非静态方法 ,sleep()是静态方法
  b.wait()不必指定等待时间,而sleep()必须指定休眠时间。
  c.尽管两个方法都会造成线程阻塞,但wait()必须用于同步方法或者同步代码块,并且一旦调用所处线程就会释放掉同步监视器对象。 而sleep()并不会释放掉同步监视器对象

  • 七、附上Java线程的等待唤醒机制代码举例

    **Person类

   package thread1;

public class Person {
    private String name;
    private int age;
    private boolean flag; // 默认情况是没有数据,如果是true,说明有数据

    /**
     * 设置
     * @param name
     * @param age
     */
    public synchronized void set(String name, int age) {
        // 如果有数据,就等待
        if (this.flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 设置数据
        this.name = name;
        this.age = age;

        // 修改标记
        this.flag = true;
        this.notify();
    }

    /**
     * 获取
     */
    public synchronized void get() {
        // 如果没有数据,就等待
        if (!this.flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 获取数据
        System.out.println(this.name + "---" + this.age);

        // 修改标记
        this.flag = false;
        this.notify();
    }
}

**SetThread类

package thread1;

public class SetThread implements Runnable {

    private Person p;
    private int x = 0;

    public SetThread(Person p) {
        this.p = p;
    }

    public void run() {
        while (true) {
            if (x % 2 == 0) {
                p.set("张三", 11);
            } else {
                p.set("李四", 22);
            }
            x++;
        }
    }
}

**GetThread

package thread1;

public class GetThread implements Runnable {
private Person p;

public GetThread(Person p) {
this.p = p;
}

public void run() {
while (true) {
p.get();
    }
  }
}

*测试类*

package thread1;
public class PersonDemo {
    public static void main(String[] args) {
        //创建资源
        Person p = new Person();

        //设置和获取的类
        SetThread st = new SetThread(p);
        GetThread gt = new GetThread(p);

        //线程类
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);

        //启动线程
        t1.start();
        t2.start();
    }
}

*测试结果*
这里写图片描述

  • 八、参考书籍

    《JAVA并发编程实践》
    内容简介 · · · · · ·
    《JAVA并发编程实践》随着多核处理器的普及,使用并发成为构建高性能应用程序的关键。Java 5以及6在开发并发程序中取得了显著的进步,提高了Java虚拟机的性能以及并发类的可伸缩性,并加入了丰富的新并发构建块。在《JAVA并发编程实践》中,这些便利工具的创造者不仅解释了它们究竟如何工作、如何使用,还阐释了创造它们的原因,及其背后的设计模式。
    作者简介 · · · · · ·
    本书作者都是Java Community Process JSR 166专家组(并发工具)的主要成员,并在其他很多JCP专家组里任职。Brian Goetz有20多年的软件咨询行业经验,并著有至少75篇关于Java开发的文章。Tim Peierls是“现代多处理器”的典范,他在BoxPop.biz、唱片艺术和戏剧表演方面也颇有研究。Joseph Bowbeer是一个Java ME专家,他对并发编程的兴趣始于Apollo计算机时代。David Holmes是《The Java Programming Language》一书的合著者,任职于Sun公司。Joshua Bloch是Google公司的首席Java架构师,《Effective Java》一书的作者,并参与著作了《Java Puzzlers》。Doug Lea是《Concurrent Programming》一书的作者,纽约州立大学 Oswego分校的计算机科学教授。

猜你喜欢

转载自blog.csdn.net/qq_25859403/article/details/51628428