美文网首页
13_并发编程基础

13_并发编程基础

作者: 真是个点子王 | 来源:发表于2020-12-11 11:43 被阅读0次

线程的创建方式

  • 1.直接定义一个类继承线程类Thread,重写run()方法,创建线程对象,并调用线程对象的start()方法启动线程;
  • 2.定义一个线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把线程任务对象包装成线程对象,调用线程对象的start()方法启动线程;
  • 3.实现Callable接口

方式一:继承Thread

  • 1.定义一个线程类继承Thread
  • 2.重写run()方法
  • 3.创建一个新的线程对象
  • 4.调用线程对象的start()方法启动线程
  • 这种方法虽然编码简单,但是只能实现单继承。
public class ThreadDemo {
    // 启动后的ThreadDemo当成一个进程。
    // main方法是由主线程执行的,理解成main方法就是一个主线程
    public static void main(String[] args) {
        // 3.创建一个线程对象
        Thread t = new MyThread();
        // 4.调用线程对象的start()方法启动线程,最终还是执行run()方法!
        t.start();

        for(int i = 0 ; i < 100 ; i++ ){
            System.out.println("main线程输出:"+i);
        }
    }
}

// 1.定义一个线程类继承Thread类。
class MyThread extends Thread{
    // 2.重写run()方法
    @Override
    public void run() {
        // 线程的执行方法。
        for(int i = 0 ; i < 100 ; i++ ){
            System.out.println("子线程输出:"+i);
        }
    }
}

Thread类的API

1.public void setName(String name):给当前线程取名字。
2.public void getName():获取当前线程的名字。
     -- 线程存在默认名称,子线程的默认名称是:Thread-索引。
     -- 主线程的默认名称就是:main
3.public static Thread currentThread()
     -- 获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象。
4.public static void sleep(long time)
     -- 让当前线程休眠多少毫秒再继续执行
5.public Thread(String name)
     -- 创建线程对象并取名字。
  • 示例1:线程的命名
public class ThreadDemo {
    // 启动后的ThreadDemo当成一个进程。
    // main方法是由主线程执行的,理解成main方法就是一个主线程
    public static void main(String[] args) {
        // 创建一个线程对象
        Thread t1 = new MyThread();
        t1.setName("1号线程");
        t1.start();
        //System.out.println(t1.getName()); // 获取线程名称

        Thread t2 = new MyThread();
        t2.setName("2号线程");
        t2.start();
        //System.out.println(t2.getName());  // 获取线程名称

        // 主线程的名称如何获取呢?
        // 这个代码在哪个线程中,就得到哪个线程对象。
        Thread m = Thread.currentThread();
        m.setName("最强线程main");
        //System.out.println(m.getName()); // 获取线程名称

        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(m.getName()+"==>"+i);
        }
    }
}

// 1.定义一个线程类继承Thread类。
class MyThread extends Thread{
    // 2.重写run()方法
    @Override
    public void run() {
        // 线程的执行方法。
        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+"==>"+i);
        }
    }
}

方式二:实现Runnable接口

  • 1.创建一个线程任务类实现Runnable接口
  • 2.重写run()方法
  • 3.创建一个线程任务对象
  • 4.把线程任务对象包装成线程对象
  • 5.调用线程对象的start()方法启动线程

Thread的构造器

-- public Thread(){}
-- public Thread(String name){}
-- public Thread(Runnable target){}
-- public Thread(Runnable target,String name){}
  • 示例:
public class ThreadDemo {
    public static void main(String[] args) {
        // 3.创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
        Runnable target = new MyRunnable();
        // 4.把线程任务对象包装成线程对象.且可以指定线程名称
        // Thread t = new Thread(target);
        Thread t = new Thread(target,"1号线程");
        // 5.调用线程对象的start()方法启动线程
        t.start();

        Thread t2 = new Thread(target);
        // 调用线程对象的start()方法启动线程
        t2.start();

        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+"==>"+i);
        }
    }
}

// 1.创建一个线程任务类实现Runnable接口。
class MyRunnable implements Runnable{
    // 2.重写run()方法
    @Override
    public void run() {
        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+"==>"+i);
        }
    }
}
  • 匿名内部类的简化写法
