快速理解多线程(一)

多线程

讲多线程前先来了解下面几个概念;

进程、线程、多任务、程序
进程:其实说白了就是程序,只不过进程是动态的一个概念,而一个进程里往往有着>=1个的线程,比如,你在看直播的时候,技能看弹幕,又能听声音,这种肯定不是一个线程能处理的,不过一般多线程,是对应多个CPU的。
程序:是指令和数据的有序集合是静态的一个概念。
多任务:就是说一个人要执行很多的任务,比如你在吃饭的时候玩手机。
线程:就是独立执行的路径

1.核心概念

  • ◆线程就是独立的执行路径;
  • ◆在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
  • ◆main() 称之为主线程,为系统的入口,用于执行整个程序;
  • ◆在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为的干预的。
  • ◆对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
  • ◆线程会带来额外的开销,如cpu调度时间,并发控制开销。
  • ◆每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

2.线程的状态图:

线程有以下五种状态,具体如图所示,图里介绍的很明白。

在这里插入图片描述
在这里插入图片描述

具体想看的话可以通过代码输出线程的状态查看,如下所示:

package 线程的三种创建方式;

public class ThreadState  {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            for (int i=0;i<5;i++){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("------------");
        });

        //创建的时候的状态
        Thread.State state=t.getState();
        System.out.println(state);

        //启动后的状态
        t.start();
        state=t.getState();
        System.out.println(state);

        while(state !=Thread.State.TERMINATED){
            //只要线程不终止,就一直输出他运行的状态
            try {
                Thread.sleep(100);
                state=t.getState();
                System.out.println(state);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

在这里插入图片描述

3. 线程的一些方法

方法说明
join()就是插队执行,等加入的线程执行完了,再去执行其他的
yield()通俗说就是让出cpu和其他线程一起再次竞争资源
getPriority()获取线程优先级 1~10 默认值为5 优先级越高被优先调用的频率越高
setDaemon()setDaemon(true) 设置当前线程为守护线程 默认false就是用户线程,当用户线程结束了守护线程也就结束了一般
interrupt()中断线程 由运行状态到死亡状态
getName()和setName()getName() 获取此线程的名字 setName() 设置此线程的名字

补充:一般停止线程不用stop,通过外部标志位来控制结束。

4. 线程的3中创建方式Thread、Runnable、Callable

补充下:其实Callable底层也是通过前面两种的实现的

4.1 继承Thread类

示例代码:

package 线程的三种创建方式;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

public class Test01 extends Thread{
    private String url;
    private String name;

    public Test01(String url,String name){
        this.url=url;
        this.name=name;
    }


    public static void main(String[] args) {
        Test01 test01=new Test01("https://image.baidu.com/search/detail","mao.jpg");
        test01.start();
    }

    @Override
    public void run() {
        DownLoader downLoader=new DownLoader();
        downLoader.down(url,name);
        System.out.println("下载了");
    }
}

class DownLoader{

    public void down(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("下载异常出现问题");
        }
    }

}

4.2 实现Runnable接口

示例代码如下:

package 线程的三种创建方式;


//
public class Test02 implements Runnable {

    private int tickenum=10;

    public void run() {
        while (true){
            if (tickenum<0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"抢到了第"+tickenum--+"票");
        }
    }
    public static void main(String[] args) {
        Test02 test02 = new Test02();
        new Thread(test02,"小明").start();
        new Thread(test02,"黄牛").start();
    }
}

4.3实现Callable接口

示例代码如下:

package 线程的三种创建方式;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

public class Test03 implements Callable<Boolean>{
    private String url;
    private String name;

    public Test03(String url,String name){
        this.url=url;
        this.name=name;
    }
    public static void main(String[] args) throws ExecutionException,InterruptedException {
        Test03 t=new Test03("https://image.baidu.com/search/detail","mao.jpg");
        //创建执行服务
        ExecutorService service= Executors.newFixedThreadPool(1);
        //提交执行
        Future<Boolean> result1=service.submit(t);
        //获取结果
        boolean falg1=result1.get();
        //关闭服务
        service.shutdown();
    }

    public Boolean call() {
        WebDownLoader downLoader=new WebDownLoader();
        downLoader.down(url,name);
        System.out.println("下载了");
        return true;
    }
}

class WebDownLoader{
    public void down(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("下载异常出现问题");
        }
    }

}


5. 线程同步问题

说明一下什么叫线程同步?
简单的说就是一种等待机制,就是说要用当前对象的线程需要进入排好队一个个个使用。但是光排好队是不够的,打个比方就像上厕所一样,人都排好队进厕所但是第一个进去之后没关门然后第二个人又来了,那这就不行了。所以线程同步不仅需要排好队还需要一把锁。所以为了保证安全性,必须要加入锁机制,也就是synchronized。

但是加锁了也会存在一些问题?

  1. 在多线程的情况下,加锁,放锁都会引起较多的上下文切换,消耗一定的性能。

  2. 优先级高的等待优先级低的释放锁,会导致优先级倒置,引起性能问题。

5.1 synchronized的两种用法

5.1.1 synchronized 方法 也叫同步方法

例如:public synchronized void method(int a){}
这个是控制对“对象”的访问,每个对象对应一把锁,也就是说,当你需要执行method的这个方法时,你需要拿到调用该方法的对象的锁,才行不然是不能执行的。
这个有一个很大的缺点:当你将一个大的方法加了这个synchronized之后,会大大的影响效率?有人可能会问为什么,因为当你的方法里面有一些只需要读的数据,那你还把它锁,其他线程也拿不到,只能等当前正在访问的线程结束后才能拿到岂不是太影响效率了。
补充:synchronized默认的是this这个对象。

