Java——多线程入门篇

1.线程概述

几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程,当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程。

1.1线程和进程
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程,线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。线程的执行是抢占式的,也就是说,当前运行的线程在任何时候都可能被挂起,以便另外一个线程可以运行。
归纳起来可以这么说,操作系统可以同时执行多个任务,每个任务就是进程,进程可以同时执行多个任务,每个任务就是线程。

2.线程的创建和启动

2.1继承Thread类创建线程
(1)定义Thread的子类,重写run方法;
(2)创建子类的实例,及线程对象;
(3)调用线程对象的start方法启动该线程;

public class FirstThread extends Thread{

    private int i;

    @Override
    public void run() {
        for (; i < 100; i++) {
            //当线程继承thread类时,直接调用this即可获取当前线程
            System.out.println(getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //调用thread的currentThread方法获取当前的线程
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 20) {
                //创建并启动第一个线程
                new FirstThread().start();
                //创建并启动第一个线程
                new FirstThread().start();
            }
        }
    }
}

运行结果:
在这里插入图片描述
2.2实现Runnable接口创建线程类
(1)定义Runnable接口的实现类,重写该接口的run方法;
(2)创建Runnable实现类实例,并以此作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start方法启动该线程;

public class SecondThread implements Runnable {
    private int i;

    @Override
    public void run() {
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //调用thread的currentThread方法获取当前的线程
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 20) {

                SecondThread st = new SecondThread();
                //创建并启动第一个线程
                new Thread(st, "新线程1").start();
                //创建并启动第一个线程
                new Thread(st, "新线程2").start();
            }
        }
    }
}

结果如下:
在这里插入图片描述
2.3.使用Callable创建线程
Callable接口是Runnable接口的增强版,提供的call方法可以作为线程的执行体,但call方法比run方法功能更强大。
(1)call方法可以有返回值;
(2)call方法可以声明抛出异常;

2.4创建线程方式的三种方法对比
实现Runnable接口和Callable接口:
(1)线程可以继承其他类;
(2)多个线程共享同一个target对象,所以非常适合多个相同线程来处理同一个资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想;
(3)编程复杂,如果需要访问当前的线程,则必训使用Thread.currentThread方法。

采用Thread类创建线程:
(1)不能再继承其他的父类;
(2)编写简单,无需使用Thread.currentThread方法

3.线程的生命周期

在这里插入图片描述

4.控制线程

4.1 join线程
Thread提供了让一个线程等待另一个线程完成的方法——join方法。当在某个程序执行流中调用其他线程的join方法时,调用线程被阻塞,直到被join方法加入的join线程执行完为止。

public class JoinThrend extends  Thread{
    public JoinThrend(String name) {
        super(name);
    }

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

    public static void main(String[] args) throws InterruptedException {
        new JoinThrend("新线程").start();
        for (int i = 0; i < 100; i++) {
            if (i == 20) {
                JoinThrend jt = new JoinThrend("被join的线程");
                jt.start();
                jt.join();
            }
            System.out.println(Thread.currentThread().getName() + " " + i);
        }

    }
}

结果如下:
在这里插入图片描述
4.2 线程睡眠: sleep
如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread了类的静态sleep方法来实现。

import java.util.Date;

public class SleepTest {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i <10 ; i++) {
            System.out.println("当前时间:" + new Date());
            Thread.sleep(1000);
        }
    }
}

结果如下:
在这里插入图片描述
4.3线程让步:yield
yield方法是Thread类提供的一个静态方法,他可以让当前正在执行的线程的暂停,但他不会阻塞该线程,他只是将该线程转入就绪状态。

public class YieldTest  extends Thread{
    public YieldTest(String name ){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(getName() +" " + i);
            if (i==20){
                Thread.yield();
            }
        }
    }

    public static void main(String[] args) {
        YieldTest yt1 = new YieldTest("高级");
        yt1.setPriority(Thread.MAX_PRIORITY);
        yt1.start();
        YieldTest yt2 = new YieldTest("低级");
        yt2.setPriority(Thread.MIN_PRIORITY);
        yt2.start();

    }
}

结果如下:
在这里插入图片描述

5.线程同步

5.1同步代码块
java的多线程支持引入了同步监视器,使用同步监视器的通用方法就是同步代码块,同步代码块的语法格式如下:

synchronized(obj){
    ...
    //此处的代码就是同步代码块
        }

上面语法格式中synchronized后括号里的obj就是同步监视器,上面代码的含义是:线程开始执行同步代码块之前,必须先获得同步监视器的锁定。
注意:任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。

5.2同步方法
java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字修饰某个方法,则该方法称为同步方法。对于synchronized修饰的实例方法(非static)而言,无须显示的指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。
可变类的线程安全是以减低程序的运行效率作为代价的,为了减小线程安全所带来的负面影响,程序法可以采用如下策略:
(1)不要对线程安全类的所有方法都进行同步,支队那些对改变竞争资源(竞争资源就是共享资源)的方法进行同步。
(2)如果可变类有两种运行环境:单线程环境和多线程环境,则可以为该可变类提供两种版本,即线程不安全版本和线程安全版本。在单线程环境中使用线程不安全版本以保证性能,在多线程环境中使用线程安全版本。如StringBuilder和StringBuffer。

