美文网首页
Java多线程

Java多线程

作者: 忒无聊了叭 | 来源:发表于2020-03-27 22:08 被阅读0次

    线程简介

    初识

    程序

    说起进程,就不得不说程序,程序是指令和数据的有序集合,其本身没有任何意义,是一个静态的概念。

    进程

    进程就是执行程序的一次过程,他是一个动态的概念,是系统资源分配的单位。

    线程

    通常在一个进程中,可以包含多个线程,一个进程中至少存在一个线程,不然没有存在的意义,线程是cpu调度和执行的单位。

    注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核。

    举例:比如说一边玩游戏,一边听音乐是同时进行的吗?
    不是。因为单CPU在某一个时间点上只能做一件事情。
    而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。

    创建线程的三种方式

    创建线程的三种方式.png

    继承thread类进行实现多线程

    package com.szw;
    
    //创建线程方式一:继承thread,重新run()方法,调用start()开启线程
    public class Thread01 extends Thread {
    
        @Override
        public void run() {
            //run方法线程体,重写父类run方法
            for (int i = 0; i < 200; i++) {
                System.out.println("我在看代码~~"+i);
            }
        }
        public static void main(String[] args) {
            //main主线程
            Thread01 thread01 = new Thread01();
            //调用start方法,启动多线程
            thread01.start();
            for (int i = 0; i < 200; i++) {
                System.out.println("我在学习多线程~~"+i);
            }
        }
    }
    

    问题:启动线程为什么不直接使用run方法,而是start方法?

    Thired类用于描述线程,其中有一个定义存储功能run,用于存储线程要运行的代码,主线程的代码在main中,新线程的代码在run中,start除了开启新线程之外,还有就是启动线程的功能。

    实现runnable进行多线程

    package com.szw;
    
    //创建线程方式2:实现runnable接口,重新run方法
    public class Thread02 implements Runnable {
        @Override
        public void run() {
            //run方法线程体
            for (int i = 0; i < 200; i++) {
                System.out.println("我在看代码~~" + i);
            }
        }
    
        public static void main(String[] args) {
            Thread02 thread02 = new Thread02();
            //创建线程对象,通过线程对象来开启我们的线程,代理
            new Thread(thread02).start();
    
            for (int i = 0; i < 200; i++) {
                System.out.println("我在学习多线程~~" + i);
            }
        }
    }
    
    

    以上两种方式,查看源码可知,thread也是实现了runnable接口,由于java单继承的特性,所以推荐使用runnable进行多线程。

    上面两种开启时的区别是:runnable是需要放在Thread中进行start,而thread直接开启即可。

    模拟龟兔赛跑

    Thread.currentThread表示当前代码段正在被哪个线程调用的相关信息。

    package com.szw;
    
    //模拟龟兔赛跑
    public class Race implements Runnable{
    
        //胜利者
        private static String winner;
    
        @Override
        public void run() {
    
            //如果是兔子线程,模拟睡觉
            if (Thread.currentThread().getName().equals("兔子")){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            for (int i = 1 ;i <= 100; i++) {
                //判断比赛是否结束
                boolean b = gameOver(i);
                if (b){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
            }
        }
    
        public boolean gameOver(int steps){
            //判断是否有胜利者
            if (winner!=null){//这里是因为两个进程,一个进程胜利之后,另一个还在跑
                return true;
            }else {
                if (steps>=100){
                    winner = Thread.currentThread().getName();
                    System.out.println("胜利者是"+winner);
                    return true;
                }
            }
            return false;
        }
    
        public static void main(String[] args) {
            Race race = new Race();
            new Thread(race,"兔子").start();
            new Thread(race,"乌龟").start();
        }
    }
    
    

    扩展:Lambda表达式

    官方定义:Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。

    Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

    使用 Lambda 表达式可以使代码变的更加简洁紧凑。

    由于runnable接口就是函数式接口,所以我们这里扩展Lambda可以让代码更简洁。

    package com.szw;
    
    //Lambda表达式
    public class Lambda {
    
        //第二种方法:静态内部类
        static class Love implements ILove{
            @Override
            public void ILove(int a) {
                System.out.println("I Love you   序号2"+a);
            }
        }
    
        public static void main(String[] args) {
            //第三种方法:局部内部类
            class Love implements ILove{
                @Override
                public void ILove(int a) {
                    System.out.println("I Love you   序号3"+a);
                }
            }
            //第四种方法:匿名内部类
            ILove love = new Love(){
                @Override
                public void ILove(int a) {
                    System.out.println("I Love you   序号4"+a);
                }
            };
            //第五种方法Lambda表达式
            ILove love2 = (int a) -> {
                System.out.println("I Love you   序号5"+a);
            };
            //简化一:简化Lambda表达式
            ILove love3 = (a) -> {
                System.out.println("I Love you   序号6"+a);
            };
            //简化二:去掉a的括号,继续简化Lambda表达式
            ILove love4 = a -> {
                System.out.println("I Love you   序号7"+a);
            };
    
            //简化三:去掉花括号,(注意这里只是一行代码的情况)继续简化Lambda表达式
            ILove love5 = a -> System.out.println("I Love you   序号8"+a);
    
            //Lambda表达式:
            //前提:1、只有一行代码,否则使用代码块包裹。
            //2、必须是函数式接口,接口中只有一个方法
            //3、多个参数也可以去掉int,但是要加括号
            love.ILove(0);
            love2.ILove(0);
            love3.ILove(0);
            love4.ILove(0);
            love5.ILove(0);
        }
    }
    
    //第一种方法:首先定义一个接口,实现它,然后在主方法中进行调用(普通的方法)
    interface ILove{
        void ILove(int a);
    }
    class Love implements ILove{
        @Override
        public void ILove(int a) {
            System.out.println("I Love you   序号1"+a);
        }
    }
    

    线程状态

    线程的状态.png

    注意:查看源码,可知线程有六种状态!

    线程的一些方法:

    线程停止

    线程停止
    1、建议线程停止--->利用次数,不建议死循环
    2、建议使用标志位--->设置一个标志位
    3、不建议使用stop和destroy等过时的方法,jdk官方不推荐

    package com.szw;
    
    //测试线程停止
    //1、建议线程停止--->利用次数,不建议死循环
    //2、建议使用标志位--->设置一个标志位
    //3、不建议使用stop和destroy等过时的方法,jdk官方不推荐
    public class ThreadStop implements Runnable {
    
        //设置一个标志位
        private boolean flag = true;
    
        @Override
        public void run() {
            int i = 0;
            while (flag) {
                System.out.println("run...+Thread" + i++);
            }
        }
    
        //设置一个公开的标志位
        public void stop() {
            this.flag = false;
        }
    
        public static void main(String[] args) {
            ThreadStop threadStop = new ThreadStop();
            new Thread(threadStop).start();//线程开始
            for (int i = 0; i < 200; i++) {
                System.out.println("主线程" + i);
                if (i == 100) {
                    //调用stop方法,停止
                    threadStop.stop();
                    System.out.println("线程停止...");
                }
            }
        }
    }
    
    

    线程休眠

    模拟时钟思路:首先获取系统时间,然后进行while循环,输出系统时间,线程休眠1s,然后更新获取系统时间,再输出。。

    package com.szw;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    //线程休眠模拟倒计时
    public class TestSleep {
        public static void main(String[] args) {
            Date startTime = new Date(System.currentTimeMillis());//获取系统时间
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime = new Date(System.currentTimeMillis());//更新时间
            }
        }
    
        //模拟倒计时
        public static void tenDown() throws InterruptedException {
            int num = 10;
            while (true) {
                Thread.sleep(1000);
                System.out.println(num--);
                if (num < 0) {
                    break;
                }
            }
    
        }
    }
    

    线程礼让

    • 线程礼让,让当前的线程暂停,但是不堵塞。
    • 将线程的运行状态重新定义为就绪状态。
    • 相当于让cpu重新调度,但是礼让不一定成功!看cpu

    详细解释

    ​ yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

    ​ 举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,有可能是其他人先上车了,也有可能是Yield先上车了。

    线程强制执行

    • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
    • 类似于插队,让被强制执行的线程执行完毕后,才可以允许其他线程进行。
    package com.szw;
    
    public class ThreadJoin implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++) {
                System.out.println("线程插队"+i);
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            ThreadJoin threadJoin = new ThreadJoin();
            Thread thread = new Thread(threadJoin);
            thread.start();
    
            //主线程
            for (int i = 0; i < 500; i++) {
                if (i==200){
                    thread.join();//强行让线程执行
                    System.out.println("线程执行完毕,主线程才开始~~~~~~~~~~~~~~~~~~~~~~~");
                }
                System.out.println("主线程"+i);
            }
        }
    }
    
    

    线程状态

    • NEW

    尚未启动线程处于此状态。

    • Runnable

    在Java虚拟机中执行的线程处于此状态。

    • BLOCKED

    被阻塞监视等待锁定的线程,就处于此状态。

    • WAITING

    正在等待另一个线程执行特定动作的线程处于此状态。

    • TIMED_WAITING

    正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。

    • TERMINATED

    已经退出的线程处于此状态;

    package com.szw;
    
    //线程状态
    public class ThreadState {
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(()->{//这里的意思就是让线程运行5s
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("------------");
            });
    
            //观察状态
            Thread.State state = thread.getState();
            System.out.println("未启动的线程状态"+state);
    
            //启动以后的状态
            thread.start();
            state = thread.getState();
            System.out.println("启动以后的线程状态"+state);
    
            while (state!=thread.getState().TERMINATED){//线程状态不是停止的时候
                for (int i = 0; i < 5; i++) {
                    thread.sleep(1000);//每隔一秒输出线程的状态
                    state = thread.getState();
                    System.out.println(state);
                }
            }
            System.out.println("线程停止~~~");
        }
    }
    

    线程优先级

    优先级具有随机性:一般优先级较高的线程先执行run()方法,但是这个不能说的但肯定,因为线程的优先级具有 “随机性”也就是较高线程不一定每一次都先执行完。

    package com.szw;
    
    //线程优先级
    public class ThreadPriority {
        public static void main(String[] args) {
            //主线程
            System.out.println("main"+"-->"+Thread.currentThread().getPriority());
            MyPriority myPriority = new MyPriority();
            Thread t1 = new Thread(myPriority);
            Thread t2 = new Thread(myPriority);
            Thread t3 = new Thread(myPriority);
            Thread t4 = new Thread(myPriority);
            t1.setPriority(2);//2
            t2.setPriority(3);//3
            t3.setPriority(Thread.NORM_PRIORITY);//5
            t4.setPriority(Thread.MAX_PRIORITY);//10
    
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    
    class MyPriority implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        }
    }
    
    

    守护线程daemon

    • 线程分为用户线程和守护线程
    • 虚拟机必须保护用户线程完成
    • 虚拟机不用等待守护线程完毕

    通俗易懂的就是说:守护线程是维护用户线程的,等用户线程跑完之后,守护线程也会停止。

    package com.szw;
    
    public class ThreadDaemon {
        public static void main(String[] args) {
            Gad gad = new Gad();
            You you = new You();
            Thread thread = new Thread(gad);
            thread.setDaemon(true);
            thread.start();
            //启动你
            new Thread(you).start();
    
        }
    }
    
    class You implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 36500; i++) {
                System.out.println("快乐的每一天~~"+i);
            }
        }
    }
    class Gad implements Runnable{
        @Override
        public void run() {
            while (true){
                System.out.println("上帝保护你~~");
            }
        }
    }
    
    

    多线程暴露的问题

    三人买票问题

    使用多线程三人买票,运行输出发现票的数据严重不正确。

    package com.szw;
    
    public class Thread03 implements Runnable {
    
        //定义票数有10张
        private int ticketNums = 10;
    
        @Override
        public void run() {
            while (true) {
                if (ticketNums <= 0) {
                    break;
                }
                //模拟延时
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums + "票");
                ticketNums--;
    
            }
        }
    
        public static void main(String[] args) {
            Thread03 thread03 = new Thread03();
            new Thread(thread03, "小明").start();
            new Thread(thread03, "小孩").start();
            new Thread(thread03, "黄牛党").start();
        }
    }
    
    
    1585112453859.png

    银行取钱问题

    我和妻子同时取钱:出现错误。

    package com.szw;
    
    import com.sun.org.apache.bcel.internal.generic.NEW;
    
    public class UnSafeBank {
        public static void main(String[] args) {
            //账户
            Account account = new Account(100,"结婚基金");
            new Drawing(account,100,"你").start();
            new Drawing(account,100,"妻子").start();
        }
    }
    //账户
    class Account{
        int money;
        String name;
    
        public Account(int money, String name) {
            this.money = money;
            this.name = name;
        }
    }
    //银行
    class Drawing extends Thread{
        Account account;//账户
        int drawMoney;//取了多少钱
        int nowMoney; //剩余的钱
    
        public Drawing(Account account,int drawMoney,String name){
            super(name);
            this.account = account;
            this.drawMoney = drawMoney;
        }
    
        @Override
        public void run() {
            synchronized(account){
                //判断有没有钱
                if (account.money-drawMoney < 0){
                    System.out.println(Thread.currentThread().getName()+"钱不够了,取不了");
                    return;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                //卡内的余额 = 余额 - 你取的钱
                account.money = account.money - drawMoney;
                //你手里的钱
                nowMoney = nowMoney + drawMoney;
    
                System.out.println(account.name+"余额为"+account.money);
                System.out.println(this.getName()+"手里的钱"+nowMoney);
                System.out.println(account.name+"余额为"+account.money);
                System.out.println("------------------------------");
            }
        }
    }
    
    同时取钱问题.png

    线程同步

    同修饰私有属性的关键词private一样,线程同步也有修饰词synchronized,加上该修饰词后,只允许一个对象去操作,每个对象有一把锁,当对象访问时,把锁加上,后面的进程就会形成阻塞。

    synchronized有synchronized方法,和synchronized块。

    缺陷:若将一个大的方法申明为synchronized将会影响效率。

    注意:synchronized块的使用方法是:

    synchronized(obj){
        //这里的obj是需要锁的对象,为增删改的对象
    }
    

    我们在上面的取钱的问题上加上,synchronized修饰,问题就修改了。


    取钱修改.png

    synchronized方法上使用的是:

    private synchronized void buy(){
        
    }
    //这里不需要指明被锁的对象,默认是this,就是这个方法的对象本身
    

    死锁

    解释: 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形.某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

    通俗的讲:就是两个线程运行时,都需要对面的资源进行完成指令,但是都等待对方的资源释放,从而形成停止执行的情况。

    package com.szw;
    
    
    public class DeadLock {
      public static void main(String[] args) {
          Makeup makeup = new Makeup(0,"白雪公主");
          Makeup makeup1 = new Makeup(1,"灰姑娘");
          makeup.start();
          makeup1.start();
      }
    }
    
    //口红
    class Lipstick{
    }
    
    //镜子
    class Mirror{
    }
    
    class Makeup extends Thread{
      int choice;//选择
      String girlName;//姓名
    
      Makeup(int choice,String girlName){
          this.choice = choice;
          this.girlName = girlName;
      }
    
      //化妆需要的镜子和口红都只有一份
      static Lipstick lipstick = new Lipstick();
      static Mirror mirror = new Mirror();
    
      @Override
      public void run() {
          makeup();
      }
    
      private void makeup(){
          if (choice == 0){
              synchronized (lipstick){
                  System.out.println(this.girlName+"获得了口红");
                  synchronized ((mirror)){
                      System.out.println(this.girlName+"获得了镜子");
                  }
              }
          }else {
              synchronized (mirror){
                  System.out.println(this.girlName+"获得了镜子");
                  synchronized ((lipstick)){
                      System.out.println(this.girlName+"获得了口红");
                  }
              }
          }
      }
    }
    

    可以运行代码,发现两个对象都没有继续获得镜子或者口红,这里修改的方法就是,分别把0获取镜子的线程同步方法分开。1的口红与镜子分开。这样实现了两个对象不会产生抱死的状态。

    Lock

    和synchronized功能一样。

    这里写一下加lock锁以后的取票问题,注意的是用lock锁有加锁解锁的过程,写在try...finally...中

    package com.szw;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class UnSafeBuyTicket {
        public static void main(String[] args) {
            BuyTicket buyTicket = new BuyTicket();
            new Thread(buyTicket,"我").start();
            new Thread(buyTicket,"你").start();
            new Thread(buyTicket,"黄牛党").start();
        }
    }
    class  BuyTicket implements Runnable{
        private ReentrantLock lock = new ReentrantLock();
        private int ticketNum = 10;
        boolean flag = true;
    
        @Override
        public void run(){
            while (true){
                try {
                    buy();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        privatevoid buy() throws InterruptedException {
            try {
                lock.lock();//加锁
                //判断是否有票
                if (ticketNum<=0){
                    flag = false;
                    return;
                }
                //模拟买票
                Thread.sleep(100);
                //拿到票
                System.out.println(Thread.currentThread().getName()+"拿到"+ticketNum--);
            }finally {
                //解锁
                lock.unlock();
            }
        }
    }
    

    线程协作

    线程通信

    生产消费者模式

    生产消费者.png
    package com.szw;
    
    //测试:生产者消费者模型->利用缓冲区解决:管程法
    //生产者、消费者、产品、缓冲区
    public class TestPc {
        public static void main(String[] args) {
            SynContainer container = new SynContainer();
            new Producer(container).start();
            new Consumer(container).start();
    
        }
    }
    
    //生产者
    class Producer extends Thread {
        //缓冲区   
        SynContainer container;
    
        public Producer(SynContainer container) {
            this.container = container;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    container.push(new Chicken(i));
                    System.out.println("生产了" + i + "只鸡");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    //消费者
    class Consumer extends Thread {
        SynContainer container;
    
        public Consumer(SynContainer container) {
            this.container = container;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    System.out.println("消费了-->" + container.pop().id + "");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    //产品
    class Chicken {
        int id;
    
        Chicken(int id) {
            this.id = id;
        }
    }
    
    //缓冲区
    class SynContainer {
        //需要一个容器大小
        Chicken[] chickens = new Chicken[10];
        int Count = 0;
    
        //生产者放入产品
        public synchronized void push(Chicken chicken) throws InterruptedException {
            if (Count == chickens.length) {
                //如果有10只鸡,就先停止生产,让消费者消费
                this.wait();
            }
            chickens[Count] = chicken;
            Count++;
            //通知消费者消费
            this.notifyAll();
        }
    
        //消费者消费产品
        public synchronized Chicken pop() throws InterruptedException {
            if (Count == 0) {
                //通知生产者生产,先停止消费
                this.wait();
            }
            //有鸡:就进行消费
            Count--;
            Chicken chicken = chickens[Count];
            this.notifyAll();//唤醒全部线程
            return chicken;
        }
    
    }
    
    1585317414956.png
    package com.szw;
    
    //测试生产消费者问题2:信号灯法,标志位解决
    public class TestPc2 {
        public static void main(String[] args) {
            TV tv = new TV();
            new Player(tv).start();
            new Watcher(tv).start();
        }
    }
    
    //表演者
    class Player extends Thread {
        TV tv;
    
        public Player(TV tv) {
            this.tv = tv;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                if (i % 2 == 0) {
                    this.tv.play("快乐大本营。。。");
                } else {
                    this.tv.play("广告中。。。");
                }
            }
        }
    }
    //消费者
    class Watcher extends Thread {
        TV tv;
    
        public Watcher(TV tv) {
            this.tv = tv;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                tv.watch();
            }
        }
    }
    
    //产品,TV
    class TV {
        //演员表演,观众等待
        //观众观看,演员等待
        String voice;
        boolean flag = true;//true代表现在演员要表演了
    
        //表演
        public synchronized void play(String voice) {
            if (!flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("演员表演了" + voice);
            //通知观众观看
            this.notifyAll();//通知唤醒
            this.voice = voice;
            this.flag = !this.flag;
        }
    
        //观看
        public synchronized void watch() {
            if (flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("观看了" + voice);
            this.notifyAll();//通知唤醒
            //通知演员表演
            this.flag = !this.flag;
        }
    }
    

    线程池

    背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

    思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

    好处:

    ◆提高咆应速度(减少了创建新线程的时间)

    ◆降低资源消耗(重复利用线程池中线程,不需要每次都创建)

    ◆便于线程管理

    相关文章

      网友评论

          本文标题:Java多线程

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