Java线程

来源:互联网 发布:淘宝商城运营客服 编辑:程序博客网 时间:2024/06/10 10:33

Java线程Thread


1. 线程相关概念

程序     一段静止的代码进程     正在运行的(一组任务)程序线程     将一个进程划分为多个执行单元,每个执行单元称为一个线程多进程     在同一时间内,多个进程貌似同时执行(cup时间片切换)多线程     在同一时间内,多个线程貌似同时执行(cup时间片切换)

2. 线程的创建和启动的两种方式

  1. 创建

    方式一:继承Thread,并重写run方法方式二:实现Runnable接口,重写run方法
  2. 启动

    方式一的启动:调用Thread的start方法方式二的启动:创建Thread对象,指定Runable子类对象参数,并调用start方法
  3. 示例

    • 方式一

    创建:

    class MyThread1 extends Thread{    @Override    public void run() {    }}

    启动:

    MyThread1 t1=new MyThread1();t1.start();
    • 方式二

    创建:

    class MyThread2 implements Runnable{@Overridepublic void run() {}

    }

    启动:

    Thread t2 = new Thread(new MyThread2());t2.start();
  4. 为什么调用的是start,执行的却是run方法呢?

    • start表示启动该线程,使其成为一条单独的执行流,背后,操作系统会分配线程相关的资源,每个线程会有单独的程序执行计数器和栈,操作系统会把这个线程作为一个独立的个体进行调度,分配时间片让它执行,执行的起点就是run方法。
    • 如果不调用start,而直接调用run方法呢?屏幕的输出并不会发生变化,但并不会启动一条单独的执行流,run方法的代码依然是在main线程中执行的,run方法只是main方法调用的一个普通方法。
  5. 区别

    • 1.实现Runnable接口的方式,比较适合于多个线程共享(访问)同一资源的时候;

    • 2.实现Runnable接口的方式,避免了单继承的局限性,更加灵活;

    • 3.继承Thread的方式,启动线程的代码比较简洁。

3. 线程的停止

  1. 控制执行条件

    • 添加 一个boolean类型的循环标记,默认为true;
    • 在run方法中使用此循环标记做循环;
    • 添加一个公共的更改循环标记的方法;
    • 希望停止该线程时,调用此公共方法即可。
  2. 使用interrupt方法中断线程。

4. 线程的常用方法

    currentThread  获取当前线程    setName        为线程起名字    getName        获取线程名    setPriority    设置线程优先级    getPriority    获取线程优先级    sleep          在指定的毫秒数内让当前正在执行的线程休眠    interrupt      中断线程。    yield          暂停当前正在执行的线程对象,并执行其他线程。    join           等待该线程终止。阻塞调用线程,直到该线程终止或经过了指定时间为止。    setDaemon      将该线程标记为守护线程或用户线程。    isAlive        测试线程是否处于活动状态。

5. 线程分类和优先级

  1. 在Java中有两类线程:User Thread(用户线程)Daemon Thread(守护线程)

    • Daemon的作用是为其他线程的运行提供便利服务,比如垃圾回收线程就是一个很称职的守护者。
    • User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
    • 值得一提的是,守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程
    • 可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。。
  2. 线程的优先级用1-10之间的整数表示,数值越大优先级越高,默认优先级为5。

6. 线程的生命周期

  1. 新建

    MyThread t = new MyThread();
  2. 就绪

    t.start();
  3. 运行

    抢到cpu使用权
  4. 阻塞

    调用sleep、wait、yield等

    5 死亡

    线程停止