public class ThreadDemo02 {
    public static void main(String[] args) {
        // 创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
        Runnable target = new Runnable() {
            @Override
            public void run() {
                for(int i = 0 ; i < 10 ; i++ ){
                    System.out.println(Thread.currentThread().getName()+"==>"+i);
                }
            }
        };
        // 把线程任务对象包装成线程对象.且可以指定线程名称
        Thread t = new Thread(target);
        // 调用线程对象的start()方法启动线程
        t.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0 ; i < 10 ; i++ ){
                    System.out.println(Thread.currentThread().getName()+"==>"+i);
                }
            }
        }).start();

        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+"==>"+i);
        }
    }
}

特点

  • 线程任务只实现了Runnable接口,可以继续继承其他的类,而且可以实现其他接口;
  • 同一个线程任务可以被包装成多个线程对象;
  • 适合多个线程去共享一个资源;
  • 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立;
  • 线程池可以放入实现RunnableCallable线程任务对象;
  • 不能直接得到线程执行的结果。

方式三:实现Callable接口

  • 1.定义一个线程任务类型实现Callable接口,声明线程的执行结果;
  • 2.重写线程任务类的call方法,这个方法可以直接返回执行结果;
  • 3.创建一个Callable的线程任务对象;
  • 4.把Callable的线程任务对象包装成一个未来任务对象;
  • 5.把未来任务对象包装成一个线程对象;
  • 6.调用线程的start()方法启动线程。
  • 示例:
public class ThreadDemo {
    public static void main(String[] args) {
        // 3.创建一个Callable的线程任务对象
        Callable call = new MyCallable();
        // 4.把Callable任务对象包装成一个未来任务对象
        //      -- public FutureTask(Callable<V> callable)
        // 未来任务对象是啥,有啥用?
        //      -- 未来任务对象其实就是一个Runnable对象:这样就可以被包装成线程对象!
        //      -- 未来任务对象可以在线程执行完毕之后去得到线程执行的结果。
        FutureTask<String> task = new FutureTask<>(call);
        // 5.把未来任务对象包装成线程对象
        Thread t = new Thread(task);
        // 6.启动线程对象
        t.start();

        for(int i = 1 ; i <= 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+" => " + i);
        }

        // 在最后去获取线程执行的结果,如果线程没有结果,让出CPU等线程执行完再来取结果
        try {
            String rs = task.get(); // 获取call方法返回的结果(正常/异常结果)
            System.out.println(rs);
        }  catch (Exception e) {
            e.printStackTrace();
        }

    }
}

// 1.创建一个线程任务类实现Callable接口,申明线程返回的结果类型
class MyCallable implements Callable<String>{
    // 2.重写线程任务类的call方法!
    @Override
    public String call() throws Exception {
        // 需求:计算1-10的和返回
        int sum = 0 ;
        for(int i = 1 ; i <= 10 ; i++ ){
            System.out.println(Thread.currentThread().getName()+" => " + i);
            sum+=i;
        }
        return Thread.currentThread().getName()+"执行的结果是:"+sum;
    }
}

特点

  • 线程任务只实现了Callable接口,可以继续继承其他的类,而且可以实现其他接口;
  • 同一个线程任务可以被包装成多个线程对象;
  • 适合多个线程去共享一个资源;
  • 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立;
  • 线程池可以放入实现RunnableCallable线程任务对象;
  • 可以直接得到线程执行的结果。

线程安全问题

  • 多个线程同时操作同一个共享资源的时候可能会出现线程安全问题。
//Account.java
public class Account {
    private String cardId;
    private double money;

    public void draw(double money) {
        System.out.println("现在取钱的用户:"+Thread.currentThread().getName());
        //显示谁来取钱

        if( this.money >= money){
            System.out.println("余额充足,"+Thread.currentThread().getName() + "取走:" + money);
            this.money -= money;
            System.out.println(Thread.currentThread().getName()+"取款后的余额为:"+this.money);
        }else{
            System.out.println("余额不足:"+Thread.currentThread().getName());
        }
    }

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "cardId='" + cardId + '\'' +
                ", money=" + money +
                '}';
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}


