多线程编程的详解

相关概念:
  • 进程:进程是程序的基本执行实体,一个软件运行就是一个进程
  • 线程:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程之中的实际单位
  • 例如,一个软件的互相独立,同时运行的功能,这个软件可以看作进程,各个功能可以看作是线程

CPU可以在多个程序之间进行切换,把等待的时间进行充分利用起来

一个进程当中,可以有多条线程,但一个进程当中,至少有一个线程

并发:在同一时间,有多个任务在单个CPU上交替执行,单核心,单CPU编程

并行:在同一时间,有多个任务在多个CPU上同时执行,多核心,多CPU编程

并发和并行是有可能同时发生的

每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程

每一个线程启动之后,都会创建一个处于自己的线程栈

多线程的实现方式
  • 继承Thread类的方式
  • 实现Runnable接口的方式
  • 利用Callable和Future接口的方式

继承Thread类的方式:

将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例

public static void main(String[] args) {
        /*
        *   多线程的第一种启动方式:
        *   自己定义的类继承Thread
        *   重写Run方法
        *   创建子类的对象,并启动线程
        *
        *
        * */
        Student student = new Student();
        student.setName("线程1");
        student.start();


        Student student1 = new Student();
        student1.setName("线程2");
        student1.start();



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

实现Runnable接口的实现方式

Thread.currentThread()——获取当前执行线程的对象

public static void main(String[] args) {
        /*
        *           多线程启动的第二种方式
        *           创建一个子类实现Runnable接口
        *           重写Run方法
        *           创建子类对象,表示多线程要执行的任务
        *           创建Thread对象,并把要执行的任务传入Thread当中,Thread就表示线程
        *           开启线程
        *
        *
        * */
        User user = new User();
        Thread thread = new Thread(user);
        thread.setName("线程1");
        thread.start();


        Thread thread1 = new Thread(user);
        thread1.setName("线程2");
        thread1.start();




    }
public class User implements Runnable {
    @Override
    public void run() {
        //获取当前线程的对象
        Thread thread = Thread.currentThread();
        for (int i = 0; i < 100; i++) {
            System.out.println(thread.getName()+"HelloWorld");
        }
    }
}

利用Callable和Future方式实现

可以获取多线程运行的结果

实现Callable的时候会有泛型,泛型里的数据类型就是多线程执行的结果

  /*
    *           多线程实现的第三种方式
    *           1. 创建一个子类,实现Callable接口,并重写call方法
    *           2. 创建子类对象,表示多线程要执行的任务
    *           3. 创建一个FutureTask类,来管理多线程返回的结果
    *           4. 创建Thread,表示线程
    *
    *
    * */
public static void main(String[] args) throws ExecutionException, InterruptedException {
        Animal animal = new Animal();
        FutureTask<Integer>futureTask = new FutureTask<>(animal);
        Thread thread = new Thread(futureTask);
        thread.start();
        Integer integer = futureTask.get();
        System.out.println(integer);
    }
public class Animal implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum=0;
        for (int i = 0; i < 100; i++) {
            sum = sum+i;
        }



        return sum;
    }
}
三种实现方法的优缺点

优点

缺点

继承Thread类

编程较简单,可以直接使用Thread的方法

可扩展性差,不能够继承其他类

实现Runnable接口

扩展性强,实现接口的同时,还能继承其他类

编程复杂,不能直接使用Thread中的方法

实现Callable接口

扩展性强,实现接口的同时,还能继承其他类

编程复杂,不能直接使用Thread中的方法

常见的成员方法

方法名称

说明

String getName()

返回此线程的名字

void setName(String name)

设置线程的名字,可以用构造方法设置名字

static Thread currentThread()

获取当前线程的对象

static void sleep(long time)

让线程休眠指定的时间,单位:毫秒

setPriority(int newPriority)

设置线程优先级,默认的优先级是5

final int getPriority()

获取线程的优先级

final void setDaemon(boolean on)

设置为守护线程

public static void yield()

礼让线程

