二刷能变强系列-多线程

一.概念

程序
在idea躺着代码就是程序就那么简单

进程
当躺着的代码被加载到虚拟机中跑起来了,就变成了进程。官方话还是要说说,指在系统中能独立运行并作为资源分配的基本单位

线程
线程是进程中的一个实体,作为cpu调度和分派的基本单位

二.线程的创建

1.继承Thread方法

直接创建一个类继承Thread,重写run方法,主线程中用start(),启动线程

package com.wq.thread0;

public class TestThread {
    public static void main(String[] args) {
        Thread myThread = new MyThread();
        myThread.setName("孤寡线程");
        myThread.setPriority(5);
        myThread.start();
    }
}


package com.wq.thread0;

public class MyThread extends Thread {
    @Override
    public void run() {
        int i = 0 ;
        while (i<100){
            System.out.println(Thread.currentThread().getName() + "线程  孤寡 孤寡 " );
            i++;
        }
    }
}

2.实现Runnable接口

创建一个类实现Runnable接口的重写run方法,主线程中使用Thread(Runnable)的构造方法创建多线程,用start(),启动线程

package com.wq.thread1;

public class TestThread {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread myThread = new Thread(myRunnable);
        myThread.setName("孤寡线程");
        myThread.setPriority(5);
        myThread.start();
    }
}

package com.wq.thread1;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        int i = 0 ;
        while (i<100){
            System.out.println(Thread.currentThread().getName() + "线程  孤寡 孤寡 " );
            i++;
        }
    }
}

3.实现Callable接口

创建一个类实现Callable接口,重写call()方法,然后在主线程new Thread(),发现没有Callable构造方法的创建,我们使用runnable的构造方法,用Runnable的实现类FutureTask来创建与Callable关联的对象。然后我们使用runnableTask .get()来返回值

package com.wq.thread2;

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TestCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> myCallable = new MyCallable();
        FutureTask<String> runnableTask = new FutureTask<String>(myCallable);
        Thread myThread = new Thread(runnableTask );
        myThread.start();
        System.out.println(runnableTask .get());
    }


}

package com.wq.thread2;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName() + "线程  孤寡 孤寡 " ;
    }
}

三种线程的比较
在这里插入图片描述

三.同步线程

先说说为什么会有同步线程的概念

假设王二和赵四同时对一个账户进行取钱,账户有600元,因为我们的线程是由cpu调度的,当线程的时间片使用完了,cpu去就绪队列里运行其中的一个线程,在不能保证调度的是之前的线程的情况,就可以出现下面的情况
在这里插入图片描述
解决方法:同步线程

同步关键字:synchronized

同步监视器(锁)

  • 只能是引用数据类型
  • 只能改变值,不能改变其引用地址
  • 一般使用同步资源作为同步监视器
  • 建议使用final保证安全性

同步执行过程

王二在查余额前会对查询余额,取钱操作做加锁操作,如果王二在查完余额后cpu切换,此时我们的查询和取钱操作是上锁了的状态,所以即使赵四得到了cpu的调度,但是也无法进入被锁住的操作,所以又会等时间消耗完返回,此时王二再次得到cpu就会继续完成取钱操作,然后释放锁。待锁释放后赵四再次得到cpu,此时查询和取钱操作没被加锁,所以操作再次被加锁并进入执行。

在这里插入图片描述
同步线程:同步代码块,同步方法
同步代码锁:

  • 同步监视器

同步方法锁:

  • 普通方法 ->同步监视器是this
  • 静态方法 ->同步监视器是 类.class
Lock

对象Lock也可以实现同步线程。因为它是jdk提供的对象,所以对于关键字synchronized来说可以实现的功能更多。

lock特点

  • 只能锁代码块
  • 需手动加锁手动解锁
  • 可重入锁
  • 性能 lock > synchronized代码块 > synchronized方法

四.线程通信

先说说什么是线程通信吧。线程通信就是我们最经常说到的生产者消费者模型,就是当有生产者生产了商品,消费者就会来消费商品。在生产和消费的同时会进行通信来保证供需的平衡

