多线程初阶总结

多线程基础前言

在这里插入图片描述

●进程是系统分配资源的最小单位,线程是系统调度的最小单位。一个进程内的线程之间是可以共享资源的。
● 进程调用系统api,申请系统分配资源(Java进程就是分配Java内存空间, 通过JVM虚拟机初始化共享区域)
●线程:线程也存在并发.并行(单个CPU时间片轮转、一个时间点,多个CPU上的真同时)

一、创建线程

1.匿名内部类

2.继承Thread类

3.实现Runnable接口

 class Main{
    public static void main(String[] args) {
      MyThread myThread=new MyThread();
//     myThread.start();//匿名内部类
    myThread.run();//run方法直接调用,不会启动线程,只是在线程main中,调用run方法

       new Thread(new MyRunnable()).start();

    }
}
 class MyThread extends Thread {//继承Thread类
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
class MyRunnable implements Runnable{//实现Runnable接口
     @Override
    public void run(){
         System.out.println(Thread.currentThread().getName());
     }
}

二、线程基本API

1.线程的让步

Thread.yield();//运行态–>就绪态

public class ThreadYield {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
        while(Thread.activeCount()>1){//使用调试的方法运行
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

2.线程的等待

(1)join

public static void main(String[] args) throws InterruptedException {
   Thread t= new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    });
    t.start();
    //先将T线程执行完毕,再往下执行
    t.join();
    t.join(2000);
    System.out.println(Thread.currentThread().getName());
}

(2)结合activeCount()+yield();使用

public class ThreadYield {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
        while(Thread.activeCount()>1){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

3.线程中断

(1)isInterrupted()
(2)nterrupted():不是真实的直接中断,而是告诉某个线程需要中断,具体是否中断由该线程自己决定
(3)interrupt()
(4)过期方法stop();

注意

1.线程启动以后:中断标志位=false

2.在线程运行态中,处理线程中断,需要自行通过判断中断标志位,来进行中断的处理逻辑。通过方法thread.isInterrupted()/Thread.interrupted()
3.线程因调用wait()/join()/sleep()处于阻塞状态时,将线程中断,会造成:
(1)在这三个阻塞方法所在的代码行,直接抛InterruptedException异常
(2)抛出异常之后,重置线程的中断标志位(=true)
4.static void interrupted():返回中断标志位,并重置标志位
void isInterrupted():返回中断标志位
5.自定义的标志位满足不了线程处于阻塞状态时,中断操作


public class InterruptThread {
    //中断一个线程,但是线程没有处理中断
    public static void test1(){
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){

                }
            }
        });
        t.start();
        t.interrupt();
    }
    //
    public  static  void test2() throws InterruptedException {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {//线程运行状态下处理中断标志
                for(int i=0;i<50;i++){
                    System.out.println(i+""+Thread.currentThread().isInterrupted());
                }
//              while (!Thread.currentThread().isInterrupted()){
                while (!Thread.interrupted()){
                  System.out.println(Thread.currentThread().getName());
              }
            }
        });
        t.start();//t线程中的中断标志位=false
        t.interrupt();//t线程中的中断标志位=true
    }
    public  static  void test3() throws InterruptedException {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().isInterrupted());//true
                    //线程处于调用wait()/join()/sleep()阻塞的时候,如果把当前线程中断,会直接抛出一个异常
                    //阻塞状态时,通过捕获及处理异常,来处理线程的中断逻辑
                    //抛出异常以后线程的标志位被重置
                    Thread.sleep(9999999);
                    System.out.println(Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(Thread.currentThread().isInterrupted());//false
                }

            }
        });
        t.start();//t线程中的中断标志位=false
        t.interrupt();//t线程中的中断标志位=true
    }
    public  static  void test4() throws InterruptedException {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
               for (int i=0;i<10;i++){
                   System.out.println(Thread.currentThread().isInterrupted());
                  // System.out.println(Thread.interrupted());//返回中断标志位,并重置标志位
               }

            }
        });
        t.start();//t线程中的中断标志位=false
        t.interrupt();//t线程中的中断标志位=true
    }
    private  static volatile boolean IS_INTERRUPTDE;
    //使用自定义的中断标志位
    public static  void test5(){
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                //自定义的标志位 能满足线程处于运行态的中断操作
//                while (!IS_INTERRUPTDE){
//                    System.out.println(Thread.currentThread().getName());
//                }
                //自定义的标志位满足不了西安城处于阻塞状态时,中断操作
                try {
                    Thread.sleep(99999);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        IS_INTERRUPTDE=true;
    }
    public static void main(String[] args) throws InterruptedException {
        //test1();
        //test2();
        // test3();
        // test4();
        test5();
    }
}