//DrawThread.java
public class DrawThread extends Thread{
    private Account account;


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

    @Override
    public void run(){
        account.draw(1000000);
    }
}


//ThreadSafe.java
public class ThreadSafe {
    public static void main(String[] args) {

        Account account = new Account("ICBC-110",1000000);
        //创建一个账户

        Thread t1 = new DrawThread(account,"小红");
        // 小红进程

        Thread t2 = new DrawThread(account,"小明");
        // 小明进程
        t1.start();
        t2.start();
    }
}

线程同步


方法一:同步代码块

  • 作用:把出现线程安全问题的核心代码给上锁,每次只能一个线程进入执行完毕以后自动解锁,其他线程才可以进来执行。
  • 格式:
synchronized(锁对象){
 // 访问共享资源的核心代码
}
锁对象:理论上可以是任意的“唯一”对象即可。
原则上:锁对象建议使用共享资源。
    -- 在实例方法中建议用this作为锁对象。此时this正好是共享资源!必须代码高度面向对象
    -- 在静态方法中建议用类名.class字节码作为锁对象。
public void drawMoney(double money) {
        String name = Thread.currentThread().getName();
        synchronized (this){
            if(this.money >= money){           
                System.out.println(name+"来取钱,吐出:"+money);                
                this.money -= money;
                System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
            }else{
                System.out.println(name+"来取钱,余额不足,剩余"+this.money);
            }
        }
    }

方法二:同步方法

  • 作用:把出现线程安全问题的核心方法给锁起来,每次只能一个线程进入访问,其他线程必须在方法外面等待。
  • 用法:直接给方法加上一个修饰符 synchronized
  • synchronized修饰方法默认是以当前对象作为锁的对象。
    public synchronized void drawMoney(double money) {      
        String name = Thread.currentThread().getName();
        if(this.money >= money){
            System.out.println(name+"来取钱,吐出:"+money);
            this.money -= money;
            System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
        }else{
            System.out.println(name+"来取钱,余额不足,剩余"+this.money);
        }
    }

方法三:Lock显式锁

  • java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大
public class Account {
    private double money ; // 余额
    private String cardId ;
    // 创建一把锁对象:因为账户对象对于小明小红是唯一的,所以这里的锁对象对于小明小红也是唯一的
    private final Lock lock = new ReentrantLock();