public static void join()

插入线程

当JVM虚拟机启动之后,会自动启动多条线程

其中有一条线程叫做main线程

他的作用就是执行main方法里面的代码,并执行里面的代码

在Java中使用的是抢占式调度的规则来执行进程的,优先级越高抢到cpu的概率越大

守护线程:当其他的非守护线程执行完之后,守护线程也跟着陆续结束,可能不会马上结束,但也没有存在的必要了

礼让线程: 出让当前CPU的执行权,尽可能地平均执行

插入线程:插入到当前线程之前

线程的生命周期

同步代码块

变量加上static表示:该类的所有对象共享这个变量

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

  • 锁默认打开,有一个线程进去了,锁自动关闭
  • 里面的代码全部执行完毕,线程出来,锁自动打开
  • 锁对象一定要是唯一的
  • 保持唯一性的关键就是在变量前面加static关键字,这样就算类中创建其他的锁对象,也表明这个变量是共享的
  • 可以采用当前类的字节码文件作为锁对象,因为在一个路径在只能存在一个这样名称的字节码文件,保证了唯一性

synchronized(锁对象){
操作共享数据的代码

}

同步方法
  • 同步方法就是锁住方法里面的所有代码
  • 锁对象不能自己指定
  • 非静态的是this
  • 静态的是当前类的字节码文件对象

修饰符 synchronized 返回值类型 方法名(方法参数列表){

}

当使用Runnable来实现多线程时,可以不用定义共享数据,因为只会创建一次对象

但是如果用继承Thread的方式实现多线程时,必须定义共享数据,会多次创建对象

Lock锁

为了更清晰的表示如何加锁和释放锁,JDK5以后提供了新的锁对象Lock

Lock实现提供比使用synchronized方法和语句可以更广泛的对锁操作

Lock中提供了获得锁和手动释放锁的方法

void lock()——获得锁

void unlock()——手动上锁,释放锁

Lock是接口不能够被实例化, 对他的实现类ReentrantLock来实例化

ReentrantLock的构造方法

ReentrantLock()——创建一个ReentrantLock的实例

锁对象在写的时候,当使用Runnable来实现多线程时,可以不用定义共享数据,因为只会创建一次对象

但是如果用继承Thread的方式实现多线程时,必须定义共享数据,会多次创建对象

关锁的方法无论怎么样都要执行,所以可以把关锁的方法写到finally结构体当中

死锁

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

java 死锁产生的四个必要条件:

  • 1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
  • 2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
  • 3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
  • 4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
生产者消费者模式(等待唤醒机制)

void wait()——当前线程等待,直到被其他线程唤醒

void notify()——唤醒一个线程

void notifyAll()——唤醒所有线程

在调用wait和notifyAll方法的时候应该使用锁对象调用

wait表示让当前线程和锁对象进行绑定

notifyAll表示唤醒与这个锁有关的所有线程

package myThread;

public class Cookie extends Thread{

    @Override
    public void run() {
        //循环
        //同步代码块
        //判断共享数据是否到了末尾,到了末尾会怎么样执行
        //判断共享数据是否到了末尾,没到末尾会怎么样执行
        while(true){
            synchronized (Desk.lock){
                //判断共享数据是否到了末尾
                if (Desk.count==0){
                    //到了末尾,直接跳出
                    break;
                }else {
                    //没到末尾会怎么执行
                    //先判断桌子上食物状态
                    //如果是0,等待,
                    if (Desk.footFlag==0){
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }

                    }else {
                        //如果是1,就开吃,吃完后唤醒厨师做食物,开吃,修改食物状态,修改共享数据
                        Desk.count--;
                        System.out.println("吃货正在吃面条,还能吃"+Desk.count+"碗!!!");
                        Desk.lock.notifyAll();
                        Desk.footFlag=0;

                    }
                }
            }

        }
    }
}


package myThread;

