多线程-基础方法-死锁-生产者消费者模式

线程与进程
进程:一个程序运行进行资源分配和独立运行的基本单位,资源包括内存开辟的字节空间和CPU资源。可以理解为一个操作系统正在运行的exe程序。
进程并发是指同时处理多个任务,实现CPU在不同程序之间的切换。
线程:一个任务执行的最小单元,可以理解为独立运行的子单位,在运行一个程序时多个任务同时进行就是多线程发挥了作用,例如边听音乐边下载。
如果用一个工厂来举例说明,一个工厂包含好几个小工厂进行同时工作,这个工厂就是一个进程,其中的工厂占据的地皮就是开辟的内存空间,里面的工人就是CPU资源,每个小工厂就是并发的进程,每个小工厂的流水线就是一个线程。
进程与线程的关系:
1,进程是包含线程的,而且每个进程至少包含一个线程。
2,线程是可以共享资源的,但是进程是不共享的,是相互独立的。
线程的生命周期
生命周期的概念:在程序开发中,将一个对象从被实例化完成到这个对象呗使用、结束、销毁的过程。
线程的生命周期概念:一个线程的实例化、使用、结束、并销毁的过程。
线程的状态:
1,新生态:New,线程被对象实例化完成,但是还没有任何操作。
2,就绪态:Ready,一个线程开始抢夺CPU资源。
3,运行态:Run,CPU抢到了CPU时间片,开始执行一个线程的运行过程。
4,阻塞态:在运行过程中,受到某些操作的影响,放弃已经获取到的时间片,处于挂起状态,并且不再参与时间片的争抢。
5,死亡态:一个线程需要被销毁。
多线程-基础方法-死锁-生产者消费者模式_第1张图片
线程的实例化方法
线程的实例化有三种方法
方法1:继承Thread方法

public class MyThread extends Thread{
    private int i;

    public MyThread(int i) {
        this.i = i;
        System.out.println("开启线程" + i);
    }
    @Override
    public void run() {
        super.run();
        while(true) {
            for(int i = 0 ; i < 10000;i++){
                for(int j = 0; j < 100000; j++){
                }
            }
            System.out.println("线程"+i+"运行");
        }
    }
}

主要是两个方面:一是类要继承Thread;二是重写run方法。
启动方法:start()

 MyThread thread1 = new MyThread(1);
 thread1.start();

方法2:实现Runnable方法

public class MyRunnable implements Runnable{
    private int i;
    public MyRunnable(int i) {
        this.i = i;
        System.out.println("开启线程" + i);
    }
    @Override
    public void run() {
        while(true) {
            for(int i = 0 ; i < 10000;i++){
                for(int j = 0; j < 100000; j++){
                }
            }
            System.out.println("线程"+i+"运行");
        }
    }
}

Runnable接口的类写法跟Thread类似,不同的是Thread是一个实现类,Runnable是一个接口,实际上Thread是一个继承Runable接口的实现类,而且Runnable没有start()方法,因此如果要具体运行需要借助Thread进行启动。

        Runnable runnable1 = new MyRunnable(3);
        new Thread(runnable1).start();

还有一种lamda表达式方法如下所示:

Runnable runnable2 = () -> {
            while(true) {
                for(int i = 0 ; i < 10000;i++){
                    for(int j = 0; j < 100000; j++){
                    }
                }
                System.out.println("lamda线程运行");
            }
        };
        new Thread(runnable2).start();

比较Thread与Runnable方法,Runnable方法毫无疑问是更好的,因为Runnable是接口,由于Java语言只能单继承,如果继承Thread类,那么所写的类就不能作为父类被其余类继承了,另一方面,通过接口实现可以极大的减少程序的耦合性。

方法3:实现Callable方法

Callable也是一种接口实现方法,它与Runable不同的是不再是重写run方法了,二是call()方法,两种方法作用是完全相同的,不同的是call()方法有个返回值,如果这个线程发生异常了可以返回异常值,而run()方法是void类型的。

线程的常用方法
1,线程的命名方法:
①setName(String s)
②直接在构造器中实现
③在实例化的对象中实现

        thread1.setName("线程1");
        Thread thread3 = new Thread("线程3");
        Thread thread4 = new MyThread(4,"线程5");

