第1章 Java 多线程技能

来源:互联网 发布:ui设计 笔记本 知乎 编辑:程序博客网 时间:2024/06/05 10:17

第1章 Java 多线程技能

标签: Java多线程编程

《Java多线程编程核心技术》 个人笔记


  • 第1章 Java 多线程技能
    • 进程和多线程的概念及线程的优点
    • 使用多线程
    • currentThread方法
    • isAlive方法
    • sleep方法
    • getId方法
    • 停止线程
      • 停止不了的线程
      • 判断线程是否是停止状态
      • 能停止的线程 异常法
      • 在沉睡中停止
      • 能停止的异常暴力停止
      • 方法stop与javalangThreadDeath异常
      • 释放锁的不良后果
      • 使用return停止线程
    • 暂停线程
      • suspend与resume方法的使用
      • suspend与resume方法的缺点独占
      • suspend与resume方法的缺点不同步
    • yield方法
    • 线程的优先级
      • 优先级具有继承特性
      • 优先级具有规则性
      • 优先级具有随机性
    • 守护线程


本章关键技术点:

  • 线程如何启动
  • 如何使线程暂停
  • 如何使线程停止
  • 线程的优先级
  • 线程安全的相关问题

进程和多线程的概念及线程的优点

  • 进程(Process):是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体
  • 线程:可以理解为是在进程中独立运行的子任务。
  • 使用多线程(异步)的优点:可以在同一时间内运行更多不同种类的任务。
  • 单任务的特点:排队执行,也就是同步。缺点是CPU利用率低。

