多线程高并发--1

分享 123456789987654321 ⋅ 于 2021-02-05 15:38:33 ⋅ 1749 阅读

多线程基础

为什么要使用多线程?

理财软件,一般是按日计息,如果每次访问利息是实时计算的,当用户量大的时候,计算的成本就会增加,一般会在凌晨通过定时任务将前一天的利息计算出来,放到一个汇总表中,之后用户查询时只访问汇总表。定时任务会计算每一个用户的数据,当用户量特别大,预计2小时将所有用户任务执行完成,当我们使用单线程处理的话,可能就不能完成,所以需要多线程同时来处理。

进程和线程

进程可以理解为一个应用程序

比如QQ就是一个进程,可以一边视频、一边传输文件、还可以发送聊天表情,每一个功能都是不同的任务,可以理解为是线程,这些不同的任务或者功能可以同时运行

多线程创建方式

继承自Thread

// 创建线程的第一种方式
public class Demo01CreateThread {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+" 主线程执行");
        Thread thread = new CreateThread1();
        // 启动线程 start
        thread.start();
        // 直接调用run() 不会启动新的线程,而是在主线程中执行
//        thread.run();
    }
    // 内部类,为了演示方便,在企业中使用可以将线程类放在外面
    static class CreateThread1 extends Thread{
        // 需要告诉线程要做什么
        // run()中是线程执行的逻辑
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+" 执行");
        }
    }
}

线程类实现Runnable接口

// 创建线程的第二种方式
public class Demo02CreateThread {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+" 主线程执行");
        Runnable runnable = new CreateThread2();
        // 创建线程类
        Thread thread = new Thread(runnable);
        thread.start();
    }

    static class CreateThread2 implements Runnable{

        public void run() {
            System.out.println(Thread.currentThread().getName()+" 执行");
        }
    }
}

匿名内部类(其实也是使用Runnable)

// 创建线程的第三种方式
public class Demo03CreateThread {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+" 主线程执行");
        // 通过匿名内部类创建线程
        Thread thread = new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 执行");
            }
        });
        // 启动线程
        thread.start();
    }
}

线程池方式

// 创建线程的第四种方式
public class Demo04CreateThread {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+" 主线程执行");
        // 开启线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        // 提交线程任务
        executorService.submit(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 执行");
            }
        });
        // 关闭线程池
        executorService.shutdown();
    }
}

守护线程

非守护线程,当主线程停止后,子线程会继续执行完成

守护线程,在主线程停止后,会立刻停止子线程

public class Demo05DaemonThread {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+" 主线程执行");
        Thread thread = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+" "+i);
                }
            }
        });
        // 将线程设置为守护线程
        thread.setDaemon(true);
        thread.start();
        System.out.println("主线程运行结束");
    }
}

线程安全

多个线程同时操作同一个共享变量,如果每次运行结果与预期一致,那么就是线程安全,否则为线程不安全

卖票案例

public class Demo06SellTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Window window = new Window(ticket);
        Thread thread = new Thread(window);
        // 给线程设置名称
        thread.setName("窗口1");
        Thread thread1 = new Thread(window);
        thread.start();
        thread1.setName("窗口2");
        thread1.start();
    }

    // 票对象
    static class Ticket {
        // 票总数
        public int count = 10;
    }

    // 卖票窗口
    static class Window implements Runnable {

        private Ticket ticket;

        public Window(Ticket ticket) {
            this.ticket = ticket;
        }

        public void run() {
            while (ticket.count>0){
                sell();
            }
        }

        // 卖票方法
        void sell() {
            // 判断票数是否大于0
            if (ticket.count > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票");
                // 卖出票后将总数减一
                ticket.count--;
            }
        }
    }
}

线程同步

线程安全的实现方法:

  • 互斥同步
  • 非阻塞同步

同步时指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个线程使用。互斥是实现同步的一种手段,互斥是因,同步是果,互斥是方法,同步时目的。

在Java中,最基本的互斥同步手段是synchronized关键字