2,线程的休眠:
Thread.sleep(long)
①让线程处于休眠状态
②传入的参数的单位是毫秒
③它可以让线程从运行状态到阻塞状态
④等休眠时间结束,线程会进入阻塞状态
3,线程的优先级
①优先级的设置是一个整数1-10,默认是5
②设置线程的优先级只是能够让线程抢到CPU时间片的概率更高一些,并不能保证线程一定能抢到时间片。
③设置优先级必须放在start之前
④设置方法
Thread.setPriority(int newPriority);

    public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }

4,线程的礼让
运行中的线程让出当前所占有的CPU资源,进入就绪状态,再去争抢当前的时间片,并不是说当前线程礼让了就一定不再占有时间片了,

 public static native void yield();

临界资源的问题
多个线程共享的资源就是临界资源,在多个线程共享时会发生一些问题,通过例子来演示一下,例如售票员卖票的问题,如有一个景点需要卖门票,总共有1000张票,有四个售票员去卖,编写程序如下所示,

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = () ->{
            while (TicketCenter.ticketNum > 0 ){
                System.out.println(Thread.currentThread().getName()+"卖出了一张票,剩余"+TicketCenter.ticketNum--);
            }
        };
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
    }
}
class TicketCenter{
    public static  int ticketNum = 1000;
}

截取其中一个片段,会发现,在每一个售票员的余票数目是不同步的
多线程-基础方法-死锁-生产者消费者模式_第2张图片
首先分析Thread-0第一次卖出剩余999,第二次就剩余995了,主要原因是在于,Thread-0首先抢到时间片并产生了输出,第二次还没有输出的时候线程就被其他线程抢走了,并且完成了–运算,还没等那些抢走时间片的线程输出,Thread-0又抢回线程,进行计算并且输出了。其中抢走998的线程如下所示
多线程-基础方法-死锁-生产者消费者模式_第3张图片
解决临界资源问题
主要原因是:多个线程同时操作一个资源,于是引入了锁的概念,通过上锁,锁住资源,在线程访问期间不让其他资源访问,在线程操作完这个资源之后再解锁。
解决临界资源的问题
加上锁
在这个程序中加上一个对象锁

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = () ->{
            while (TicketCenter.ticketNum > 0 ){
                synchronized (" "){
                    System.out.println(Thread.currentThread().getName()+"卖出了一张票,剩余"+ --TicketCenter.ticketNum);
                }

            }
        };
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
    }
}
class TicketCenter{
    public static  int ticketNum = 1000;
}

运行结果如下
多线程-基础方法-死锁-生产者消费者模式_第4张图片
这个时候会发现出现了负数的状况,主要原因是在Thread-3占据线程执行输出命令的时候,其余的三个线程在sychronized的外面处于堵塞状态,知道Thread-3线程执行完毕,其余线程载开始抢占资源导致输出为负数,所以需要在锁对象里面加一个判断语句,如下所示

 synchronized (" "){
                    if(TicketCenter.ticketNum == 0 )
                        return;
                    System.out.println(Thread.currentThread().getName()+"卖出了一张票,剩余"+ --TicketCenter.ticketNum);
                }

这样就正常了。
如果逻辑比较复杂,那么可以考虑用同步方法,就是将锁加到方法上,如下所示

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = () ->{
            while (TicketCenter.ticketNum > 0 ){
                soldTickts();
            }
        };
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
    }
    private synchronized static void soldTickts(){
            if(TicketCenter.ticketNum == 0 )
                return;
            System.out.println(Thread.currentThread().getName()+"卖出了一张票,剩余"+ --TicketCenter.ticketNum);
    }
}
class TicketCenter{
    public static  int ticketNum = 1000;
}

可以看到同步方法直接在方法定义上加上一个锁。
显示锁ReenTrantLock

ReentrantLock lock = new ReentrantLock();
        Runnable r = () ->{
            while (TicketCenter.ticketNum > 0 ){
                lock.lock();//上锁
                soldTickts();
                lock.unlock();//解锁
            }
        };

由代码可知,在开始要对代码上锁,使用完之后需要解锁。

死锁
多个线程同时持有对方的锁,而不释放自己的锁,从而导致发生死锁。

