【学习-多线程】

学习内容:

  1. 为什么要有多线程
  2. 多线程的两个概念
  3. 多线程的实现方式
  4. 常见的成员方法
  5. 线程的生命周期
  6. 线程安全问题
  7. Lock锁
  8. 死锁
  9. 生产者和消费者
  10. 线程池

学习产出:

为什么要有多线程

进程:进程是程序执行的基本实体(一个软件运行之后就是一个进程)
线程:线程是操作系统能够调度的最小单位,包含在进程中,是进程中的实际运作单位。

多线程可以让程序同时做多件事,已提高效率,


多线程的两个概念

并发:同一时刻,多个指令,在单个CPU上交替执行
并行:同一时刻,多个指令,在多个CPU上同时执行


多线程的实现方式

继承Thread类

       Thread thread=new Thread(){
           @Override
           public void run() {
               for (int i = 0; i < 100; i++) {
                   System.out.println(getName()+"world");
               }
           }
       };
       thread.setName("hello");
       thread.start();

实现Runnable接口

public class MyThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"hellp");
        }
    }
}

利用Callable接口和Future接口方式实现

/*
* 特点:可以获取到多线程运行的结果
* 1,创建一个类实现Callable接口
* 2,重写call(有返回值,表示多线程运行的结果)
* 3,创建对象,(表示多线程执行的任务)
* 4,创建FutureTask对象(作用管理多线程运行结果)
* 5,创建Thread类对象,并启动
* */
public class MyThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        //求1-100和
        int res=0;
        for (int i = 0; i < 100; i++) {
            res+=i;
        }
        return res;
    }
}
         //创建对象
        MyThread myThread = new MyThread();
        //创建FutureTask对象
        FutureTask<Integer> futureTask=new FutureTask<>(myThread);
        //创建Thread类对象,并启动
        Thread thread=new Thread(futureTask);
        //获取到结果
        Integer res = futureTask.get();
        System.out.println(res);

常见的成员方法

【学习-多线程】_第1张图片java中是抢占式调度(是随机的),优先级越高,抢占到线程的概率越大

Thread thread1=new Thread(()->{
           for (int i = 0; i < 100; i++) {
               System.out.println(Thread.currentThread().getName()+i);
           }
       },"飞机");

        Thread thread2=new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName()+i);
            }
        },"坦克");
//输出可以看到默认为5
        System.out.println(thread1.getPriority());
        System.out.println(thread2.getPriority());
        System.out.println(Thread.currentThread().getPriority());
//优先级为1-10
        thread1.setPriority(1);
        thread2.setPriority(10);

        thread1.start();
        thread2.start();


      //当其他线程结束时,守护线程也会结束
         Thread thread1=new Thread(()->{
           for (int i = 0; i < 10; i++) {
               System.out.println(Thread.currentThread().getName()+i);
           }
       },"女神");

        Thread thread2=new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName()+i);
            }
        },"坦克");

        thread2.setDaemon(true);
        thread1.start();
        thread2.start();

//出让当前Cpu的执行权
Thread.yield();
//下面代码,thread2是在线程1启动之前,但是thread1.join();表示插入到thread2线程之前执行
Thread thread1=new Thread(()->{
           for (int i = 0; i < 10; i++) {
               System.out.println(Thread.currentThread().getName()+i);
           }
       },"女神");

        Thread thread2=new Thread(()->{
            try {
                thread1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName()+i);
                Thread.yield();
            }
        },"坦克");

        thread2.start();
        thread1.start();

线程的生命周期

【学习-多线程】_第2张图片

线程安全问题

同步代码块:把操作共享数据的代码锁起来
特点:

  1. 锁默认打开,有一个线程进去了,锁自动关闭
  2. 里面的所有代码执行完成,线程出来,锁自动打开
  3. 锁对象需要是唯一的
//格式
synchronized(){
   //操作共享数据的代码
}

//买票问题
 while (true){
            synchronized(MyThread1.class){
               if ( num<100){
                   num++;
                   try {
                       Thread.sleep(10);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName()+"在卖第"+num+"张票");
               }else {
                   break;
               }

            }
        }

同步方法:把synchronized关键字加到方法上
特点

  1. 同步方法锁定的是方法里面的所有代码
  2. 锁对象不能自己指定(当前方法是非静态的,this;如果是静态的,当前类的字节码文件对象)
//格式
修饰符 synchronized 返回值 方法名(参数)

//如果不会写,可以先写同步代码块 ,然后ctrl+alt+m可以抽取成一个方法
public class MyThread1 implements Runnable {
   static int num=0;
    @Override
    public void run() {
        while (true){
            if (extracted()) break;
        }
    }
    private synchronized boolean extracted() {
        if (num<100){
            num++;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"在卖第"+num+"张票");
        }else {
            return true;
        }
        return false;
    }
}

 Runnable myThread1 = new MyThread1() {
        };

        Thread thread =new Thread(myThread1,"窗口1");
        Thread thread2 =new Thread(myThread1,"窗口2");
        Thread thread3 =new Thread(myThread1,"窗口3");

        thread.start();
        thread2.start();
        thread3.start();