synchronized

  • 同步方法,在需要同步的方法上加上synchronized关键字

    public class Demo06SellTicket {
      public static void main(String[] args) {
          Ticket ticket = new Ticket();
          Window window = new Window(ticket);
          Thread thread = new Thread(window);
          // 给线程设置名称
          thread.setName("窗口1");
          Thread thread1 = new Thread(window);
          thread.start();
          thread1.setName("窗口2");
          thread1.start();
      }
    
      // 票对象
      static class Ticket {
          // 票总数
          public int count = 10;
      }
    
      // 卖票窗口
      static class Window implements Runnable {
    
          private Ticket ticket;
    
          public Window(Ticket ticket) {
              this.ticket = ticket;
          }
    
          public void run() {
              while (ticket.count>0){
                  sell();
              }
          }
    
          // 卖票方法
          synchronized void sell() {
              // 判断票数是否大于0
              if (ticket.count > 0) {
                  System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票");
                  // 卖出票后将总数减一
                  ticket.count--;
              }
          }
      }
    }
  • 同步代码块

    public class Demo06SellTicket {
      public static void main(String[] args) {
          Ticket ticket = new Ticket();
          Window window = new Window(ticket);
          Thread thread = new Thread(window);
          // 给线程设置名称
          thread.setName("窗口1");
          Thread thread1 = new Thread(window);
          thread.start();
          thread1.setName("窗口2");
          thread1.start();
      }
    
      // 票对象
      static class Ticket {
          // 票总数
          public int count = 10;
      }
    
      // 卖票窗口
      static class Window implements Runnable {
    
          private Ticket ticket;
    
          public Window(Ticket ticket) {
              this.ticket = ticket;
          }
    
          public void run() {
              while (ticket.count>0){
                  // this指的是当前Window对象
                  synchronized(this){
                      // todo something
                      sell();
                      // todo something
                  }
              }
          }
    
          // 卖票方法
          void sell() {
              // 判断票数是否大于0
              if (ticket.count > 0) {
                  System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票");
                  // 卖出票后将总数减一
                  ticket.count--;
              }
          }
      }
    }

synchronized锁定的是同一个对象,才能实现同步,如果锁定的是不同的对象,同步无效

public class Demo06SellTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Window window = new Window(ticket);
        Window window1 = new Window(ticket);
        Thread thread = new Thread(window);
        // 给线程设置名称
        thread.setName("窗口1");
        Thread thread1 = new Thread(window1);
        thread.start();
        thread1.setName("窗口2");
        thread1.start();
    }

    // 票对象
    static class Ticket {
        // 票总数
        public int count = 10;
    }

    // 卖票窗口
    static class Window implements Runnable {

        private Ticket ticket;

        public Window(Ticket ticket) {
            this.ticket = ticket;
        }

        public void run() {
            while (ticket.count>0){
                // this指的是当前Window对象
                synchronized(this){
                    // todo something
                    sell();
                    // todo something
                }
            }
        }

        // 卖票方法
        void sell() {
            // 判断票数是否大于0
            if (ticket.count > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票");
                // 卖出票后将总数减一
                ticket.count--;
            }
        }
    }
}

如果锁定的是类对象,给Class类上锁,对类的所有对象实例起作用

public class Demo06SellTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Window window = new Window(ticket);
        Window window1 = new Window(ticket);
        Thread thread = new Thread(window);
        // 给线程设置名称
        thread.setName("窗口1");
        Thread thread1 = new Thread(window1);
        thread.start();
        thread1.setName("窗口2");
        thread1.start();
    }

    // 票对象
    static class Ticket {
        // 票总数
        public int count = 10;
    }

    // 卖票窗口
    static class Window implements Runnable {

        private Ticket ticket;

        public Window(Ticket ticket) {
            this.ticket = ticket;
        }

        public void run() {
            while (ticket.count>0){
                // this指的是当前Window对象
                synchronized(this.getClass()){
                    // todo something
                    sell();
                    // todo something
                }
            }
        }

        // 卖票方法
        void sell() {
            // 判断票数是否大于0
            if (ticket.count > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票");
                // 卖出票后将总数减一
                ticket.count--;
            }
        }
    }
}
synchronized 加在非静态方法上是给当前对象上锁
synchronized 加在静态方法上是给Class类上锁
synchronized(this) 是给当前对象上锁
synchronized(this.getClass()) 是给Class类上锁,对类的所有对象实例都起作用

Lock

除了使用synchronized,还可以使用JUC提供的可重入锁来实现同步

public class Demo06SellTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Window window = new Window(ticket);
        Window window1 = new Window(ticket);
        Thread thread = new Thread(window);
        // 给线程设置名称
        thread.setName("窗口1");
        Thread thread1 = new Thread(window);
        thread.start();
        thread1.setName("窗口2");
        thread1.start();
    }

    // 票对象
    static class Ticket {
        // 票总数
        public int count = 10;
    }

    // 卖票窗口
    static class Window implements Runnable {

        private Ticket ticket;

        private ReentrantLock lock = new ReentrantLock();

        public Window(Ticket ticket) {
            this.ticket = ticket;
        }

        public void run() {
            while (ticket.count>0){
                // 锁定锁对象
                lock.lock();
                // todo something
                sell();
                // todo something
                // 业务逻辑执行完成后解锁
                lock.unlock();
            }
        }

        // 卖票方法
        void sell() {
            // 判断票数是否大于0
            if (ticket.count > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket.count + "张票");
                // 卖出票后将总数减一
                ticket.count--;
            }
        }
    }
}

