Java多线程的应用

1、概述

  提到线程不得不提进行。由于线程是进程的一个执行单元。下面对线程和进程分别进行介绍。java

  一、进程

    进程是当前操做系统执行的任务,是并发执行的程序在执行过程当中分配和管理资源的基本单位,是一个动态概念,竟争计算机系统资源的基本单位。通常而言,如今的操做系统都是多进程的。设计模式

   进程的执行过程是线状的, 尽管中间会发生中断或暂停,但该进程所拥有的资源只为该线状执行过程服务。一旦发生进程上下文切换,这些资源都是要被保护起来的。安全

  二、线程

  线程,是进程的一部分,一个没有线程的进程能够被看做是单线程的。即:每一个进程中至少包含一个线程多线程

  线程自己是在CPU上执行的,CPU的每个核在同一时刻只能执行一个线程,但CPU在底层会对线程进行快速的轮询切换。并发

  三、线程的特色

   线程在执行任务的过程大概能够分为2大块:dom

  • 在CPU上执行
  • 和计算机的硬件进行交互。当线程和硬件进行交互(例如读取文件)是不占用CPU的。
  • 提升CPU利用率。理论上,当线程个数足够多的时候,CPU的利用率是可以到达100%。
  • 一个程序的主函数所在的类默认是一个单独的线程。

   2、JAVA中如何定义线程

   一、经过继承Thread,重写run方法,将要执行的逻辑放在run方法中,而后建立线程对象调用start方法来开启线程。示例以下:异步

public class ThreadDemo {

    public static void main(String[] args) {

        TDemo t1 = new TDemo("A");
        // 启动线程
        // start方法中会给线程作不少的配置
        // 配置完成以后会自动调用run方法执行指定的任务
        t1.start();
        // t1.run();
        TDemo t2 = new TDemo("B");
        t2.start();
        // t2.run();

    }

}

class TDemo extends Thread {

    private String name;

    public TDemo(String name) {
        this.name = name;
    }

    // 打印0-9
    // 线程要执行的任务就是放在这个方法中
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(name + ":" + i);
        }
    }
}

    二、实现Runnable,重写run方法,而后利用Runnable对象来构建Thread对象,调用start方法来启动线程。示例以下:ide

public class RunnableDemo {

    public static void main(String[] args) {

        RDemo r = new RDemo();
        // 包装 - 装饰设计模式
        Thread t = new Thread(r);
        t.start();

    }
}
class RDemo implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

  三、实现Callable<T>,重写call方法,经过线程池定义线程。示例以下:函数

public class CallableDemo {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        CDemo c = new CDemo();
        // 执行器服务 执行器助手
        ExecutorService es = Executors.newCachedThreadPool();
        Future<String> f = es.submit(c);
        System.out.println(f.get());
        es.shutdown();
    }

}

// 泛型表示要的结果类型
class CDemo implements Callable<String> {

    @Override
    public String call() throws Exception {
        return "SUCCESS";
    }

}

3、多线程的并发安全问题

 一、线程之间是相互抢占执行,并且抢占是发生在线程执行的每一步;当线程从新抢回执行权以后,会沿着上次被抢占位置继续向下执行,而不是从头开始执行this

 二、因为线程的抢占而致使出现了不合理的数据的现象:多线程的并发安全问题。

4、线程中的锁机制

   一、概述

    为了解决线程并发问题,引入了synchronized代码块,亦即同步代码块。同步代码块须要一个锁对象。

   二、锁对象及其特色

      锁对象要求被当前的全部线程都认识。共享资源,方法去中的资源和this均可以做为锁对象。

      当使用this做为锁对象的时候,要求利用同一个Runnable对象来构建不一样的Thread对象。

     示例以下:利用多线程实现卖票机制

package cn.tedu.thread;

import java.io.FileInputStream;
import java.util.Properties;