使用多线程

    public static void main(String[] args) {        //获取当前线程的名字        System.out.println(Thread.currentThread().getName());    }
  • 实现多线程的方式主要有两种,一种是继承Thread类,另一种是实现Runnable接口
  • 在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。
  • Thread.java类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法,随机异步执行
  • 如果调用thread.run()则是同步执行,此线程是交给main()主线程来调用run()方法,所以必须等run()中的代码执行完才能执行后面的代码
  • 执行的start()的顺序并不代表线程的启动顺序
  • 也就是start()方法是异步,run()则只是像普通方法一样,是同步的

  • 根据Thread.java的构造函数,构造函数支持传入一个Runnable接口的对象,也可以传入一个Thread类的对象,所以完全可以将一个Thread对象中的run()方法交给其他线程进行调用

    public static void main(String[] args) {        Runnable runnable=new MyRunnable();        Thread thread=new Thread(runnable);        thread.start();        System.out.println("main!");    }
  • 自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这在多个线程之间进行交互时是很重要的一个技术点。
    1. 不共享数据的情况(每个线程有各自的count变量)
public class MyThread extends Thread {    private int count = 5;    public MyThread(String name) {        super();        this.setName(name);    }    @Override    public void run() {        super.run();        while (count > 0) {            count--;            System.out.println("由" + this.currentThread().getName()                    + " 计算,count=" + count);        }    }}public class Run {    public static void main(String[] args) {        MyThread a = new MyThread("A");        MyThread b = new MyThread("B");        MyThread c = new MyThread("C");        a.start();        b.start();        c.start();    }}
  1. 共享数据的情况(多个线程可以访问同一个变量)
public class MyThread extends Thread {    private int count=5;    @Override    synchronized public void run() {        super.run();            count--;            System.out.println("由 "+this.currentThread().getName()+" 计算㣬count="+count);    }}public class Run {    public static void main(String[] args) {        MyThread mythread=new MyThread();        Thread a=new Thread(mythread,"A");        Thread b=new Thread(mythread,"B");        Thread c=new Thread(mythread,"C");        Thread d=new Thread(mythread,"D");        Thread e=new Thread(mythread,"E");        a.start();        b.start();        c.start();        d.start();        e.start();    }}
  • 如果有多个线程同时访问,那么一定会出现非线程安全。(输出的顺序不是4,3,2,1,0)
  • 通过在run方法前加入synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。
  • synchronized可以在任意对象及方法上加锁,而加锁的这段代码称为“互斥区”或“临界区”

currentThread()方法

  • currentThread()方法可以返回代码段正在被哪个线程调用的信息
public class CountOperate extends Thread {    public CountOperate() {        System.out.println("CountOperate---begin");        System.out.println("Thread.currentThread().getName()="                + Thread.currentThread().getName());        System.out.println("this.getName()=" + this.getName());        System.out.println("CountOperate---end");    }    @Override    public void run() {        System.out.println("run---begin");        System.out.println("Thread.currentThread().getName()="                + Thread.currentThread().getName());        System.out.println("this.getName()=" + this.getName());        System.out.println("run---end");    }}public class Run {    public static void main(String[] args) {        CountOperate c = new CountOperate();        Thread t1 = new Thread(c);        t1.setName("A");        t1.start();    }}/*----结果-----CountOperate---beginThread.currentThread().getName()=mainthis.getName()=Thread-0CountOperate---endrun---beginThread.currentThread().getName()=Athis.getName()=Thread-0run---end*/

isAlive()方法

  • 方法isAlive()的功能是判断当前的线程是否处于活动状态。活动状态就是线程已经启动且尚未终止。线程处于正在正在运行或准备开始运行的状态,就认为线程是“存活”的。

sleep()方法

方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”(this.currentThread()返回的线程)休眠(暂停执行)。

getId()方法

  • 取得线程的唯一标识

停止线程

  • Thread.stop()方法是不安全的,已经被废弃。
  • 在java中有以下3中方法可以终止正在运行的线程:
    1. 可以使用退出标识,是线程正常退出,也就是当run方法完成后线程终止
    2. 使用stop(),但该方法不安全,已被废弃,同被废弃的还有suspend,resume
    3. 使用interrupt()方法中断线程

停止不了的线程

  • interrupt()方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程

判断线程是否是停止状态

  • this.interrupted()方法:测试当前线程是否已经中断,当前线程是指运行this.interrupted()方法的线程。执行后具有将状态标志清除为false的功能
  • this.isInterrupted():测试线程Thread对象是否已经是中断状态,但不清除状态标识。

能停止的线程 ———— 异常法

  • 可在线程中用for语句来判断一下线程是否是停止状态,如果是则中断for循环,继续执行for后面的语句
public class MyThread extends Thread {    @Override    public void run() {        super.run();        for (int i = 0; i < 500000; i++) {            if (this.interrupted()) {                System.out.println("已经是停止状态了,我要退出了!");                break;            }            System.out.println("i=" + (i + 1));        }        System.out.println("我被输出,如果此代码是for又继续输出,线程并未停止!");    }}    public static void main(String[] args) {        try {            MyThread thread = new MyThread();            thread.start();            Thread.sleep(2000);            thread.interrupt();        } catch (InterruptedException e) {            System.out.println("main catch");            e.printStackTrace();        }        System.out.println("end!");    }
  • 如果不想让for后面的语句继续运行,则可以 try catch :
    public void run() {        super.run();        try {            for (int i = 0; i < 500000; i++) {                if (this.interrupted()) {                    System.out.println("已经是停止状态了,我要退出了!");                    throw new InterruptedException();                }                System.out.println("i=" + (i + 1));            }            System.out.println("我在for下面");        } catch (InterruptedException e) {            System.out.println("进入MyThread.java类中run方法中的catch");            e.printStackTrace();        }    }

在沉睡中停止

  • 如果在sleep状态下停止某一线程,会进入catch语句,并且清除停止状态值,使之变成false.
public class MyThread extends Thread {    @Override    public void run() {        super.run();        try {            System.out.println("run begin");            Thread.sleep(200000);            System.out.println("run end");        } catch (InterruptedException e) {            System.out.println("在沉睡中停止,进入catch!"+this.isInterrupted());            e.printStackTrace();        }    }}    public static void main(String[] args) {        try {            MyThread thread = new MyThread();            thread.start();            Thread.sleep(200);            thread.interrupt();        } catch (InterruptedException e) {            System.out.println("main catch");            e.printStackTrace();        }        System.out.println("end!");    }
  • 如果先interrupte在sleep会报异常

能停止的异常————暴力停止

  • 使用stop()停止线程则是非常暴力的

方法stop()与java.lang.ThreadDeath异常

  • 方法stop()已经作废,因为如果强制停止则有可能使一些清理性的工作得不到完成。另外一个情况就是对锁定的对象进行了“解锁”,导致数据得不到同步的处理

释放锁的不良后果

  • 使用stop()方法释放锁将会给数据造成不一致的结果。

使用return停止线程

  • 将方法interrupt()与return结合也能实现停止线程的效果。
  • 不过还是建议使用“抛异常”的方法来实现线程停止,因为在catch块中还可以将异常向上抛,使线程停止的事件得以传播
public class MyThread extends Thread {    @Override    public void run() {            while (true) {                if (this.isInterrupted()) {                    System.out.println("ֹͣ停止!");                    return;                }                System.out.println("timer=" + System.currentTimeMillis());            }    }}    public static void main(String[] args) throws InterruptedException {        MyThread t=new MyThread();        t.start();        Thread.sleep(2000);        t.interrupt();    }

暂停线程

suspend与resume方法的使用

  • suspend()方法暂停线程,resume()方法回复线程使用

suspend与resume方法的缺点————独占

  • 使用suspend与resume方法时,如果使用不当,极易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象
  • 还有一种独占锁的情况也要格外注意:当程序运行到println()方法内部时,同步锁未被释放,这导致当前PrintStream对象的println()方法一直呈现“暂停”状态,并且“锁未释放”,而main()方法中的代码System.out.println()迟迟不能打印

suspend与resume方法的缺点————不同步

  • 在使用suspend和resume方法时也容易出现因为线程的暂停而导致数据不同步的情况。
    public void setValue(String u, String p) {        this.username = u;  //这句跟下面        if (Thread.currentThread().getName().equals("a")) {            System.out.println("ͣ停止a线程");            Thread.currentThread().suspend();        }        //这句应该须要同步才行,但是在执行这句之前已经停止了,导致不同步        this.password = p;     }

yield方法

  • yield方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片
    public void run() {        long beginTime = System.currentTimeMillis();        int count = 0;        for (int i = 0; i < 50000000; i++) {            //Thread.yield();            count = count + (i + 1);        }        long endTime = System.currentTimeMillis();        System.out.println("用时" + (endTime - beginTime) + "毫秒");    }

线程的优先级

  • 在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
  • 设置线程优先级有助于帮助“线程规划期”确定下一次选择哪一个线程来优先执行。
  • 设置线程的优先级使用setPriority()方法
  • 在java中,线程的优先级分为1~10这十个等级

优先级具有继承特性

  • 线程的优先级具有继承性,比如A线程启动B线程,则B线程 的优先级与A一样

优先级具有规则性

  • 高优先级的线程总是大部分先执行完

优先级具有随机性

  • 也就是说,优先级较高的线程不一定每一次都先执行完

守护线程

  • java有两种线程,用户线程和守护线程
  • 守护线程是一种特殊的线程,它的特性有“陪伴”的含义,当进程中不存在非守护线程了,则守护线程自动销毁。(如垃圾回收线程)
  • Daemon的作用是为其线程的运行提供遍历服务,守护线程最典型的应用是GC(垃圾回收器),它就是一个很称职的守护者。
public class MyThread extends Thread {    private int i = 0;    @Override    public void run() {        try {            while (true) {                i++;                System.out.println("i=" + (i));                Thread.sleep(1000);            }        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }}    public static void main(String[] args) {        try {            MyThread thread = new MyThread();            thread.setDaemon(true);         //设置守护线程            thread.start();            Thread.sleep(5000);            System.out.println("我离开thread对象也不再打印了");            //此时main函数运行结束,守护线程也就自动销毁了        } catch (InterruptedException e) {            e.printStackTrace();        }    }
0 0