死锁

死锁是一个经典的多线程问题,不同的线程都在等待根本不可能释放的锁,从而导致所有的任务都无法继续执行。

使用jps查看所有运行的虚拟机

jps
# 使用jstack -l 进程ID
jstack -l 9132

image-20200407104922863

线程状态

wait()和notify()

主要是实现线程间的通信,如果没有等待通知机制,就需要在一个线程中通过轮询机制来检测某一条件是否达成,轮询会浪费CPU资源,典型的应用场景是生产者、消费者模式

public class Demo08WaitNotify {
    public static void main(String[] args) throws InterruptedException {
        Product product = new Product();
        Producer producer = new Producer(product);
        Consumer consumer = new Consumer(product);
        Thread threadPro = new Thread(producer);
        Thread threadCon = new Thread(consumer);
        threadPro.start();
        threadCon.start();
        // 主线程休眠3秒
        Thread.sleep(15000);
        product.add();
        synchronized (product){
            product.notify();
        }

    }
    // 商品
    static class Product{
        // 商品数量
        int count = 0;
        // 生成商品
        public synchronized void add(){
            System.out.println(Thread.currentThread().getName()+"生成了一个商品");
            count++;
            // 唤醒在Product上等待的进程
            this.notify();
        }
        // 消费商品
        public synchronized void consume(){
            if(count>0){
                System.out.println(Thread.currentThread().getName()+"消费了一个商品");
                count--;
            }else{
                System.out.println(Thread.currentThread().getName()+"等待生产中...");
                try {
                    // 消费者在Product对象上进行等待,等待需要获取当前对象的对象级别锁
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 生产者线程
    static class Producer implements Runnable{
        private  Product product;

        public Producer(Product product) {
            this.product = product;
        }

        public void run() {
            for (int i = 0; i < 5; i++) {
                product.add();
                try {
                    // 模拟生产商品时间
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 消费者线程
    static class Consumer implements Runnable{
        private Product product;

        public Consumer(Product product) {
            this.product = product;
        }

        public void run() {
            // 每隔1秒消费一个商品,这里设置消费者消费速度比生成速度快
            while (true){
                System.out.println("消费者准备消费...");
                product.consume();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

wait和sleep

wait sleep
Object上的方法 Thread上的方法
作用于所有在对象上等待的线程 作用于当前线程
等待notify()随机唤醒线程,notifyAll唤醒所有线程 超过时间自动恢复执行
执行wait()后,当前线程释放锁 不会释放锁
public class Demo09WaitSleep {
    public static void main(String[] args) throws InterruptedException {
        Locked locked = new Locked();
        WaitSleep waitSleep = new WaitSleep(locked);
        Thread threadA = new Thread(waitSleep);
        threadA.setName("A");
        Thread threadB = new Thread(waitSleep);
        threadB.setName("B");
        threadA.start();
        Thread.sleep(1000);
        threadB.start();
    }

    static class Locked{
        public synchronized void exec(){
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+" 执行:"+i);
                try {
//                    Thread.sleep(1000);
                    if(Thread.currentThread().getName().equals("A")){
                        wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    static class WaitSleep implements Runnable{
        private Locked locked;

        public WaitSleep(Locked locked) {
            this.locked = locked;
        }

        public void run() {
            locked.exec();
        }
    }
}

线程停止

使用停止标志

public class Demo10ThreadExit {
    // 定义是否停止线程
    static boolean exit = false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            public void run() {
                while (!exit){
                    System.out.println(Thread.currentThread().getName()+" 正在执行");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        Thread.sleep(1000);
        // 将退出标志设置为true
        exit = true;
        System.out.println("将退出标志设置为true");
    }
}

使用interrup()

使用interrupt方法不会终止一个正在运行的线程

需要加上判断条件来终止线程,其实也是使用退出标志

public class Demo11ThreadInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new ThreadInterrupt();
        thread.start();
        Thread.sleep(100);
        // 调用中断方法
        thread.interrupt();
        System.out.println("主线程执行中断方法");
    }

    static class  ThreadInterrupt extends Thread{
        @Override
        public void run() {
            while (true){
                if(this.isInterrupted()){
                    System.out.println("已经是停止状态,即将退出");
                    break;
                }
            }
        }
    }
}

如果线程在sleep()或者wait()状态下被调用interrupt(),会进入异常处理

public class Demo12ThreadSleepInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new SleepInterrupt();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
    static class SleepInterrupt extends Thread{
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("在Sleep中被中断");
            }
        }
    }
}

线程优先级

线程可以划分优先级,优先级越高的线程得到CPU资源较多,而不是优先级高的线程先执行

在Java中,线程的优先级分为1-10这10个等级,默认是5,如果小于1或者大于10,会抛异常

public class Demo13ThreadPriority {
    public static void main(String[] args) throws InterruptedException {
        List<Thread> list = new ArrayList<Thread>();
        for (int i = 1; i <= 10; i++) {
            Thread thread = new Priority();
            thread.setName("thread"+i);
            // 设置优先级
            thread.setPriority(i);
            list.add(thread);
            thread.start();
        }
        Thread.sleep(3000);
        for (Thread thread : list) {
            thread.interrupt();
        }
    }

    static class Priority  extends   Thread{
        private  int i = 0;
        public void run() {
            while (!this.isInterrupted()){
                i++;
            }
            System.out.println(currentThread().getName()+"执行次数:"+i);
        }
    }
}

线程等待join

如果主线程需要等待子线程执行完成后才继续执行,就需要使用join方法

public class Demo14ThreadJoin {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"主线程开始执行");
        // 启动子线程处理任务
        Thread thread = new Thread(new Runnable() {
            public void run() {
                System.out.println(Thread.currentThread().getName()+"子线程开始执行");
                // 模拟子线程执行时间
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"子线程执行结束");
            }
        });
        thread.start();
        // 等待子线程执行结束
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"主线程执行结束");
    }
}

线程让出yield

yield()作用是当前线程放弃CPU资源,将它让给其他的任务,但放弃的时间不确定,有可能刚刚放弃,马上又获取到CPU资源。

public class Demo15ThreadYield {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            // 获取开始执行时间
            long begin = System.currentTimeMillis();
            int count = 0;
            for (int i = 0; i < 50000000; i++) {
                if (i % 1000 == 0) {
                    // 让出CPU资源
                    Thread.yield();
                }
                count = count + i;
            }
            long end = System.currentTimeMillis();
            System.out.println("总耗时: " + (end - begin) + " 毫秒");
        }
    }
}

多线程并发的特性

原子性

有序性

指令重排序,指令重排序不会影响单个线程执行结果,但是在多线程中就会引发问题。

在本线程内观察,所有操作都是有序的,在一个线程中观察另外一个线程,所有的操作都是无序的。

可见性

多个线程访问同一个变量,当一个线程修改了变量的值,其他线程能够立刻看到修改后的值

Java内存模型

Java内存模型规定了所有的变量都存储在主内存中

每条线程有自己的工作内存,线程的工作内存中保存了被该线程使用的的变量的主内存副本拷贝

线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量

不同的线程之间无法直接访问对方工作内存中的变量

线程间变量值的传递需要通过主内存来完成

可见性问题

public class Demo16Visible {
    public static void main(String[] args) throws InterruptedException {
        Visible runnable = new Visible();
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(1000);
        Visible.flag = false;
        System.out.println("已经修改flag为false");
    }

    static class Visible implements Runnable{
        static boolean flag = true;
        public void run() {
            System.out.println("子线程执行");
            while (flag){
                // todo something
            }
            System.out.println("子线程结束");
        }
    }
}

使用synchronized关键字来解决可见性问题,synchronized还能解决原子性和有序性问题

public class Demo16Visible {
    public static void main(String[] args) throws InterruptedException {
        Visible runnable = new Visible();
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(1000);
        Visible.flag = false;
        System.out.println("已经修改flag为false");
    }

    static class Visible implements Runnable{
        static boolean flag = true;
        public void run() {
            System.out.println("子线程执行");
            while (flag){
                synchronized (this){
                    // todo something
                }
            }
            System.out.println("子线程结束");
        }
    }
}

锁优化

在Java虚拟机的实现中每个对象都有一个对象头,用于保存对象的系统信息

对象头中有一个称为Mark Word 的部分,它是实现锁的关键

Mark Word 是一个多功能的数据区,可以存放对象的哈希值、对象年龄、锁的指针等信息。

file

偏向锁

顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程不需要触发不同,这种情况下,就会给线程加一个偏向锁

如果在运行过程中,遇到了其他线程抢占锁,则只有偏向锁的线程会被挂起,JVM会清除它身上的偏向锁,将锁回复到标准的轻量级锁。

版权声明:原创作品,允许转载,转载时务必以超链接的形式表明出处和作者信息。否则将追究法律责任。来自海汼部落-123456789987654321,http://hainiubl.com/topics/75388
点赞
成为第一个点赞的人吧 :bowtie:
回复数量: 0
    暂无评论~~
    • 请注意单词拼写,以及中英文排版,参考此页
    • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
    • 支持表情,可用Emoji的自动补全, 在输入的时候只需要 ":" 就可以自动提示了 :metal: :point_right: 表情列表 :star: :sparkles:
    • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif,教程
    • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
    Ctrl+Enter