// 利用多线程机制模拟卖票场景
public class SellTicketDemo {
    public static void main(String[] args) throws Exception {
        // 利用properties作到改动数量可是不用改动代码的效果
        Properties prop = new Properties();
        prop.load(new FileInputStream("ticket.properties"));
        int count = Integer.parseInt(prop.getProperty("count"));
        // 利用ticket对象作到全部的线程共享一个对象
        Ticket t = new Ticket();
        t.setCount(count);
        // 表示四个售票员在分别卖票
        Thread t1 = new Thread(new Seller(t), "A");
        Thread t2 = new Thread(new Seller(t), "B");
        Thread t3 = new Thread(new Seller(t), "C");
        Thread t4 = new Thread(new Seller(t), "D");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

// 定义了线程类表示售票员
class Seller implements Runnable {

    private Ticket t;
    public Seller(Ticket t) {
        this.t = t;
    }

    @Override
    public void run() {
        // 锁对象 --- 须要指定一个对象做为锁来使用
        while (true) {
            // 因为全部的Seller线程都在卖票t,因此t是被全部线程都认识的
            // synchronized (t) {
            // 因为全部的Seller线程都是Seller类产生的,因此Seller类也是被全部线程都认识的
            // synchronized (Seller.class) {
            // synchronized (Thread.class) {
            synchronized ("abc") {
                if (t.getCount() <= 0)
                    break;
                try {
                    // 让当前线程陷入休眠
                    // 时间单位是毫秒
                    // 不改变线程的执行结果
                    // 只会把线程的执行时间拉长
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 票数减小1张
                t.setCount(t.getCount() - 1);
                // currentThread()获取当前在执行的线程
                // 获取线程的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "卖了一张票,剩余" + t.getCount());
            }
        }
    }
}

class Ticket {
    private int count;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

   三、线程的同步和异步

    同步:在同一时刻内资源/逻辑只被一个线程占用/执行。

    异步:在同一时刻内资源/逻辑能够被多个线程抢占使用。

   四、多线程死锁

     因为多个线程之间的锁造成了嵌套而致使代码没法继续执行,这种现象称之为死锁。

     咱们只能尽可能避免出现死锁,在实际开发中,会作死锁的检验;若是真的出现了死锁,会根据线程的优先级打破其中一个或者多个锁。

   死锁的示例以下:

package cn.tedu.thread;

public class DeadLockDemo {

    static Printer p = new Printer();
    static Scan s = new Scan();

    public static void main(String[] args) {

        // 第一个员工
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                synchronized (p) {
                    p.print();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s) {
                        s.scan();
                    }
                }
            }
        };
        new Thread(r1).start();
        // 第二个员工
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                synchronized (s) {
                    s.scan();
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (p) {
                        p.print();
                    }
                }
            }
        };
        new Thread(r2).start();
    }
}

// 表明打印机的类
class Printer {
    public void print() {
        System.out.println("打印机在吱呦吱呦的打印~~~");
    }
}

// 表明扫描仪的类
class Scan {
    public void scan() {
        System.out.println("扫描仪在哼哧哼哧的扫描~~~");
    }
}

5、线程的优先级

    一、Java中将线程的优先级分为1-10共十个等级。

    二、理论上,数字越大优先级越高,那么该线程能抢到资源的几率也就越大;但实际上,相邻的两个优先级之间的差异很是不明显;若是想要相对明显一点,至少要相差5个优先级。

  线程优先级示例以下:

   

public class PriorityDemo {

    public static void main(String[] args) {

        Thread t1 = new Thread(new PDemo(), "A");
        Thread t2 = new Thread(new PDemo(), "B");

        // 在默认状况下,线程的优先级都是5
        // System.out.println(t1.getPriority());
        // System.out.println(t2.getPriority());

        // 设置优先级
        t1.setPriority(1);
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}

class PDemo implements Runnable {
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        for (int i = 0; i < 10; i++) {
            System.out.println(name + ":" + i);
        }
    }
}