代码的几个问题

现在我们有一只会叫孤寡的青蛙和一只会叫布谷的布谷鸟

问题:生产了一只会叫布谷的青蛙
解决:线程同步,注意生产者和消费者都需要加锁,并且加的是同一把锁。

问题:消费者去得的商品属性为空
解决:要对同一资源进行生产消费

问题:生产和消费如何交互
解决:使用线程通信,object的wait(),notify(),notifyAll()

package com.wq.xctx;

public class TestXctx {
    public static void main(String[] args) {
        //创建商品类,保证生产和消费的是同一个商品
        Product product = new Product();
        //创建生产线程
        Produce produce = new Produce(product);
        Thread produceThread = new Thread(produce);
        produceThread.start();
        //创建消费线程
        Consume consume = new Consume(product);
        Thread consumeThread = new Thread(consume);
        consumeThread.start();

    }
}

package com.wq.xctx;

public class Product {
    public String animal;
    public String sound;
    public boolean flag = false; //是否有动物在叫

    public String getAnimal() {
        return animal;
    }

    public void setAnimal(String animal) {
        this.animal = animal;
    }

    public String getSound() {
        return sound;
    }

    public void setSound(String sound) {
        this.sound = sound;
    }
}

package com.wq.xctx;

public class Produce implements Runnable{
    private Product product ;

    public Produce(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        //生产商品
        int i = 0 ;
        while (true){
            synchronized (product){
                if (product.flag){
                    try {
                        product.wait(); //释放cpu 释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (i%2==0){
                    product.setAnimal("青蛙");
                    try {
                        Thread.sleep(10); //1.暴露问题,可能产生线程同步问题 解决 上锁  2.释放CPU 释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setSound("孤寡");
                }else {
                    product.setAnimal("布谷鸟");
                    try {
                        Thread.sleep(10); //1.暴露问题,可能产生线程同步问题 解决 上锁  2.释放CPU 释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setSound("布谷");
                }
                System.out.println("发现了一个"+product.getAnimal()+", 发出了"+product.getSound()+"的叫声");
                product.flag=true;
                product.notify();//唤醒等待队列的线程
                i++;
            }
        }

    }
}

package com.wq.xctx;

public class Consume implements Runnable{
    private Product product ;

    public Product getProduct() {
        return product;
    }

    public Consume(Product product) {
        this.product = product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        while (true){
            synchronized (product){
                if (!product.flag){
                    try {
                        product.wait();//释放cpu 释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("收到了一个"+product.getAnimal()+", 发出了"+product.getSound()+"的叫声");
                product.flag=false;
                product.notify();//唤醒等待队列的线程
            }

        }
    }
}

线程通信的几个细节
1.进行线程通信的多个线程要使用同一个同步监视器

我们点开Object类可以看到对应的wait(),notify(),notifyAll()都是对应的类对象的方法,在下面的生命周期也会提到,同一个类的wait()会进入同一个等待队列,notify()也是唤醒对应的线程(这就是之前为什么建议使用同步资源作为同步监视器)

2.通信方法

wait()
线程等待,直到同步监视器使用notify()或者notifyAll()唤醒才结束等待。也可以使用wait(time)来对线程在超过指定时间进行唤醒

notify()
随机唤醒一个指定同步监视器在wait()等待队列的的线程

notifyAll()
唤醒同步监视器里的全部的wait()等待队列里的线程

3.线程的生命周期

在这里插入图片描述

4.sleep和wait的区别
  • sleep调用后线程进入的是阻塞状态,wait进入的是等待队列
  • sleep和wait都会让出CPU,但是sleep不释放锁(同步线程),wait会释放锁(可做线程通信)
  • sleep可以在任何地方使用,wait只能在同步代码块或者同步方法中使用

五.结束

第一次了解线程,以为他是一种很牛批牛批的东西,因为涉及到线程就难不了扯到并发,宕机,祭天这些词,但现在在学一次还是认为他是很牛批的东西,这一节只是讲到线程的基本的东西,太多了,线程池就放在下章了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值