java多线程

1、多线程的基本概念

线程指进程中的一个执行场景,也是执行流程,那么进程和线程有什么区别呢?

  • 每个进程是一个应用程序,都有独立的内存空间
  • 同一个进程中的线程共享其进程中的内存和资源(共享的内存是堆内存和方法区内存,栈内存不共享,每个线程有自己的栈内存
    1.1、什么是进程?
    一个进程对应一个应用程序。例如:在windows操作系统启动word就表示启动了一个进程。在java开发环境下启动JVM,就表示启动了一个进程。现代计算机是支持多进程的,在同一个操作系统中,可以同时启动多个进程。
    1.2、多进程有什么作用?
    单进程计算机只能做一件事情。
    玩电脑,一边玩游戏(游戏进程)一边听音乐(音乐进程)。
    对于单核计算机来讲,在同一个时间点上做一件事情。由于计算机将在“游戏进程”和“音乐进程”之间频繁的切换执行,切换速度极高,人类感觉游戏和音乐在同时进行。
    多进程的作用不是提高执行速度,而是提高CPU的使用率。进程和进程之间的内存是独立的。
    1.3、什么是线程
    线程是一个进程中的执行场景,一个进程可以启动多个线程。
    1.4、多线程有什么作用
    多线程不是为了提高执行速度,而是提高应用程序的使用率。例如让一个应用被多个客户端使用。
    可以给现实世界中的人一种错觉:感觉多个线程在同时并发执行
    1.5、java程序的运行原理
    java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也等于启动了一个进程。该进程会自动启动一个“主线程”,然后主线程去调用木某个类的main方法。所以main方法运行在主线程中。

2、线程的创建和启动

java虚拟机的主线程入口是main方法,用户可以自己创建线程,方式有两种:

  • 继承Thread类
  • 实现Runnable接口(推荐使用Runnable接口)
    2.1、通过继承Thread类实现线程
    简例:
/**
 * 通过继承Thread实现多线程的方式:
 *      第一步:继承java.lang.Thread;
 *      第二步:重写run方法。
 * 三个知识点:
 *      如何定义线程?
 *      如何创建线程?
 *      如何启动线程? 
 */
public class ThreadTest01 {
    public static void main(String[] args) {
        // 创建线程
        Thread t = new Processor();
        // 启动线程
        t.start();// 这段代码执行瞬间结束。告诉JVM再分配一个新的栈给t线程。
                  // run不需要程序员手动调用,系统线程启动之后自动调用run方法
        
        // t.run(); // 只是普通的方法调用,这样做程序只有一个线程,run方法结束后,下面的程序才能继续执行。
        
        // 这段代码在主线程中运行
        for (int i = 0; i < 10; i++) {
            System.out.println("main-->" + i);
        }
        
        // 有了多线程之后,main方法结束之时主线程中没有方法栈帧了
        // 但是其他线程或者其他栈中海油栈帧
        // main方法结束,程序可能还在运行
    }
    
}

// 定义一个线程类
class Processor extends Thread {
    // 重写run方法
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("run-->" + i);
        }
    }
}

2.2、通过实现Runnable实现线程

简例:

/**
 * java中通过 Runnable接口实现线程
 *      第一步:写一个类实现java.lang.Runnable;接口
 *      第二步:实现run方法
 *
 */
public class ThreadTest02 {
    public static void main(String[] args) {
        // 创建线程
        Thread t = new Thread(new Processor2());
        // 启动
        t.start();
    }
}
// 推荐使用这种方式。因为一个类实现接口之外保留了类的继承
class Processor2 implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("run-->" + i);
        }
    }
}

2.3、UML图描述线程的生命周期

线程的生命周期有五个状态:新建、就绪、运行、阻塞、消亡。下图为5个状态的关系
线程状态示意图

2.4、线程的调度与控制

通常我们计算机只有一个CPU,CPU在某一时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行命令。在单CPU的机器上线程不是并行运行的,只有在多个CPU上线程才可以并行运行。java虚拟机要负责线程的调度,取得CPU的使用权,目前有两种调度模式:分时调度模式和抢占式调度模式,java使用抢占式调度模式。

  • 分时调度模式:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
  • 抢占式调度模式:优先级高的线程获得的CPU时间片相对多一些,如果线程的优先级相同,则随机选择一个。
    常用方法简例