5.3同步锁(Lock)

5.4死锁
当两个线程相互等待对方释放同步监视器时就会发生死锁,java虚拟机没有监测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

5.5线程通信
假设现在系统中有两个线程,这两个线程分别代表取钱者和存钱者,——现在假设系统有一种特殊的要求,系统要求存钱者和取钱者不断重复存钱和取钱的动作,而且要求每当存钱者将钱存入指定的账户后,取钱者就立即取出这笔钱。不允许存钱者连续两次存钱,也不允许取钱者连续两次取钱。
为了实现这种功能,可以借助于Object类的wait方法、notify方法、notifyAll方法,这三个方法并不属于Thread类,而是属于Object类。
wait():导致当前线程等待,直到其他线程调用同步监视器的notify方法或notifyAll方法来唤醒该线程。
notify():唤醒在此同步监视器上等待的单个线程。
motifyAll():唤醒在此同步监视器上等待的所有线程。
实例:

import java.util.Objects;

public class Account {
    //封装账户编号、账户余额
    private String accountNo;
    private double balance;
    //标示账户是否已有存款的旗标
    private boolean flag = false;
    public Account(){};

    public Account(String accountNo, double balance) {
        this.accountNo= accountNo;
        this.balance = balance;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }
    //因为账户余额不能随便修改,只为balance提供getter方法
    public double getBalance() {
        return balance;
    }

    public synchronized void draw(double drawAmount) {
        try {
            //如果flag为假,表明账户中还没有人存钱进去,取钱方法阻塞
            if (!flag) {
                wait();
            } else {
                //执行取钱操作
                System.out.println(Thread.currentThread().getName() + "取钱: " + drawAmount);
                balance-=drawAmount;
                System.out.println("账户余额为: " + balance);
                //将标示账户是否已有存款的旗标设为false
                flag = false;
                //唤醒其他线程
                notifyAll();
            }

        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
    public synchronized void deposit(double depositAmount) {
        try {
            //如果flag为真,表明账户中有人存钱进去,存钱方法阻塞
            if (flag) {
                wait();
            } else {
                //执行存款操作
                System.out.println(Thread.currentThread().getName() + "存钱: " + depositAmount);
                balance+=depositAmount;
                System.out.println("账户余额为: " + balance);
                //将标示账户是否已有存款的旗标设为true
                flag = true;
                //唤醒其他线程
                notifyAll();
            }

        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Account)) {
            return false;
        }
        Account account = (Account) o;
        return Double.compare(account.getBalance(), getBalance()) == 0 &&
                flag == account.flag &&
                Objects.equals(getAccountNo(), account.getAccountNo());
    }

    @Override
    public int hashCode() {

        return Objects.hash(getAccountNo(), getBalance(), flag);
    }
}

public class DrawThread extends Thread {
    //模拟用户账户
    private  Account account;
    //将当前的取钱线程去的钱数
    private double drawAmount;

    public DrawThread(String name, Account account, double drawAmount) {
        super(name);
        this.account=account;
        this.drawAmount=drawAmount;
    }

    //重复100次执行取钱操作
    @Override
    public void run(){
        for (int i = 0; i <100 ; i++) {
            account.draw(drawAmount);
        }
    }
}

public class DepositThread extends Thread {
    //模拟用户账户
    private  Account account;
    //将当前的存钱线程去的钱数
    private double depositAmount;

    public DepositThread(String name, Account account, double depositAmount) {
        super(name);
        this.account=account;
        this.depositAmount=depositAmount;
    }

    //重复100次执行取钱操作
    @Override
    public void run(){
        for (int i = 0; i <100 ; i++) {
            account.deposit(depositAmount);
        }
    }

}
public class DrawTest {
    public static void main(String[] args) {
        //创建一个账户
        Account acct = new Account("1234567", 0);
        new DrawThread("取钱者",acct,800).start();
        new DepositThread("存钱者甲", acct, 800).start();
        new DepositThread("存钱者乙", acct, 800).start();
        new DepositThread("存钱者丙", acct, 800).start();

    }
}

运行结果如下:
在这里插入图片描述
从运行结果可以看出,3个存款者随机的向账户中存钱,只有一个取钱者执行取钱操作。程序最后被阻塞无法继续运行,这是因为3个存款者线程共有300次存款操作,但一个取钱者线程只有100次取钱操作,所以程序最后被阻塞。这种阻塞并不是死锁,对于这种情况,取钱者线程已经执行完毕,而存钱者线程只是在等待其他线程来取钱而已,并不是等待其他线程释放同步监视器。

5.6线程池

猜你喜欢

转载自blog.csdn.net/weixin_41809206/article/details/89339894
今日推荐