5.1.2 synchronized 块 也叫同步块

例如: synchronized (Object){ } ---->Object 称之为同步监视器

  • Object 可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是这个对象本身,或者是class
同步监视器的执行过程
  1. 第一个线程访问,锁定同步监视器,执行其中代码.
  2. 第二个线程访问,发现同步监视器被锁定,无法访问.
  3. 第一个线程访问完毕,解锁同步监视器.
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问.

补充一下:打个比方就是说,银行取钱这个案例,有两个类,Bank类、Account类。你在run()方法里进行扣钱的这个操作,如果说你在run()方法前加了一个synchronized那这个就是锁的Bank这个对象,那是没有用的数据的操作是你的的Account类所以要锁这个对象,就要用同步块来锁。

class Bank implements Runnable{
	@override
	public void run(){
		//扣钱操作
	}
}

class Account{
}

5.死锁

定义:多个线程互相拿着对方需要的资源不放,僵持在那里。
例如下示例,就是死锁。
在这里插入图片描述

死锁产生的4个必要条件:

  1. 互斥条件:一个资源只能被一个进程占用
  2. 不可剥夺条件:某个进程占用了资源,不能强行剥离。
  3. 请求和保持条件:一个进程因请求资源而阻塞,对已经获得资源不释放。
  4. 循环等待条件:一定会有一个环互相等待。

6.Lock锁

这里补充一个这个Lock锁,先看下这个和synchronized的一个对比

  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁, 出了作用域自动释放
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用L ock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  4. 优先使用顺序: Lock >同步代码块(已经进入了方法体,分配了相应资源) >同步方法(在方法体之外)

具体来看下简单的案例怎么用的:

package 线程的三种创建方式.juc;

import java.util.concurrent.locks.ReentrantLock;

public class TestLock implements Runnable{
    private int tickenum=10;

    private final ReentrantLock reentrantLock=new ReentrantLock();

    public void run() {
        while (true) {
            //上锁
            reentrantLock.lock();
            if (tickenum > 0) {
                try {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName() + "抢到了第" + tickenum-- + "票");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //放锁
                    reentrantLock.unlock();
                }
            } else {
                break;
            }
        }

    }
    public static void main(String[] args) {
        TestLock test02 = new TestLock();
        new Thread(test02,"小明").start();
        new Thread(test02,"黄牛").start();
    }
}


7. 多线程写协作

重点来了这个很重要的,线程之间怎么协作呢?首先要搞清楚消费者跟生产者的问题。

7.1 消费者和生产者问题

通俗点说实际上主要是包含了两类线程,一种是生产者线程用于生产数据,另一种是消费者线程用于消费数据,为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库,生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为;而消费者只需要从共享数据区中去获取数据,就不再需要关心生产者的行为。

但是,这个共享数据区域中应该具备这样的线程间并发协作的功能:
  1. 如果共享数据区已满的话,阻塞生产者继续生产数据放置入内;
  2. 如果共享数据区为空的话,阻塞消费者继续消费数据;

7.2 实现生产者消费者问题的方法

这里例举3种,但是只简单介绍一下第一种了,后面2种先不介绍了,哎 ,码字太累了下次写。
1.使用Object的wait/notify的消息通知机制;

2.使用Lock的Condition的await/signal的消息通知机制;

3.使用BlockingQueue实现。

说第一种之前先补个东西

方法说明
sleep()这个不会释放会抱着锁睡觉
wait()这个会释放锁

7.2.1 使用Object的wait/notify的消息通知机制

并发协作模型“生产者 / 消费者模式 ” —>管程法
  • 生产者 : 负责生产数据的模块(可能是方法、对象、线程、进程)
  • 消费者 : 负责处理数据的模块(可能是方法、对象、线程、进程)
  • 缓冲区 : 消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
  • 生产者将生产好的数据放入缓冲区,消费者从里面拿。
并发协作模型“生产者 / 消费者模式 ” —>信号灯法

管程法的案例展示:

package 管程法;


//管程法--》利用缓冲区解决
public class Test {
    public static void main(String[] args) {
        Box box=new Box();
        new Productor(box).start();
        new Consumer(box).start();
    }
}

class Productor extends Thread{
    Box box;
    public Productor(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            box.put(new Duck(i));
            System.out.println("生产了"+i+"只鸭子");
        }
    }
}

class Consumer extends Thread{
    Box box;

    public Consumer(Box box) {
        this.box = box;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了"+box.get().id+"只鸭子");
        }
    }
}

class Duck{
    int id;//编号

    public Duck(int id) {
        this.id = id;
    }
}


//容器
class Box{
    //容器大小
    Duck[] ducks=new Duck[10];
    //容器计数器
    int count=0;
    //生产者放入产品
    public synchronized void put(Duck duck){
        //如果满了,就需要等待消费者消费
        if(count==ducks.length){
            //通知消费者消费,让生产者等待
            try{
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }

        }
        //如果没有满,就放入产品
        ducks[count]=duck;
        count++;

        //可以通知消费者消费了
        this.notifyAll();
    }

    //消费者消费产品
    public synchronized Duck get(){
        //判断能否消费
        if(count==0){
            //等待生产者生产,消费者等待
            try{
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }

        //如果可以消费
        count--;
        Duck duck=ducks[count];
        //消费完了,告诉生产者生产
        this.notifyAll();
        return duck;
    }

}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值