美文网首页
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