/**
 * 线程常用方法:
 *      1、Thread.currentThread() 获取当前线程对象
 *      2、t1.setName("t1")       给线程起名字
 *      3、t.getName()            获取线程的名字
 *      4、t1.setPriority(10)     更改优先级
 *      5、t1.getPriority()       获取优先级
 *      6、Thread.sleep(毫秒)     阻塞当前线程,腾出CPU给其它线程让位,是一个静态方法,阻塞的是当前线程,和创建对象无关
 *      7、t2.interrupt()         打断线程休眠,使用异常处理机制打断。建议使用变量的设定来正确终止线程
 *      8、Thread.yield()         让位,给同优先级的线程,让位的时间不固定,是一个静态方法。
 *      9、Thread.join()          线程合并,成员方法。将线程与当前线程合并。等待合并过来的线程死亡。 
 * 线程的优先级
 *      优先级高的获取的CUP时间片相对多一些
 *      优先级:1-10
 *      最低:1
 *      最高:10
 *      默认:5
 */
public class ThreadTest03 {
    public static void main(String[] args) {
        // 优先级常量
        System.out.println(Thread.MAX_PRIORITY);// 10
        System.out.println(Thread.NORM_PRIORITY);// 5
        System.out.println(Thread.MIN_PRIORITY);// 1
        // 获取当前线程对象
        Thread t = Thread.currentThread();// 此处t保存的内存地址指向的线程是“主线程对象”
        // 获取线程的名字getName()
        System.out.println(t.getName());// main
        Thread t1 = new Thread(new Processor03());
        t1.setName("t1");// 设置线程的名字setName()
        Thread t2 = new Thread(new Processor03());
        t2.setName("t2");
        // 获取优先级
        System.out.println(t1.getPriority());// 5
        System.out.println(t2.getPriority());// 5
        // 设置优先级
        t1.setPriority(1);
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}
class Processor03 implements Runnable {
    // Thread中的run()方法不抛出异常,所以重写run()方法后,在run()方法的声明位置上不能使用throws
    // 所以run()方法中的异常只能用try...catch...
    public void run() {
        // Thread t = Thread.currentThread();// 此处t保存的内存地址指向的线程是“t1线程对象”
        // System.out.println(t.getName());// Thread-0-->t1\Thread-1-->t2。线程默认名字规律Thread-*,可以改变名字。
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
} 

使用变量的设定来正确终止线程

/**
 * 使用变量的设定来正确终止线程
 *
 */
public class ThreadTest04 {
    public static void main(String[] args) throws InterruptedException {
        Processor04 p = new Processor04();
        Thread t = new Thread(p);
        t.setName("t1");
        t.start();
        // 主线程等待5秒
        Thread.sleep(5000);
        // 5秒后终止p线程
        // Thread.interrupted();// 打断当前的sleep,抛出异常,后续方法人会执行
        p.flag = false;
    }
}
class Processor04 implements Runnable {
    boolean flag = true;
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (flag) {
                try {Thread.sleep(1000);} catch (InterruptedException e) {}
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            } else {
                return;
            }
        }
    }
}

3、线程同步机制

为什么引入同步概念?
以下取款实例说明为什么引入同步。
异步的取款代码:

/**
 * t1和t2
 * 异步编程模式:t1线程和t2线程各自执行自己的,两个之间谁也不等谁。
 * 同步编程模式:两个线程执行,t1线程必须等t2线程执行结束后才执行。
 * 
 * 什么时候要用同步呢?为什么要引入同步呢?
 *      1、为了数据安全。尽管应用程序的使用率降低,但为了保证数据是安全的,必须加入线程同步机制。
 *      
 *      2、什么条件下使用线程同步?
 *          第一、必须是多线程环境
 *          第二、多线程环境共享同一个数据
 *          第三、共享的数据涉及到修改操作。
 *  
 *  以下程序演示取款操作。演示程序中不使用线程同步机制,多线程对同一个账户进行操作,会出现的问题!
 */
public class ThreadTest06 {
    public static void main(String[] args) {
        // 创建一个公共的账户
        Account act = new Account("actno-1", 5000.0);
        // 创建线程对同一个账户取款
        Thread t1 = new Thread(new Processor06(act));
        Thread t2 = new Thread(new Processor06(act));
        
        t1.start();
        t2.start();
    }
}

// 取款线程
class Processor06 implements Runnable {
    // 账户
    Account act;
    // 构造方法
    Processor06 (Account act) {
        this.act = act;
    }
    public void run() {
        act.withdraw(1000.0);
        // 得到错误的结果
        System.out.println("取款1000.0成功,余额:" + act.getBalance());// 取款1000.0成功,余额:4000.0
                                                                       // 取款1000.0成功,余额:4000.0
    }
}

// 账户实体
class Account {
    private String actno;
    private double balance;
    public Account() {}
    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }
    public String getActno() {
        return actno;
    }
    public void setActno(String actno) {
        this.actno = actno;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
    // 对外提供一个取款方法
    public void withdraw(double money) {
        double after = balance - money;
        // 延时
        try {Thread.sleep(1000);} catch (InterruptedException e) {}
        //更新
        this.setBalance(after);
    }
}