Lock锁

为了更加清晰的了解如何加锁和释放锁,jdk1.5提供了一个新的锁对象Lock
lock()获得锁
unlock()释放锁
手动上锁,手动释放锁
Lock接口不能直接实例化,采用他的实现类ReentrantLock来实例化

//这里要注意的是 在一个线程结束的时候一定要执行lock.unlock();,要不然其他的线程都结束不了
public class MyThread1 extends Thread{
   static int num=0;
   static Lock lock=new ReentrantLock();

    public MyThread1(String name) {
        super(name);
    }
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if ( num<100){
                    num++;
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"在卖第"+num+"张票");
                }else {
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

死锁

相当于锁嵌套,a线程拿到a资源之后要去拿b资源,b线程拿了b资源要去拿a资源,两个线程就死锁了

生产者和消费者(等待唤醒机制)

生产者和消费者模式是一个十分经典的多线程协作模式
【学习-多线程】_第3张图片

常见方法

void wait()//当前线程等待,直到被唤醒
void notify()//随机唤醒单个线程
void notifyAll()//唤醒所有线程

//实现
public class Desk {

    //控制生产者和消费者的执行
    //是否有面条 0没有 1有
    public static int foodFlag=0;

    //总个数
    public static int count=10;

    static Object lock=new Object();
}

public class Cook extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized(Desk.lock){
                if (Desk.count==0){
                    break;
                }else {
                    //判断桌子上是否有食物
                    //如果有,就等待
                    if(Desk.foodFlag==1){
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else {
                        //没有没有。制作
                        System.out.println("厨师做了一碗");
                        //修改做字状态
                        Desk.foodFlag=1;
                        //等待消费者开吃
                        Desk.lock.notifyAll();

                    }

                }
            }
        }
    }
}

public class Foodie extends Thread{
    @Override
    public void run() {
        /*循环
        * 同步代码块
        * 判断共享数据是否到了末尾(到了)
        * 没有到末尾*/
        while (true){
            synchronized(Desk.lock){
                if (Desk.count==0){
                    break;
                }else {
                    //判断桌子上是否有食物
                    //没有,等待
                    if (Desk.foodFlag==0){
                        try {
                            //让当前线程和锁进行绑定,唤醒的时候也是通过这个对象
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else {
                        //有,消费
                        //吃的总是-1
                        Desk.count--;
                        System.out.println("开始消费,还能吃"+Desk.count);
                        //消费完成,唤醒厨师
                        Desk.lock.notifyAll();

                        //修改桌子状态
                        Desk.foodFlag=0;
                    }


                }
            }
        }
    }
}

【学习-多线程】_第4张图片【学习-多线程】_第5张图片

//利用阻塞队列实现
//需要注意:生产者和消费者需要使用同一个阻塞队列


        //创建阻塞队列对象
        ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(1);
        //创建线程对象,并且把阻塞队列传过去
        Cook cook=new Cook(queue);
        Foodie foodie = new Foodie(queue);
        cook.start();
        foodie.start();

public class Cook extends Thread{

    ArrayBlockingQueue<String> queue;
    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue=queue;
    }
    @Override
    public void run() {
        while (true){
            try {
                queue.put("面条");
                System.out.println("做了一份");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


public class Foodie extends Thread{
    ArrayBlockingQueue<String> queue;
    public  Foodie(ArrayBlockingQueue<String> queue){
        this.queue=queue;
    }
    @Override
    public void run() {
        /*循环
        * 同步代码块
        * 判断共享数据是否到了末尾(到了)
        * 没有到末尾*/
        while (true){
            try {
                String take = queue.take();
                System.out.println(take);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

【学习-多线程】_第6张图片但是实际上JAVA中定义的只有这六种
新建:NEW
就绪状态:RUNABLE
阻塞状态:BLOCKED
无限期等待:WAITING
计时等待:TIMED_WAITING
结束状态:TERMINATED

线程池

需要线程的时候就去创建,用完就消失了
核心原理

  1. 创建一个池子,池子是空的
  2. 提交任务时,创建一个线程,任务执行完毕,线程归还池子,下回再提交任务时,不需要创建新的线程,直接复用即可
  3. 提交任务时,池子中没有空闲线程,也无法创建 新的线程,任务会排队等待

代码实现

//1,创建线程池
ExecutorService pool1 = Executors.newCachedThreadPool();
//有上限的线程池
ExecutorService pool2 = Executors.newFixedThreadPool(3);


//2,提交任务
 pool1.submit(new MyThread1());
//3,所有任务执行完毕,关闭线程池
pool1.shutdown();

自定义线程池
【学习-多线程】_第7张图片

什么时候创建临时线程,核心线程都没有空闲,且排队的任务排满。(所以不一定先提交的就会先执行)

【学习-多线程】_第8张图片

 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                3,//核心线程数量,不能小于0
                6,//最大核心线程数,不能小于0,>=核心线程数
                60,//空闲线程最大存活时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(3),//任务队列
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务拒绝方式
        );

最大线程数应该是CPU核心的两倍比较合适

你可能感兴趣的:(java,学习,java)