public class Foodie extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                //判断共享数据是否到了末尾
                if (Desk.count==0){
                    break;
                }else {
                    //先判断食物的状态
                    //如果是1就等待
                    //如果是0就做一碗,然后唤醒吃货吃
                    if (Desk.footFlag==1){
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        System.out.println("厨师做了一碗面条");
                        Desk.footFlag=1;
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }

}




package myThread;

public class Desk {
    //表示桌子上的食物状态,如果是0就表示没有,如果是1就表示有
    public static int footFlag = 0;
    //创建一个锁对象
    public static Object lock = new Object();
    //表示吃货还能吃几碗
    public static int count = 10;

}




package myThread;

public class ThreadTest05 {
    public static void main(String[] args) {
        Cookie cookie = new Cookie();
        Foodie foodie = new Foodie();
        cookie.setName("厨师");
        foodie.setName("吃货");
        cookie.start();
        foodie.start();
    }
}
等待唤醒机制(阻塞队列方式实现)

阻塞队列相当于连接生产者和消费者的通道,生产者和消费者必须使用同一个阻塞队列

先放进去的先拿出来

阻塞队列的继承结构

阻塞队列就相当于一个单列集合,可以用迭代器迭代遍历

阻塞队列的实现类

BlockingQueue是一个接口,有两个实现类

ArrayBlockingQueue:底层是数组,有界

LinkedBlockingQueue:底层是链表,无界,但不是真正的无界,最大值为int的最大值

阻塞队列实现等待唤醒机制

阻塞队列不用手动控制线程什么时候该被阻塞,什么时候该被唤醒,简化了操作

防止队列容器溢出,防止数据丢失。

put和take方法的底层是有锁的,在写生产者和消费者的同步代码块时就不用再写锁了

package myThread;

import java.util.concurrent.ArrayBlockingQueue;

public class Cook extends Thread{
   private ArrayBlockingQueue<String> arrayBlockingQueue;

    public Cook(ArrayBlockingQueue<String> arrayBlockingQueue) {
        this.arrayBlockingQueue = arrayBlockingQueue;
    }

    @Override
    public void run() {
        while (true){
            try {
                arrayBlockingQueue.put("面条");
                System.out.println("厨师做好了面条");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}




package myThread;

import java.util.concurrent.ArrayBlockingQueue;

public class Food extends Thread{
  private  ArrayBlockingQueue<String> arrayBlockingQueue;

    public Food(ArrayBlockingQueue<String> arrayBlockingQueue) {
        this.arrayBlockingQueue = arrayBlockingQueue;
    }

    @Override
    public void run() {
        while(true){
          try {
            String take = arrayBlockingQueue.take();
            System.out.println(take);
          } catch (InterruptedException e) {
            throw new RuntimeException(e);
          }
        }
    }
}




package myThread;

import java.util.concurrent.ArrayBlockingQueue;

public class ThreadTest06 {
    public static void main(String[] args) {
        //创建阻塞队列的对象
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);
        Cook cook = new Cook(arrayBlockingQueue);
        Food food = new Food(arrayBlockingQueue);
        cook.start();
        food.start();

    }
}
线程的六种状态

多线程案例
package myThread;

public class ThreadTest07 {
    public static void main(String[] args) {
        Ticket01 ticket01 = new Ticket01();

        Ticket01 ticket011 = new Ticket01();
        ticket01.setName("窗口1");
        ticket011.setName("窗口2");

        ticket01.start();
        ticket011.start();

    }
}



package myThread;

public class Ticket01  extends Thread{
    private static int number = 1000;
    @Override
    public void run() {
        //循环
        while(true){
            //同步代码块
            synchronized (Ticket01.class){
                //判断共享数据是否到了末尾,到了末尾执行什么代码
                if (number==0){
                    break;
                }else {
                    //如果没到末尾
                    number--;
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName()+"正在卖票,还剩"+number+"张票");
                }
            }
        }
    }
}
package myThread;

public class ThreadTest08 {
    public static void main(String[] args) {
        Gift gift = new Gift();
        Thread thread = new Thread(gift);
        Thread thread1 = new Thread(gift);
        thread.setName("线程1");
        thread1.setName("线程2");
        thread.start();
        thread1.start();

    }
}


package myThread;

public class Gift implements Runnable{
    private int giftNumber = 100;
    @Override
    public void run() {
        while(true){
             synchronized (Gift.class){
                 //判断共享数据是否到了末尾
                 if (giftNumber==0){
                     break;
                 }else {
                     //如果共享数据没到末尾
                     giftNumber--;
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         throw new RuntimeException(e);
                     }
                     System.out.println(Thread.currentThread().getName()+"正在送礼物,还剩"+giftNumber+"件礼物");
                 }
             }
        }
    }
}
package myThread;