使用同步机制的取款代码synchronize:

/**
 *  改变以上代码,以下程序使用线程同步机制保证数据的安全
 */
public class ThreadTest07 {
    public static void main(String[] args) {
        // 创建一个公共的账户
        Account2 act = new Account2("actno-1", 5000.0);
        // 创建线程对同一个账户取款
        Thread t1 = new Thread(new Processor07(act));
        Thread t2 = new Thread(new Processor07(act));
        
        t1.start();
        t2.start();
    }
}

// 取款线程
class Processor07 implements Runnable {
    // 账户
    Account2 act;
    // 构造方法
    Processor07 (Account2 act) {
        this.act = act;
    }
    public void run() {
        act.withdraw(1000.0);
        // 得到想要的结果,但是牺牲了部分时间。
        System.out.println("取款1000.0成功,余额:" + act.getBalance());// 取款1000.0成功,余额:4000.0
                                                                       // 取款1000.0成功,余额:3000.0
    }
}

// 账户实体
class Account2 {
    private String actno;
    private double balance;
    public Account2() {}
    public Account2(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }
    public String getActno() {
        return actno;
    }
    public void setActno(String actno) {
        this.actno = actno;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
    // 对外提供一个取款方法
    public void withdraw(double money) {
        // 把需要同步的代码,放到同步语句块中。参数填共享对象。
        /**
         * 原理:t1和t2线程
         *      t1线程执行到此处,遇到synchronize关键字,就会找this的对象锁,
         *      如果找到this对象锁,则进入同步语句块中执行程序。当同步语句块中的代码执行结束后,
         *      t1归还this对象锁
         *      
         *      在t1线程执行同步语句块的过程中,吐过t2线程也过来执行同步语句块遇到synchronize关键字,
         *      所以也去找this的对象锁,但是对象锁被t1线程持有,只能等待this对象的锁归还后再执行。
         * 		如果synchronize关键字加载方法上,同样找的是this对象。
         * 
         *     当加载synchronize的方法是静态方法时使用的是类锁,类锁也只有一个,如果两个方法都添加synchronize
         *     就算不共享对象,后执行的方法也要等类锁归还后执行,和对象锁无关。
         *     
         */
        synchronized (this) {
            double after = balance - money;
            // 延时
            try {Thread.sleep(1000);} catch (InterruptedException e) {}
            //更新
            this.setBalance(after);
        }
    }
}

死锁简单例子:

/**
 * 对象锁:死锁
 */
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new Thread(new T1(o1, o2));
        Thread t2 = new Thread(new T2(o1, o2));
        t1.start();
        t2.start();
    }
}

class T1 implements Runnable {
    Object o1;
    Object o2;
    T1(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run() {
        synchronized(o1) {
            try {Thread.sleep(1000);} catch (InterruptedException e) {}
            synchronized(o2) {// 此时o2被另一个线程锁住,需要等待释放对象锁
                
            }
        }
    }
}

class T2 implements Runnable {
    Object o1;
    Object o2;
    T2(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run() {
        synchronized(o2) {
            try {Thread.sleep(1000);} catch (InterruptedException e) {}
            synchronized(o1) {// 此时o1被另一个线程锁住,需要等待释放对象锁
                
            }
        }
    }
}

4、守护线程

从线程分类上可以分为:用户线程(以上都是用户线程),另一个是守护线程。守护线程伴随用户线程,只有所有的用户线程结束生命周期,守护线程才会结束生命周期,例如java中的垃圾回首器就是一个守护线程,只有应用程序中所有的线程结束,它才结束。
实例:

/**
 * 守护线程
 * 其他所有的用户线程结束,则守护线程退出!
 * 守护线程一般都是无限执行的
 * t1.setDaemon(true)  用户线程修改为守护线程
 */
public class ThreadTest08 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Processor08();
        t1.setName("t1");
        // 将t1这个用户线程修改为守护线程
        t1.setDaemon(true);
        t1.start();
        // 主线程main执行结束后,没有其它用户线程,则守护线程将自动退出
        for (int i = 0; i <10; i++) {
            System.out.println(Thread.currentThread().getName() + "----->" + i);
            Thread.sleep(1000);
        }
    }
}

class Processor08 extends Thread{
    public void run() {
        int i = 0;
        while(true) {
            System.out.println(Thread.currentThread().getName() + "----->" + i);
            i++;
            try {Thread.sleep(500);} catch (InterruptedException e) {}
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值