Java多线程与并发框(完结篇)——再看不懂我找不到女朋友

33 篇文章 3 订阅

不要自卑,去提升实力
互联网行业谁技术牛谁是爹
如果文章可以带给你能量,那是最好的事!请相信自己
加油o~

在这里插入图片描述

多线程

关于多线程有关的概念:

  • 进程:进程指正在运行的程序,并且具有一定独立功能
  • 线程:线程是进程中的一个执行单位,负责当前进程程序的执行,一个进程中至少会有一个线程,如果一个进程中包含多个线程,那么可称为多线程程序
  • 单线程:当要执行多个任务时,cpu只会依次执行,当一个任务执行完后,再去执行另外一个任务
  • 多线程:多个任务可以同时进行

在Java中,不同线程会有不同的优先级抢占cpu,如果线程优先级相同,就会随机先去一个线程去执行

Java程序运行时会默认执行3个进程:

  • main主线程
  • gc垃圾回收机制
  • 异常处理机制

我们如何能够判断程序是否是多线程?

如果我们能够将程序的执行用一条直线画出来,就说明是单线程

关于线程的常用API方法

在这里插入图片描述

  1. run():该方法需要被重写,重写的内容就是需要执行的操作

  2. start():调用该方法就会启动相应的线程,并调用当前线程的run方法

  3. sleep(long millitime):将当前线程进入阻塞状态(不会释放锁,即同步监视器)

  4. join():当a线程调用b线程的join方法时,a线程会进入阻塞状态,直到b线程的任务执行完毕

  5. isAlive():判断当前线程是否存活

  6. yield():调用该方法后回释放当前线程的cpu执行权,当时并不代表不会再次执行,有可能释放后,又是该线程抢占到了cpu的执行权

  7. currentThread():Thread类中的静态方法,会返回执行当前程序的线程

  8. getName():返回当前线程的名字

  9. setName():设置线程的名字

  10. getPriority():设置线程的优先级(

MAX_PRIORITY=10
MIN_PRIORITY=1
NORM_PRIORITY=5 默认优先级

  1. wait():将线程进入阻塞状态(会释放掉锁),只能在同步代码块或同步方法中使用

  2. notify():将另外一个线程唤醒

  3. notifyAll():唤醒所有被阻塞的线程

线程创建的4种方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-feelaAyr-1607955739349)(C:\Users\华为\AppData\Roaming\Typora\typora-user-images\image-20201214222120401.png)]

一:继承Thread类

  1. 首先创建一个类去继承Thread
  2. 重写Thread中的run方法
  3. main()中创建该对象
  4. 调用该对象的start方法,启动线程
public class test {
    public static void main(String[] args) {
        MyThread1 myThread1=new MyThread1();
        myThread1.start();
    }
}
class MyThread1 extends Thread{
    @Override
    public void run() {
        System.out.println("继承了Thread");
    }
}

二:实现Runnable接口

  1. 创建一个类实现Runnable接口
  2. 实现接口的run方法
  3. 在main()中创建实现Runnable的对象
  4. 创建Thread对象,并把刚创建好的类传参
  5. 调用Thread对象的start方法,启动线程
public class test {
    public static void main(String[] args) {
        MyThread1 myThread1=new MyThread1();
        Thread t=new Thread(myThread1);
        t.start();
    }
}
class MyThread1 implements Runnable{
    @Override
    public void run() {
        System.out.println("实现了Runnable");
    }
}

三:实现Callable接口

  1. 创建一个类实现Callable接口
  2. 实现接口的call()方法
  3. 在main中创建实现Callable的对象
  4. 创建FutureTask对象并把上面创建的对象传参
  5. 创建Thread对象,传入FutureTask对象
  6. 调用Thread的start方法,启动线程
public class test {
    public static void main(String[] args) {
        MyThread2 myThread2=new MyThread2();
        FutureTask futureTask=new FutureTask(myThread2);
        Thread t=new Thread(futureTask);
        t.start();
    }
}
class MyThread2 implements Callable{
    @Override
    public Object call() throws Exception {
        System.out.println("实现了Callable");
        return null;
    }
}