public class ThreadTest09 {
    public static void main(String[] args) {
        getNumber getNumber = new getNumber();
        Thread thread = new Thread(getNumber);
        Thread thread1 = new Thread(getNumber);
        thread.setName("线程1");
        thread1.setName("线程2");
        thread.start();
        thread1.start();
    }
}



package myThread;

public class getNumber implements Runnable{
    int i=1;

    @Override
    public void run() {
        while (i<=100){
            synchronized (getNumber.class){
                //判断共享数据是否到了末尾
                if (i>100){
                    break;
                }else {
                    //如果共享数据没到末尾
                    if (i%2==1){
                        System.out.println(Thread.currentThread().getName()+"打印数字"+i);
                    }
                    i++;
                }
            }
        }
    }
}
package myThread;

public class ThreadTest10 {
    public static void main(String[] args) {
        redEnvelope redEnvelope = new redEnvelope();
        Thread thread1 = new Thread(redEnvelope);
        Thread thread2 = new Thread(redEnvelope);
        Thread thread3 = new Thread(redEnvelope);
        Thread thread4 = new Thread(redEnvelope);
        Thread thread5 = new Thread(redEnvelope);
        thread1.setName("线程1");
        thread2.setName("线程2");
        thread3.setName("线程3");
        thread4.setName("线程4");
        thread5.setName("线程5");


        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();

    }
}





package myThread;

import java.util.Random;

public class redEnvelope implements Runnable {
    //剩余包的数量
    private int Envelope = 3;
    //包中有多少钱
    private double money = 100;

    private double MIN = 0.01;


    @Override
    public void run() {

        synchronized (redEnvelope.class) {
            //判断共享数据是否到了末尾
            if (Envelope == 0) {
                System.out.println(Thread.currentThread().getName() + "没抢到");
            } else {
                double prize = 0;
                //表示红包最后一个包,剩余钱都是金额
                if (Envelope == 1) {

                    prize = money;
                } else {
                    Random random = new Random();
                    //最大金额为99.98
                    double bounds = money - (Envelope - 1) * 0.01;
                    prize = random.nextDouble(bounds);
                    if (prize < MIN) {
                        prize = MIN;
                    }
                }
                Envelope--;
                money = money - prize;
                System.out.println(Thread.currentThread().getName() + "抢到了红包,抢了" + prize + "块钱");
            }
        }
    }

}
package myThread;
import java.util.ArrayList;
import java.util.Collections;

public class ThreadTest11 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list,10,5,20,50,100,200,500,800,2,80,300,700);

        RafflePool rafflePool = new RafflePool(list);

        Thread thread1 = new Thread(rafflePool);
        Thread thread2 = new Thread(rafflePool);

        thread1.setName("线程1");
        thread2.setName("线程2");

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


    }
}



package myThread;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class RafflePool implements Runnable{
    private ArrayList<Integer> arrayList;



    public RafflePool(ArrayList<Integer> arrayList) {
        this.arrayList = arrayList;
    }

    @Override
    public void run() {
        while(true){
            synchronized (RafflePool.class){
                //判断共享数据是否到了末尾
                if(arrayList.size()==0){
                    System.out.println("奖池中没有奖项,奖池空了");
                    break;
                }else{
                    Collections.shuffle(arrayList);

                    Random random=  new Random();
                    int index = random.nextInt(arrayList.size());
                    int  prize = arrayList.remove(index);
                    System.out.println(Thread.currentThread().getName()+"获得了"+prize+"元大奖");
                }
            }
        }

    }
}
package myThread;