7. 线程的同步

  1. 问题引入

    • 为什么要用到线程的同步?
    • java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
  2. 解决办法

    • 所谓同步就是多个操作在同一时间段内只能有一个线程进行,其他线程只能等此线程完成之后才可以执行。
    • 为需要同步的代码上一把”锁”,Java中每一个对象都可以看作一把锁。Java中通过同步代码块和同步方法来用对象上锁。

    • 同步前提:

      • (1)多个线程访问同一资源时才会有同步的概念;
      • (2)多个线程使用的锁必须是同一个,才能达到同步的效果。
  3. 同步实现方式

    • Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量。
    • volatile

      • Java 语言中的 volatile变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的
      • 将 volatile变量作为状态标志使用,比如用它修饰的boolean变量用作控制循环。因为很可能会从循环外部调用改变该值的方法,即在另一个线程中改变该boolean值,因此,需要执行某种同步来确保正确实现该变量的可见性。
    • (1)同步代码块

      synchronized(锁){        需要同步的代码块,即多条操作共享数据的语句。}
    • (2)同步方法

      synchronized 权限修饰符 返回值类型 方法名(参数){}

      注意:

        1. 静态方法的同步锁是当前类.class对象
        1. 非静态方法的同步锁是当前对象this
    • (3)窗口售票示例代码(同步代码块)

      public class TestTicket2 {    public static void main(String[] args) {        TicketSaleWindow2 t1 = new TicketSaleWindow2();        new Thread(t1).start();        new Thread(t1).start();        new Thread(t1).start();        new Thread(t1).start();    }}class TicketSaleWindow2 implements Runnable {    private int tick = 100;    public void run() {        while (true) {            synchronized (this) {                if (tick > 0) {                    try {                        Thread.sleep(10);// 使得当前线程失去CPU执行权                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName() + "卖" + tick-- + "票");                } else {                    break;                }            }        }    }}
    • 注意

      • 1、不要在run方法前面加synchronized;
      • 2、一般不要在循环前面同步。
      • 这样做会使得一个线程全部执行完,跟其他线程没关系了。。。
    • 使用重入锁实现线程同步

    • 在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized具有相同的基本行为和语义,并且扩展了其能力。

    • ReentrantLock类的常用方法有:

      ReentrantLock() : 创建一个ReentrantLock实例 lock() : 获得锁 unlock() : 释放锁 
    • 实例

       class Bank {    private int account = 100;    //需要声明这个锁    private Lock lock = new ReentrantLock();    public int getAccount() {        return account;    }    //这里不再需要synchronized     public void save(int money) {        lock.lock();        try{            account += money;        }finally{            lock.unlock();//释放锁        }    }}

8. 线程的通信

  1. 线程通信的目标是使线程间能够互相发送信号。另一方面,线程通信使线程能够等待其他线程的信号。
  2. 线程间通信依赖Object类有wait和notify方法。

    • wait       当前线程等待notify     唤醒或通知 同一个锁上的 其他线程(单个)notifyAll  唤醒或通知 同一个锁上的 其他所有线程(多个)
    • java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.illegalMonitorStateException(非法监视状态异常)异常。
  3. 通信的前提

    • 1、在线程同步的基础上;
    • 2、wait、notify和当前同步块的锁对象是同一个;
    • 3、使用notify()之前使用了wait()则不会被执行,所以使用notify唤醒其他线程需在本线程使用wait之前。
  4. 死锁

    • 是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又 想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。

    • 避免死锁要尽量避免synchronized的嵌套

  5. 线程通信经典案例

    • 生产者消费者问题

      public class ProducerConsumerProblem {    public static void main(String[] args) {        HouseWare hw = new HouseWare();        new Consumer(hw).start();        new Worker(hw).start();        new Consumer(hw).start();        new Worker(hw).start();    }}class Worker extends Thread {    private HouseWare hw;    public Worker(HouseWare hw) {        this.hw = hw;    }    public void run() {        while (true) {            hw.add();        }    }}class Consumer extends Thread {    private HouseWare hw;    public Consumer(HouseWare hw) {        this.hw = hw;    }    public void run() {        while (true) {            hw.sale();        }    }}class HouseWare extends Thread {    private int count = 0;    public synchronized void add() {        while (count >= 10) {            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        // 生产        count++;        System.out.println(Thread.currentThread().getName() + "生产了一台,库存:" + count);        // 通知消费者消费        // this.notify();//唤醒一个,适用一个生产者和一个消费者        this.notifyAll();// 唤醒所有,适用多个生产者和多个消费者    }    public synchronized void sale() {        while (count <= 0) {            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        // 消费        count--;        System.out.println(Thread.currentThread().getName() + "消费一台,库存:" + count);        // 通知生产者生产        // this.notify();        this.notifyAll();    }}

9. 线程池涉猎

  1. 什么是线程池?

    线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。

  2. 使用线程池的好处

    • 1.重用存在的线程,减少对象创建、消亡的开销,性能佳。
    • 2.可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
    • 3.提供定时执行、定期执行、单线程、并发数控制等功能。
  3. Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

  4. java.lang.Object
    继承者 java.util.concurrent.Executors

    • Executors类提供了创建不同类型线程池的方法。
    • ExecutorService:真正的线程池接口。
    • ThreadPoolExecutor:ExecutorService接口的默认实现。
  5. 线程池种类

    • java.util.concurrent.Executors类的API提供大量创建连接池的静态方法,通过Executors提供四种线程池,分别为:

      • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
      • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
      • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
      • newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
    • newFixedThreadPoolnewSingleThreadExector

      import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TestThreadPool2 {    public static void main(String[] args) {        // (1)创建一个可重用固定线程数的线程池        ExecutorService pool = Executors.newFixedThreadPool(2);        // 创建实现了Runnable接口的对象,Thread自然也实现了Runnable接口        Thread t1 = new MyThread2();        Thread t2 = new MyThread2();        Thread t3 = new MyThread2();        Thread t4 = new MyThread2();        // 将线程放入线程池中执行        pool.execute(t1);        pool.execute(t2);        pool.execute(t3);        pool.execute(t4);        // 关闭线程池        pool.shutdown();        // (2)创建一个单线程线程池        ExecutorService pool1 = Executors.newSingleThreadExecutor();        Thread t11 = new MyThread2();        Thread t12 = new MyThread2();        Thread t13 = new MyThread2();        pool1.execute(t11);        pool1.execute(t12);        pool1.execute(t13);        pool1.shutdown();    }}class MyThread2 extends Thread {    @Override    public void run() {        /*         * (1)结果一种示例:只有两个线程。。         * pool-1-thread-1执行。。。         * pool-1-thread-1执行。。。         * pool-1-thread-1执行。。。         * pool-1-thread-2执行。。。         * 只有两个线程         *          * (2)结果示例:         * pool-1-thread-1执行。。。         * pool-1-thread-1执行。。。         * pool-1-thread-1执行。。。         * 只有一个线程,单例         */        System.out.println(Thread.currentThread().getName() + "执行。。。");    }}
    • newCachedThreadPool

      package com.lzg.java3;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class TestThreadPool3 {    public static void main(String[] args) {        // (3) 创建一个可缓存线程池        ExecutorService pool = Executors.newCachedThreadPool();        MyThread3 t;        for (int i = 0; i < 30; i++) {            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            t = new MyThread3();            pool.execute(t);        }        pool.shutdown();    }}class MyThread3 extends Thread {    @Override    public void run() {        /*         * (3)结果示例:只有线程1和线程2两个         * pool-1-thread-1执行。。。         * pool-1-thread-2执行。。。         * pool-1-thread-1执行。。。         * pool-1-thread-2执行。。。         * .......         */        /*         * 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。         */        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + "执行。。。");    }}
    • newScheduledThreadPool

      package com.lzg.java3;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;public class TestThreadPool4 {    public static void main(String[] args) {        // (4)创建一个定长线程池,支持定时及周期性任务执行。        ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);        Thread t = new MyThread4();        Thread t1 = new MyThread4();        Thread t2 = new MyThread4();        Thread t3 = new MyThread4();        /*         * 注意:         * 调用execute方法执行没有延迟效果,ScheduleExectorService接口的schedule()方法可以设置延迟时间         */        pool.schedule(t, 3, TimeUnit.SECONDS);        pool.schedule(t1, 4, TimeUnit.SECONDS);        pool.schedule(t2, 5, TimeUnit.SECONDS);        pool.schedule(t3, 6, TimeUnit.SECONDS);        pool.shutdown();    }}class MyThread4 extends Thread {    @Override    public void run() {        /*         * (4)结果一种:各自延迟对应秒数         * pool-1-thread-1 delay different seconds         * pool-1-thread-2 delay different seconds         * pool-1-thread-3 delay different seconds         * pool-1-thread-1 delay different seconds         *          */        System.out.println(Thread.currentThread().getName() + " delay different seconds");    }}
0 0
原创粉丝点击