4.守护线程

●至少有一个非守护线程没有被销毁,进程就不会退出
●非守护线程一般可以称为工作线程,守护线程可以称为后台线程

public class DaemonThread {
    public static void main(String[] args) {
         Thread t=new Thread(new Runnable() {
             @Override
             public void run() {
                 try {
                     Thread.sleep(99999999L);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
         });
         //设置线程为守护线程
         t.setDaemon(true);
         t.start();
    }
}

5.线程的启动

run()和start()

MyThread myThread=new MyThread();
myThread.start();
//run 方法直接调用,不会启动现线程,只会在当前main线程中调用run方法
//myThread.run();

三、多线程的运行效率

在这里插入图片描述使用多线程提高效率需要考虑的因素

(1)所有线程执行是并发+并行
(2)线程创建、销毁是比较耗时
(3)线程的调度由系统决定(线程越多,系统调度越频繁;线程就绪态转变为运行态,也是有性能及时间消耗)
(4)单个线程运行的任务量

四、线程的安全问题

1.原子性

卖票操作
在这里插入图片描述
●A-1和A-2不具有原子性,导致在代码行之间插入了并发/并行执行的其他代码(B-1)
●造成的结果:业务逻辑处理出现问题

特殊的原子性代码
(分解执行存在编译为class文件时,也可能存在cpu执行指令) :

(1) n++, n–, ++n, --n都不是原子性: 需要分解为三条指令:从内存读取变量到CPU,修改变量,写回内存
(2)对象的new操作: Object obj = new Object(); 分解为三条指令:分配对象的内存,初始化对象,将对象赋值给变量

2.可见性

在这里插入图片描述

new Thread(new Runnable() {
@override
public void run() {
for(int j=0; j<COUNT; j++){
SUN++;//1.从主内存中将SUM变量复制到线程的工作内存
}          //2.在工作内存中修改变量(+1操作)
}         //3.将SUM变量从线程工作内存写回主内存
}).start(); 


在这里插入图片描述

主内存SUM=0;
●A-1
●A-2 (+1操作—>线程A的工作内存中, SUM=1,主内存SUM=0)
●B-1(从主内存复制SUM=0)
●B-2 (+ 1操作—>线程B的工作内存中,SUM=1,主内存SUM=0) ●B-3 (线程B中的SUM变量,将值写回主内存SUM=1)
●A-3 (线程A中的SUM变量,写回主内存SUM=1)
●造成线程不安全:共享变量发生了修改的丢失(线程B的++操作,发生丢失)

3.重排序

●线程内代码是JVM、CPU都进行重排序,给我们的感觉是线程内的代码是有序的,是因为重排序优化方案会保证线程内代码执行的依赖关系
●线程内看自己的代码运行,都是有序,但是看其他线程代码运行,都是无序的。

五、synchronized关键字

语法使用:

1.静态方法.上
2.实例方法
3.代码块: synchronized(对象){
new的对象.class都是对象
// TODO

在这里插入图片描述*进入synchronized代码行时,需要获取对象锁:

1.获取成功:往下执行代码
2.获取失败:阻塞在synchronized代码行

退出synchronized代码块,或synchronized方法:

1.退回对象锁
2.通知JVM及系统,其他线程可以来竞争这把锁

synchronized加锁操作的关注点:

1.对哪一个对象进行加锁一一个对象只有一把锁
2.只有同一一个对象,才会有同步互斥的作用(多线程线程安全的三大特性部能够满足)
3.对于synchronized内的代码来说,在同一个时间点,只有一个线程在运行(没有井发、并行)
4.运行的线程数量越多,性能下降越快(归还对象锁的时候,就有越多的线程不停的在被唤醒。阻塞状态切换)
5.同步代码执行时间越短,性能下降也较快

在这里插入图片描述多线程操作考虑:1.安全2.效率

在保证安全的前提条件下,尽可能的提高效率:
1.代码执行时间比较长,考虑多线程(线程的创建。销毁的时间消耗)
2.如果不能保证安全,所有代码都没有意义一-先保证安全, 再保证效率

六、volatile关键字

(1)保证可见性 (2)保证有序性
注意点:

1.不能保证原子性
2.volatile修饰的变量,进行赋值不能依赖变量(常量赋值可以保证线程安全)
3.使用场景: volatile可以结合线程加锁的一些手段,提高线程效率 只是变量的读取、常量赋值,可以不加锁,而是使用volatile, 可以提高效率

七、单例模式

三种实现方式

1.饿汉模式

public class Sington {
    private Sington(){
    }
private static Sington SINGTON=new Sington();
   public static Sington getInstance(){
        return SINGTON;
    }

2.懒汉模式

public class Sington {
    private Sington(){
    }
    private static  Sington SINGTON;

    public static Sington getInstance() {
        if (SINGTON == null) {
            SINGTON = new Sington();
        }
        return SINGTON;
    }

3.双重校验锁

原理:

1.volatile关键字修饰变量
2.私有构造方法
3.双重校验锁的写法保证线程安全

public class Sington {
    private Sington(){
    }
private static volatile Sington SINGTON;
    public static  Sington getInstance() {
        if (SINGTON == null) {//提高效率:变量使用Volatile可以保证可见性
          synchronized (Sington.class){
              //保证单例模式:返回是同一个对象
              if(SINGTON==null){
                  //三条指令:分配内存,初始化对象,赋值给变量
                  SINGTON=new Sington();
              }
          }
        }
        return SINGTON;
    }
}

八、线程的状态

在这里插入图片描述

九、wait/notily0/notifyAIl()

生产者/消费者模型



public class BreadOperator {

    // 库存面包数量:上限100,下限0
    public static volatile int SUM;

    public static void main(String[] args) {
        // 启动生产者线程,生产面包
        for(int i=0; i<5; i++){
            new Thread(new Producer(), "面包师傅"+i).start();
        }
        //启动消费者线程,消费面包
        for(int i=0; i<5; i++){
            new Thread(new Consumer(), "消费者"+i).start();
        }
    }

    // 默认生产者:面包师傅生产面包,一次生产3个面包,每个面包师傅生产20次
    private static class Producer implements Runnable{

        @Override
        public void run() {
            try {
                for(int i=0; i<20; i++){
                    synchronized (BreadOperator.class){
                        //生产完以后,库存>100是不行,所以库存在97以上就不能生产
                        while (SUM + 3 > 100){
                            //释放对象锁,需要让其他线程进入同步代码块,当前线程需要进入阻塞
                            BreadOperator.class.wait();
                        }
                        SUM += 3;//生产面包
                        Thread.sleep(10);
                        BreadOperator.class.notify();
                        System.out.println(Thread.currentThread().getName()+", 生产了,库存为:"+SUM);
                    }
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 默认消费者:消费面包,一次消费一个面包,消费者一直消费
    private static class Consumer implements Runnable{

        @Override
        public void run() {
            try {
                while (true){
                    synchronized (BreadOperator.class){
                        //库存为0,不能继续消费,阻塞当前线程(不能继续消费)
                        while (SUM == 0){
                            BreadOperator.class.wait();
                        }
                        SUM--;
                        Thread.sleep(10);
                        //notify()/notifyAll()都是通知调用wait()被阻塞的线程
                        //notify随机唤醒一个wait()阻塞的线程,notifyAll()唤醒全部wait()阻塞的线程
                        //在synchronized代码块结束,也就是释放对象锁之后,才会唤醒
                        //等于说,synchronized结束之后,wait()和synchronized代码行阻塞的线程,都会被唤醒
                        BreadOperator.class.notify();
                        System.out.println(Thread.currentThread().getName()+", 消费了,库存为:"+SUM);
                    }
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

十、阻塞式队列

利用数组和循环队列实现阻塞式队列


/*
* 实现阻塞式队列:
* 1.满足线程安全的生产、消费的功能
* 2.生产、消费达到上限/下限时,需要阻塞等待
* */
public class MyBlockQueue<E> {

    private Object[] item;

    private int takeIndex;//弹出元素的索引位置
    private int putIndex;//添加元素的索引位置
    private int size;//有效容量

    public MyBlockQueue(int capacity){
        item=new Object[capacity];

    }
//线程间通信,推荐做法:
    //1.whlie来判断,不要使用if(因为在判断代码中进行wait释放锁以后,其他线程会修改变量,再次wait恢复的时候,条件已经不满足)
   //使用notifyAll方法,通知所有wait被阻塞的线程
    public synchronized void  put(E e) throws InterruptedException {
       while (size==item.length){//达到上限需要等待
          wait();
       }
        putIndex=(putIndex+1)%item.length;//存放元素的索引++,需要满足循环队列索引》数组长度的情况
       item[putIndex]=e;//存放元素
        size++;
        notifyAll();
    }

    public synchronized E take() throws InterruptedException {
        while (size==0){//达到下限,需要等待
            wait();
        }
        takeIndex=(takeIndex+1)%item.length;
        size--;
        notifyAll();
        return (E) item[takeIndex];
    }


    public static void main(String[] args) {
      MyBlockQueue<Integer> queue=new MyBlockQueue<>(100);
       for(int i=0;i<5;i++){
           final int k=i;//匿名内部类需要使用被final修饰的变量
       new Thread(new Runnable() {
           @Override
           public void run() {
               try {
                   for(int j=0;j<100;j++){
                       queue.put(k*100+j);
                   }
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       }).start();
       }
       while (true){
           int num= 0;
           try {
               num = queue.take();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(num);
       }
    }
}

十一、线程池

现实模型对比:

●快递公司(线程池)可以接收很多的快递任务(可以>4, Runnable任务类) ,如果员工(线程池中创建的线程)没有空闲(正在干活.忙碌)。快递包裹就到快递公司的仓库中(线程地内部的一个属性,阻塞队列) .员工不停从仓库中取包裹,送快递,如果仓库中没有包裹,员工就等待,一直等到有包裹再派送(执行Runnable对象的run方法)

1.创建线程池

创建方式

① ExecutorService pool = new ThreadPoolExecutor();
②ExecutorService pool=Executors.newFixedThreadPool(4);//固定大小线程池,给定员工数量没有零时工
③ExecutorService pool=Executors.newSingleThreadExecutor();//数量为一的固定大小的线程池,就一个员工
④ ExecutorService pool=Executors.newScheduledThreadPool(4);//定时任务线程池:有正式员工,有零时员工
⑤ExecutorService pool=Executors.newCachedThreadPool();//缓存线程池:无正式员工全部为临时工(外包公司)
 

参数

 corePoolSize,// 核心线程数(正式员工):创建好线程池,正式员工就开始取快递
                // 临时工雇佣:正式员工忙不过来,就会创建临时工
                // 临时工解雇:空闲时间超出设置的时间范围,就解雇
maximumPoolSize ,// 最大线程数(最多数量的员工:正式员工+临时工) 
keepAliveTime  ,// 时间数量
TimeUnit.SECONDS,// 时间单位(时间数量+时间单位表示一定范围的时间)
 new ArrayBlockingQueue<>(1000),  // 阻塞队列:存放包裹的仓库(存放任务的数据结构)
 new ThreadFactory() {  // 线程池创建Thread线程的工厂类。没有提供的话,就使用线程池内部默认的创建线程的方式
                   @Override
                    public Thread newThread(Runnable r) {
                        return null;
                    }
              },
 new ThreadPoolExecutor.DiscardOldestPolicy() // 拒绝策略:
                // CallerRunsPolicy:谁(execute代码行所在的线程)让我(快递公司)送快递,不好意思,你自己去送
                // AbortPolicy:直接抛出异常RejectedExecutionException
                // DiscardPolicy:从阻塞队列丢弃最新的任务(队尾)
                // DiscardOldestPolicy:从阻塞队列丢弃最旧的任务(队首)

2.实现自己的线程池



import lesson6.MyBlockQueue;

public class MyThreadPool {

    private MyBlockQueue<Runnable> queue;
    public MyThreadPool(int size,int capacity){

        queue=new MyBlockQueue<>(capacity);
        //创建正式员工
        for (int i=0;i<size;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true){//正式员工一直运行
                            Runnable task=queue.take();
                            //从仓库取包裹:
                            // 1.成功取出包裹(任务),方法返回
                            //2.仓库中取不出包裹(其他员工正在取阻塞在synchronized代码行;没有包裹了)
                            task.run();
                            //正式员工送快递(实例方法的调用来执行任务)
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }).start();
        }
    }
    public  void execute(Runnable task){
        try {
            queue.put(task);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        MyThreadPool pool=new MyThreadPool(5,100);
        pool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("A");
            }
        });
        pool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("B");
            }
        });
        pool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("C");
            }
        });
    }
}

十二、定时器

1.jdk的时间操作

(1) Date

   Date date1 = new Date();//无参构造方法:返回系统当前时间
    Date date2 = new Date(99999999);//以格林威治时1970-01-01开始,经过给定时间数量的毫秒

(2)DateFormat

    DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

(3)System时间获取

从1970-01-01开始到当前时间点经过的毫秒数
 long current = System.currentTimeMillis();

(4)JDK原生提供日期处理器

        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println("起床了");
            }
        };
        new Timer().schedule(task, 3000, 1000);

2.自己实现定时器

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.*;

/**
 * 定时器:
 * 1.在约定好的时间点上,执行某个任务
 * 2.间隔时间到了,不停的执行任务
 */
public class MyTimer {

    private BlockingQueue<MyTimerTask> queue = new PriorityBlockingQueue();

    public MyTimer(int count){
        for(int i=0; i<count; i++) {
            new Thread(new MyWorker(queue)).start();
        }
    }

    /**
     * 定时任务
     * @param task 需要执行的任务
     * @param delay 从当前时间延迟多少毫秒,执行任务
     * @param period 间隔时间:<=0就忽略掉,>0需要每间隔给定时间,就执行任务
     */
    public void schedule(Runnable task, long delay, long period){
        try {
            queue.put(new MyTimerTask(task, System.currentTimeMillis()+delay, period));
            synchronized (queue){
                queue.notifyAll();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static class MyWorker implements Runnable{

        private BlockingQueue<MyTimerTask> queue;

        public MyWorker(BlockingQueue<MyTimerTask> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    //blockingQueue本身就是线程安全的,所以这里的方法调用不用放在同步代码块
                    MyTimerTask task = queue.take();
                    synchronized (queue) {
                        long current = System.currentTimeMillis();
                        if (task.next > current) {
                            queue.wait(task.next-current);
                            queue.put(task);
                        } else {
                            task.task.run();
                            if (task.period > 0) {
                                task.next = task.next + task.period;
                                queue.put(task);
                            }
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static class MyTimerTask implements Comparable<MyTimerTask>{
        //定时任务
        private Runnable task;
        //下次执行的时间
        private long next;
        private long period;
        public MyTimerTask(Runnable task, long next, long period){
            this.task = task;
            this.next = next;
            this.period = period;
        }

        @Override
        public int compareTo(MyTimerTask o) {
            return Long.compare(next, o.next);
        }
    }

十三、多线程使用场景

CPU执行指令:(1)计算指令 (2)逻辑指令 (3)数据拷贝
某Java进程,程序可能偏向
(1)计算密集型任务 (2)IO密集型任务

1.执行比较耗时的操作时,使用多线程
2,执行阻塞式代码时,会对当前线程造成阻塞时

十四、对比线程和进程

线程优点

  1. 创建一个新线程的代价要比创建一个新进程小得多
  2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  3. 线程占用的资源要比进程少很多
  4. 能充分利用多处理器的可并行数量
  5. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务 比特科技
  6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  7. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

线程和进程的区别

  1. 进程是系统进行资源分配和调度的一个独立单位,线程是程序执行的最小单位。
  2. 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。
  3. 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。
  4. 线程的创建、切换及终止效率更高。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值