import java.util.ArrayList;
import java.util.Collections;

public class ThreadTest12 {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        Collections.addAll(list, 10, 5, 20, 50, 100, 200, 500, 800, 2, 80, 300, 700);

        RafflePool01 rafflePool01 = new RafflePool01(list);
        RafflePool01 rafflePool02 = new RafflePool01(list);
        RafflePool01 rafflePool03 = new RafflePool01(list);
        RafflePool01 rafflePool04 = new RafflePool01(list);
        RafflePool01 rafflePool05 = new RafflePool01(list);

        rafflePool01.setName("线程1");
        rafflePool02.setName("线程2");
        rafflePool03.setName("线程3");
        rafflePool04.setName("线程4");
        rafflePool05.setName("线程5");

        rafflePool01.start();
        rafflePool02.start();
        rafflePool03.start();
        rafflePool04.start();
        rafflePool05.start();


    }
}





package myThread;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

public class RafflePool01 extends Thread{
    //定义共享数据
    private ArrayList<Integer> arrayList;

    public RafflePool01(ArrayList<Integer> arrayList) {
        this.arrayList = arrayList;
    }

    @Override
    public void run() {
        ArrayList<Integer> boxList = new ArrayList<>();
        while (true){
            synchronized (RafflePool01.class){
                //判断共享数据是否到了末尾
                if (arrayList.size()==0){
                    System.out.println(boxList);
                    break;
                }else {

                    Collections.shuffle(arrayList);
                    Random random = new Random();
                    int index = random.nextInt(arrayList.size());
                    int prize = arrayList.remove(index);
                    boxList.add(prize);
                }
            }
        }
    }
}
线程池

线程池是用来存放线程的。

当我们提交一个任务的时候,线程池会自动创建一个线程来执行任务,执行完之后把线程放回线程池,下次再执行任务的时候,直接拿出来用

如果提交任务的时候,没有空闲线程,也无法创建线程,任务就会排队等待

创建线程池对象

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象

public static ExecutorService newCachedThreadPool( )//创建一个没有上限的线程池

public static ExecutorService newFixedThreadPool(int nThreads)//创建有上限的线程池

线程池多大合适

CPU密集运算:最大并行数+1

I/O密集运算:最大并行数*期望CPU利用率*总时间(CPU计算时间+等待时间)/CPU计算时间

自定义线程对象

自定义线程对象中共有7个参数

  • 核心线程数量——>(不能小于0)
  • 线程池中的最大容量——>(最大数量>=核心线程数量)
  • 空闲时间(值)——>(不能小于0)
  • 空闲时间(单位)——>(用TimeUnit指定)
  • 阻塞队列——>(不能为空)
  • 创建线程的方式——>(不能为null)
  • 要执行的任务过多时的解决方案——>(不能为null)

什么时候会创建临时线程:当核心线程没有空闲,阻塞队列满了的时候才会创建临时线程

先提交的任务不一定先执行

 public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心线程数量
                6,//线程池中的最大容量
                60,//空闲时间(值)
                TimeUnit.SECONDS,//空闲时间(单位)
                new ArrayBlockingQueue<>(3),//阻塞队列的实现类创建
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
        );
任务拒绝策略

任务拒绝策略

说明

ThreadPoolExecutor.AbortPolicy

默认策略:丢弃任务,并抛出RejectExecutionException异常

ThreadPoolExecutor.DiscardPolicy

丢弃任务,但是不抛出异常,不推荐此做法

ThreadPoolExecutor.DiscardOldestPolicy

抛弃队列中等待最久的任务,然后把当前任务加入队列当中

ThreadPoolExecutor.CallerRunsPolicy

调用任务的run()方法绕过线程池直接执行

  • 18
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不是真的冷漠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值