此方式如果需要得到返回值需要调用futureTask.get();

但是会抛异常,用try,catch方法捕捉一下就好了

四:线程池

  1. 创建ExecutorService对象
  2. 传入相应的线程对象
  3. 结束线程池
public class test {
    public static void main(String[] args) {
        //创建线程池,设置线程池线程的数量为10
        ExecutorService service = Executors.newFixedThreadPool(10);
        //execute适用于实现了Runnable的对象
        service.execute(new MyThread1());
        //submit适用于实现了Callable的对象
        service.submit(new MyThread2());
        //结束线程池
        service.shutdown();
    }
}
class MyThread1 implements Runnable{
    @Override
    public void run() {
        System.out.println("实现了Runnable");
    }
}
class MyThread2 implements Callable{
    @Override
    public Object call() throws Exception {
        System.out.println("实现了Callable");
        return null;
    }
}
Runnable和Callable的对比:
  • Callable可以有返回值,执行完相应操作后可以返回需要的结果
  • 可以抛异常,可以将call方法中的异常抛出
  • 支持泛型,可以指定返回值类型

线程安全

什么是线程安全问题呢?

当多个线程对同一个共享数据操作时,线程执行还没来得及更新处理共享的数据,从而使得其他操作的线程并未得到最新的数据,从而产生问题

举个例子:

  1. 当甲乙两人向同一账户存钱,让甲乙两个线程同时存钱,如果甲向账户存了1000元,并打印此时余额,应为1000元,但是如果此时乙也存了1000元,就会导致,显示余额为2000元,并不是甲当时的余额
  2. 还有就是火车售票问题,如果多个窗口同时售票,如果1号窗口正在卖001号票时,此时还未处理完成,这是2号窗口也卖了001号票,这就导致产生了两个001号票

那么如何解决呢?

有三种方式:

方法一:同步代码块
synchronized(Object obj)
{
	//操作内容                
}
  • synchronized():传入的可以是任意类的对象,但必须是多个线程共用的,一般可以利用this,即当前对象(Runnable),Thread不太行,因为继承多个Thread类会导致this对象不一致
  • 被包住的代码执行为单线程,当一个线程执行完后,另外一个线程才有可能会分配到执行权去执行
  • 多个线程必须共用同一把锁,这样才能够判断一个线程是够执行
  • Runnable一般很实用,因为多个线程都调用同一个类的方法,但是Thread就需要自己定义静态变量或者当前的唯一类即(windows.class)