    public void drawMoney(double money) {
        // 1.先拿到是谁来取钱:取当前线程对象的名称
        String name = Thread.currentThread().getName();
        lock.lock(); // 上锁~!
        try{
            if(this.money >= money){
                // 3.余额足够
                System.out.println(name+"来取钱,吐出:"+money);
                // 4.更新余额
                this.money -= money;
                // 5.输出结果
                System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
            }else{
                // 6.余额不足
                System.out.println(name+"来取钱,余额不足,剩余"+this.money);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock(); // 解锁~!
        }

    }

线程通信

  • 核心方法
    public void wait(): 让当前线程进入到等待状态 此方法必须锁对象调用.
    public void notify() : 唤醒当前锁对象上等待状态的某个线程  此方法必须锁对象调用
    public void notifyAll() : 唤醒当前锁对象上等待状态的全部线程  此方法必须锁对象调用

模拟案例

  • 小明和小红有一个共同账户:共享资源
  • 他们有3个爸爸(亲爸,岳父,干爹)给他们存钱。
  • 模型:小明和小红去取钱,如果有钱就取出,然后等待自己,唤醒他们3个爸爸们来存钱他们的爸爸们来存钱,如果发现有钱就不存,没钱就存钱,然后等待自己,唤醒孩子们来取钱.做整存整取:10000元。
// ThreadCommunication.java
public class ThreadCommunication {
    public static void main(String[] args) {
        Account account = new Account(0, "ICBC-111");
        Thread xiaohong = new DrawThread(account, "小红");
        Thread xiaoming = new DrawThread(account, "小明");
        Thread qindie = new SaveThread(account, "亲爹");
        Thread gandie = new SaveThread(account, "干爹");
        Thread yuefu = new SaveThread(account, "岳父");
        xiaohong.start();
        xiaoming.start();
        qindie.start();
        gandie.start();
        yuefu.start();
    }
}

//Account.java
public class Account {
    private int money;
    private String cardID;
    public synchronized void drawMoney(int money) {

        String name = Thread.currentThread().getName();
        try {
            if(this.money >= 10000){
                this.money -= money;
                System.out.println(name +":取钱成功,余额:"+this.money);
                this.notifyAll();
                this.wait();
            }else{
                System.out.println(name +":余额不足");
                this.notifyAll();
                this.wait();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public synchronized void saveMoney(int money) {
        String name = Thread.currentThread().getName();
        try {
            if(this.money >= 10000){
                System.out.println(name +":余额充足,余额:"+ this.money);
                this.notifyAll();
                this.wait();
            }else{
                this.money += money;
                System.out.println(name +":存钱成功,余额:"+ this.money);
                this.notifyAll();
                this.wait();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Account() {
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public String getCardID() {
        return cardID;
    }

    public void setCardID(String cardID) {
        this.cardID = cardID;
    }

    public Account(int money, String cardID) {
        this.money = money;
        this.cardID = cardID;
    }
}

//DrawThread.java
public class DrawThread extends Thread {

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

    @Override
    public void run(){
        while (true){
            try {
                Thread.sleep(3000);
                account.drawMoney(10000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

//SaveThread.java
public class SaveThread extends Thread {
    private Account account;

    SaveThread(Account account,String name){
        super(name);
        this.account = account;
    }
    @Override
    public void run(){
        while (true){
            try {
                Thread.sleep(3000);
                account.saveMoney(10000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

线程状态

  • 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态
线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread只有线程对象,没有线程特征。
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。调用了t.start()方法 :就绪(经典教法)
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

线程池


  • 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程都需要时间,线程也是属于宝贵的系统资源。
  • 线程池其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源。
  • 优势:
    • 1.降低资源消耗:减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可以执行多个任务;
    • 2.提高响应速度:不需要频繁的创建线程,如果有线程可以直接用,不会出现系统僵死;
    • 3.提高线程的可管理性(线程池可以约束系统最多只能有多少个线程,不会因为线程过多而死机)

创建线程池示例

  • Runnable示例
public class ThreadPoolsDemo02 {
    public static void main(String[] args) {
        // a.创建一个线程池,指定线程的固定数量是3.
        // new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
        ExecutorService pools = Executors.newFixedThreadPool(3);
        // b.创建线程的任务对象。
        Runnable target = new MyRunnable();
        // c.把线程任务放入到线程池中去执行。
        pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
        pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
        pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
        pools.submit(target); // 不会再创建新线程,会复用之前的线程来处理这个任务

        pools.shutdown(); // 等待任务执行完毕以后才会关闭线程池
        //pools.shutdownNow(); // 立即关闭线程池的代码,无论任务是否执行完毕!
    }
}

class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i  = 0 ; i < 5 ; i++ ){
            System.out.println(Thread.currentThread().getName()+" => "+i);
        }
    }
}
  • Callable示例
public class TestCallableThreadPool {

    public static void main(String[] args) {
        // 创建一个线程池,固定线程数为3
        ExecutorService pools = Executors.newFixedThreadPool(3);

        Future<String> t1 = pools.submit(new MyCallable(50));
        Future<String> t2 = pools.submit(new MyCallable(100));
        Future<String> t3 = pools.submit(new MyCallable(200));
        Future<String> t4 = pools.submit(new MyCallable(300));

        try {
            System.out.println(t1.get());
            System.out.println(t2.get());
            System.out.println(t3.get());
            System.out.println(t4.get());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pools.shutdown();
        }

    }

}

class MyCallable implements Callable<String>{

    private int n;

    MyCallable(int n){
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum = 0;
        for(int i = 1 ; i <= n ;i++){
            sum += i;
        }
        return Thread.currentThread().getName() + " - Sum = " + sum;
    }
}

相关文章

网友评论

      本文标题:13_并发编程基础

      本文链接:https://www.haomeiwen.com/subject/foilgktx.html