public class Main {
    public static  void main(String[] args) {
        Runnable r1 = () ->{
            synchronized ("A"){
                System.out.println(Thread.currentThread().getName()+"线程持有了A锁,等待B锁");
                synchronized ("B"){
                    System.out.println(Thread.currentThread().getName()+"线程同时持有了A和B锁");
                }
            }
        };
        Runnable r2 = () ->{
            synchronized ("B"){
                System.out.println(Thread.currentThread().getName()+"线程持有了B锁,等待A锁");
                synchronized ("A"){
                    System.out.println(Thread.currentThread().getName()+"线程同时持有了A和B锁");
                }
            }
        };
        new Thread(r1).start();
        new Thread(r2).start();
    }
}

多线程-基础方法-死锁-生产者消费者模式_第5张图片
这时会发现两个线程卡住了,无法的到下一个锁,也就是没法持有对方的锁了。
为了解决这个问题我们应该尽量保证在持有一个锁后不要去持有其他的锁了,但是如果确实有这个需求的话需要加多个锁,那就需要wait/notify方法。
有资料整理出死锁产生的四个条件:
1、互斥等待,即必须有锁;
2、hold and wait,即拿着一个锁还在等另一个锁;
3、循环等待,即A对象拿了A锁,等待B锁,B对象拿了B锁等待A锁。

public class Main {
    public static  void main(String[] args) {
        Runnable r1 = () ->{
            synchronized ("A"){
                System.out.println(Thread.currentThread().getName()+"线程持有了A锁,等待B锁");
                try {
                    "A".wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized ("B"){
                    System.out.println(Thread.currentThread().getName()+"线程同时持有了A和B锁");
                }
            }
        };
        Runnable r2 = () ->{
            synchronized ("B"){
                System.out.println(Thread.currentThread().getName()+"线程持有了B锁,等待A锁");
                synchronized ("A"){
                    System.out.println(Thread.currentThread().getName()+"线程同时持有了A和B锁");
                    "A".notify();
                }
            }
        };
        new Thread(r1).start();
        new Thread(r2).start();
    }
}

在这里插入图片描述
代码分析,在Thread-0首先持有了A锁,此时Thread-1持有了B锁,Thread-0暂时释放资源,处在等待状态,等Thread-1释放A锁,Thread-0再持有B锁。
生产者消费者模式
对生产者:在没有生产产品之前要通知消费者等待,在生产了产品之后又要马上通知消费者消费。
对于消费者:在消费之后,要通知生产者消费已经结束,需要继续生产产品。
生产者与消费者共享一个资源,并且相互依赖,互为条件。
多线程-基础方法-死锁-生产者消费者模式_第6张图片
由上图可以看出,在消费者设计者模式中主要有四个角色:
生产者,容器,产品,消费者。
其中生产者向容器中存放产品,而消费者向容器中取出产品。分为四个模块。
产品代码(产品具有某种性质),我们要作用的对象:

public class Productor {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Productor() {
        this.name = (int)(Math.random()*100) + "产品";
    }
}

容器代码(存放的空间)具有存取功能:

import java.util.ArrayList;
import java.util.List;

public class ProductPool {
    List<Productor> productors = new ArrayList<>();
    int max_size = 0;
    public ProductPool(int max_size) {
        this.max_size = max_size;
    }
    public synchronized void push(Productor productor) throws InterruptedException {
        if(productors.size() == max_size){
            this.wait();
        }else{
            productors.add(productor);
            System.out.println("生产"+productor.getName());
            notifyAll();
        }
    }
    public synchronized Productor pop() throws InterruptedException {
        Productor productor1 = null;
        if(productors.size() == 0){
            this.wait();
        }else{
            productor1 = productors.remove(0);
            System.out.println("消费"+productor1.getName());
        }
        return productor1;
    }
}

生产者角色:主要向容器中存放产品,同时告诉消费者可以消费了。

public class Product implements Runnable{
    private ProductPool productPool = null;
    public Product(ProductPool productPool) {
        this.productPool = productPool;
    }
    @Override
    public void run() {
        while(true){
            try {
                productPool.push(new Productor());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者角色(从容器中消费产品):

public class Consumer implements Runnable {
    private  ProductPool productPool;

    public Consumer(ProductPool productPool) {
        this.productPool = productPool;
    }

    @Override
    public void run() {
        while(true){
            try {
                Productor productor = productPool.pop();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果
多线程-基础方法-死锁-生产者消费者模式_第7张图片
在这里插入图片描述
这样实现了边存边取的作用。

你可能感兴趣的:(Java基础与八股,多线程,java)