public class test {
    public static void main(String[] args) {
        Window t1 = new Window("窗口1");
        Window t2 = new Window("窗口2");
        Window t3 = new Window("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Window extends Thread{
    private static int ticket=100;
    //继承方式,要用静态对象,因为继承实现多线程有多个对象,不是共用一个对象
    private static Object obj=new Object();
    public Window(String name){
        super(name);
    }
    @Override
    public void run() {
        while(true){
            //不可以用this,同理,因为有很多对象,不唯一
            synchronized(obj){
                if(ticket>0){
                    System.out.println(getName()+":卖票,票号为:"+ticket);
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }
}
方法二:同步方法

和同步代码块类似

就是将共享数据的操作封装成方法

将这个方法用锁锁住

public class test {
    public static void main(String[] args) {
        Window t1 = new Window("窗口1");
        Window t2 = new Window("窗口2");
        Window t3 = new Window("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Window extends Thread{
    private static int ticket=100;
    public Window(String name){
        super(name);
    }
    @Override
    public void run() {
        while(true){
            show();
        }
    }
    public static synchronized void show(){
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
            ticket--;
        }
    }
}

注意:

  • show方法要定义成静态,因为如果是非静态,它的锁默认是当前对象,继承方式就会有多个锁,如果是Runnable就可以,所以需要编程静态,这样默认锁就是当前类对象
方法三:lock锁
  • 首先创建一个ReentrantLock对象
  • 在执行共享数据之前将锁打开,调用lock方法
  • 在结束时将锁解开,调用unlock方法
public class test {
    public static void main(String[] args) {
        Windows w = new Windows();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Windows implements Runnable{
    private int ticket=100;
    private ReentrantLock lock=new ReentrantLock(true);
    @Override
    public void run() {
        while(true){
            try {
                lock.lock();
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
                    ticket--;
                }
                else{
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

问题:该方式和synchrnized有什么不同呢?

synchronized在执行完相应代码后会自动上锁解锁,而lock需要手动上锁和解锁,较为灵活

线程的死锁

描述:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

举个例子:两个人迎面相遇,甲希望乙会给他让路,而乙希望甲给他让他让路,就这样两个人僵持在这里,最终谁也不给谁让路,导致死锁问题

public class test {
    public static void main(String[] args) {
        StringBuffer s1=new StringBuffer();
        StringBuffer s2=new StringBuffer();
        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }

            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){
                    s1.append("c");
                    s2.append("3");
                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

本例中,线程1首先拿到了s1锁,然后阻塞了一段时间,这段时间线程2拿到了s2锁,这是线程1就绪需要s2锁,而线程2需要s1锁,两者谁都拿不到,就会僵持住

解决死锁的相应办法:

  • 减少共享变量的使用
  • 设计相应的算法去规避死锁问题
  • 尽量减少锁的嵌套使用

线程的通信

  • wait():将线程进入阻塞状态(会释放掉锁),只能在同步代码块或同步方法中使用

  • notify():将另外一个优先级高的线程唤醒

  • notifyAll():唤醒所有被阻塞的线程

注意:这三个方法只能够在同步代码块或者同步方法使用,都定义在了Object类中

例题要求:

让两个线程交替打印1-100之间的数字

public class 线程通信 {
    public static void main(String[] args) {
        Number number=new Number();
        Thread t1=new Thread(number);
        Thread t2=new Thread(number);
        t1.setName("线程一");
        t2.setName("线程二");
        t1.start();
        t2.start();
    }
}
class Number implements Runnable{
    private int number=1;

    @Override
    public void run() {
        while(true){
            synchronized (this) {

                notify();
                //唤醒全部
                // notifyAll();
                if(number<=100){
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;

                    try {
                        //使得调用如下方法进程阻塞,执行wait后,锁就被释放
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }
    }
}

  • 当线程1首次进入,会打印出1,然后调用了wait方法 ,进入阻塞状态
  • 此时线程2进入,首先会唤醒线程1,然后打印2,然后自己进入阻塞状态
  • 两者交替阻塞唤醒,直到打印完为止

那么问题是sleep和wait方法有什么异同?

两个方法声明的位置不同,sleep是Thread中声明的,而wait是Object中声明的

sleep可以在任何情景调用,而wait只能够在同步代码块或同步方法中使用

sleep执行后不会释放当前的锁,而wait会释放掉当前的锁

生产者和消费者问题:

生产者(Priductor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如20个),如果生产者视图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产:如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

package 生产者与消费者问题;

public class 生产者 {
    public static void main(String[] args) {
        Clerk clerk=new Clerk();
        Producer p1 = new Producer(clerk);
        p1.setName("生产者");
        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者");
        p1.start();
        c1.start();
    }
}
class Clerk{

    private int productCount=0;

    public synchronized void consumeProduct() {
        if(productCount>0){
            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
            productCount--;
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void produceProduct() {
        if(productCount<20){
            productCount++;
            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Producer extends Thread{
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName()+":开始生产产品......");
        while(true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            clerk.produceProduct();
        }
    }
}
class Consumer extends Thread{
    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk=clerk;
    }

    @Override
    public void run() {
        System.out.println(getName()+":开始消费产品......");
        while(true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.consumeProduct();
        }
    }
}

  • 生产者和消费者会共享产品数量
  • 我们可以在售货员类中定义方法,当此时的生产数量未达到标准时,就会进行生产,然后会唤醒消费的进程,否则就会进入阻塞状态
  • 而当产品数量不足时,消费者就会唤醒生产进程,此时,自己进入阻塞状态
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

海洋 之心

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

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

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

打赏作者

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

抵扣说明:

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

余额充值