6、线程的等待唤醒机制

  一、利用标记为以及wait、notify、notifyAll方法来调用线程之间的执行顺序;

  二、wait、notify、notifyAll和锁有关,用那个对象做为锁对象使用,那么就用该锁对象来调用wait、notify。

   等待和唤醒示例以下: 

package cn.tedu.thread;

public class WaitNotifyAllDemo {
    public static void main(String[] args) {
        Product p = new Product();

        new Thread(new Supplier2(p)).start();
        new Thread(new Supplier2(p)).start();
        new Thread(new Consumer2(p)).start();
        new Thread(new Consumer2(p)).start();
    }
}

// 生产者
class Supplier2 implements Runnable {
    private Product p;

    public Supplier2(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (p) {
//由于线程被抢断后,会沿着中止出继续执行,由于用while循环强制对其进行判断,知足条件时才能执行
//不知足条件就让其等待
while (p.flag == false){ try { // 让当前线程陷入等待 p.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 计算本次生产的商品数量 int count = (int) (Math.random() * 1000); p.setCount(count); System.out.println("生产者生产了" + count + "件商品~~~"); p.flag = false;
//当多个线程执行时,要唤醒全部的线程,不然可能连续唤起一个线程,致使程序执行混乱 p.notifyAll(); } } } }
// 消费者 class Consumer2 implements Runnable { private Product p; public Consumer2(Product p) { this.p = p; } @Override public void run() { while (true) { synchronized (p) { while (p.flag == true){ try { p.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } int count = p.getCount(); p.setCount(0); System.out.println("消费者消费了" + count + "件商品~~~"); p.flag = true; // 唤醒在等待的线程 p.notifyAll(); } } } }

7、线程的状态

  线程从建立到开始消亡通常会经历以下几种状态:

8、守护线程

   一、概述

     守护其余的线程被称为守护线程,只要被守护的线程结束,那么守护线程就会随之结束。

   二、守护线程的特色

  • 一个线程要么是守护线程,要么是被守护线程
  • 守护线程能够守护其余的守护线程
  • 在Java中,最多见的一个守护线程是GC

守护线程的示例以下:

package cn.tedu.thread;

public class DaemonDemo {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(new Monster(), "小怪1号");
        Thread t2 = new Thread(new Monster(), "小怪2号");
        Thread t3 = new Thread(new Monster(), "小怪3号");
        Thread t4 = new Thread(new Monster(), "小怪4号");

        // 设置为守护线程
        t1.setDaemon(true);
        t2.setDaemon(true);
        t3.setDaemon(true);
        t4.setDaemon(true);

        t1.start();
        t2.start();
        t3.start();
        t4.start();

        for (int i = 10; i > 0; i--) {
            System.out.println("Boss掉了一滴血,剩余" + i);
            Thread.sleep(50);
        }
    }

}
//守护boss的小怪线程
class Monster implements Runnable {

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        for (int i = 1000; i > 0; i--) {
            System.out.println(name + "掉了一滴血,剩余" + i);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

  总结:sleep和wait的区别

   一、sleep:在使用的时候须要指定休眠时间,单位是毫秒,到点天然醒。在无锁状态下,会释放CPU;在有锁状态下,不释放CPU。

     sleep方法是一个静态方法,被设计在了Thread类上。

   二、wait:能够指定等待时间也能够不指定。若是不指定等待时间则须要被唤醒。wait必须结合锁使用,当线程在wait的时候会释放锁。wait方法设计在了Object类上。

9、线程产生和结束的场景

  一、线程产生的场景

  • 系统自启动:开机默认启动的程序;
  • 用户请求:QQ好友聊天;
  • 线程之间的启动:App软件之间带有的插件。

   二、线程结束的场景

  • 寿终正寝:线程天然结束
  • 他杀:被其余线程kill 
  • 意外:线程由于报错崩溃而退出  

10、JAVA虚拟机方法区和线程的关系

   一、类是存储在方法区中的,方法区是被全部的线程共享的空间。

    二、每个线程独有一个栈内存。

相关文章
相关标签/搜索