Java线程基础(进程线程、并发并行概念区别、线程生命周期、线程同步、synchronized、Lock锁、线程通讯、生产者消费者、线程池的基本使用)

目录

线程

线程的介绍

线程终止

线程中断(interrupt)

线程礼让(yield)

线程插队(join)

线程的生命周期,6种状态

线程同步机制

互斥锁(对上面的解释)

释放锁

Lock锁

​编辑

synchronized 与 Lock 的对比

线程通讯

线程池简介


线程

线程的介绍

进程:进程是程序的执行,比如QQ打开了,就是一个进程;微信打开了,也是一个进程

线程:线程是依附于进程的,进程可以启动线程,一个进程可以有多个线程;比如QQ同时跟多人聊天;迅雷同时下载多个不同的文件,每个下载都是一个线程;如果主线程结束了,但是还有其他线程,那么进程也不会结束;

并发:并发是指一个cpu在一个时间片里面轮换的执行进程,假的同时,实际上是一个执行一会儿。

并行:并行是多核cpu在同时间执行不同的任务,真正的同时。

问题1:为什么不直接调用Run方法?

因为run方法实际上就是一个普通方法,并没有真正的启动线程,就会把这个run方法执行完毕才会执行后面的代码。只有调用start方法才会真正的启动一个线程,实现多线程。

问题2:为什么调用start()方法,不调用run()方法

start()方法的源码,实际上是调用start0()方法,而start0方法是一个native方法,实际上是JVM来调用的;调用该方法后,该线程不一定会马上执行,只是将线程变成了可运行状态,具体什么时候执行,取决于CPU,由CPU统一执行。

线程终止

基本说明:

  • 当线程完成任务后,会自动退出。

  • 还可以通过使用变量来控制run方法退出的方式停止线程,即通知的方式

    • 可以给线程设置一个变量,然后主线程改变这个变量,就能达到终止线程的效果

线程中断(interrupt)

Interrupt方法:

如果线程在休眠的时候,被别人调用了他的interrupt方法,则会抛出一个异常:InerruptedException中断方法,可以通过catch来写业务逻辑。

线程礼让(yield)

yield方法:

让出CPU,让其他线程先执行,但是礼让的时间不确定,所以也不一定礼让成功

线程插队(join)

join方法:

调用其他线程的join方法,自己主动放弃CUP,等待其他线程执行结束之后,再执行自己。t1线程调用t2.join,让t2执行完毕,再执行t1

线程的生命周期,6种状态

 

线程同步机制

同步的概念:

  • 在多线程编程里面,一些敏感的数据不允许被多个线程同时访问,此时使用同步访问技术,保证数据在任何时刻最多有一个线程访问,以保证数据的完整性!!

  • 也可以这样理解,例如,有一个线程在内存进行操作的时候,其他线程都不能对这个内存地址进行操作,直到该线程完成操作。就像上厕所,有一个人在上的时候,别人是不能进去上厕所的,只能等到那个人上完厕所。

同步具体的方法:Synchronized

  • 同步代码块

    synchronized(对象){
        
    }
  • synchronized放在方法的声明中,表示整个方法为同步方法

    public synchronized void method(){
        //需要被同步的代码
    }

互斥锁(对上面的解释)

基本介绍

  1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性.

  2. 每个对象都应对应一个可以称为"互斥锁"的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象

  3. synchronized来与对象进行互斥锁的联系,当某个对象使用synchronized修饰的时候,就表名该对象在任一时刻只能由一个线程访问。

  4. 同步的局限性:导致程序的执行效率降低(例如高速公路上,收费站只有一个,能同时通过的很少,所以一般堵车的地方就是收费站附近)

  5. 同步方法如果是非静态的,这个锁可以是this,也可以是其他对象(要求是用一个对象)

  6. 如果是静态的,这个锁实际上是加在当前类的

释放锁

以下操作会释放锁:

  1. 当线程的同步方法、同步代码块执行结束。

    例如:上完厕所出来

  2. 当前线程在同步代码块、同步方法中遇到break、return。

    例如:没有正常的完事,但是发生火灾了,不得从厕所出来

  3. 当前线程在同步代码块、同步方法中出现了未处理的Error或者Exception,导致异常结束。

    例如:没有正常完事儿,发现忘记带纸了,不得已出来

  4. 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

    例如:拉不出来,先去休息一下,酝酿一下,所以出来

一下操作不会释放锁:

  • 当前线程在同步代码块、同步方法时,程序调用了Thread.slepp()、Thread.yield()方法,暂停当前线程的执行,不会释放锁。

    案例:上厕所,困了,在坑位上睡一会

  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法,将该线程挂起,该线程不会释放锁

Lock锁

简介:Lock是一个接口,是java.util.concurrent.locks包下面的,它是控制对个线程对共享资源访问的工具。锁提供了对共享资源的独占访问,就是只允许一个线程去访问。每次只能由一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

import java.util.concurrent.locks.ReentrantLock;
​
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();
​
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}
​
class TestLock2 implements Runnable {
    //定义Lock锁
    private final ReentrantLock lock = new ReentrantLock();
    int ticketNum = 10;
    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();//这里就是加锁
                if (ticketNum > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                    System.out.println(Thread.currentThread().getName() +" | "+ ticketNum--);
                }else {
                    break;
                }
            } finally {
                lock.unlock();//释放锁
            }
        }
    }
}

synchronized 与 Lock 的对比

  • Lock是显式锁(手动开启和关闭锁) synchronized是隐式的锁,出了作用域自动释放

  • Lock只有代码块,synchronized有代码块锁和方法锁

  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且有更好的拓展性(提供更多的子类)

  • 优先顺序

    Lock > 同步代码块 > 同步方法

线程通讯

问题引入:生产者和消费者问题,生产者生产完毕之后,要提醒消费者来取,消费者取完之后要通知生产者生产,仅仅用synchronized锁是无法实现的,所以需要了解线程通讯的机制。

方法名作用
wait()表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long time)指定等待的毫秒数
notify()唤醒一个处于等待状态的线程
notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度
/**
 * 测试生产者,消费者模型
 * 利用缓冲区解决:管程法
 * //生产者,消费者,产品,缓冲区
 */
public class TestPC {
    public static void main(String[] args) {
        SyncContainer container = new SyncContainer();
        new Producer(container).start();
        new Custom(container).start();
    }
}
​
//生产者
class Producer extends Thread {
    SyncContainer container;
    public Producer(SyncContainer container){
        this.container = container;
    }
​
    //生产
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了"+i+"只鸡");
        }
    }
}
​
//消费者
class Custom extends Thread {
    SyncContainer container;
    public Custom(SyncContainer container){
        this.container = container;
    }
    //消费
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            Chicken pop = container.pop();
            System.out.println("消费了Id为"+pop.id+"的鸡");
        }
    }
}
​
//产品
class Chicken{
    int id;//产品编号
    public Chicken(int id) {
        this.id = id;
    }
}
​
//缓冲区
class SyncContainer{
    //需要一个容器大小,能放十只鸡
    Chicken[] chickens = new Chicken[10];
    int count = 0;
​
    //生产者放入产品
    public synchronized void push(Chicken chicken){
        //如果容器满了,等待消费者消费
        if (count == chickens.length){
            //通知消费者消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满,则丢入产品
        chickens[count++] = chicken;
        //通知消费者来消费
        this.notifyAll();
    }
    //需要消费者消费产品
​
    public synchronized Chicken pop(){
        if (count == 0) {
            //等待生产者生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        count--;
        Chicken chicken = chickens[count];
        //通知生产者
        this.notifyAll();
​
        return chicken;
​
    }
}

线程池简介

背景:经常创建销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。

好处:

  • 提高响应速度

  • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)

  • 便于线程管理

    • corePoolSize:核心池的大小

    • maximumPoolSize:最大线程数

    • keepAliveTime:线程没有任务时候最多保持多长的时间后终止

ExecutorService:真正的线程池接口,常见子类TreadPoolExecutor

void execute(Runnable com):执行任务、命令,没有返回值,一般用来执行Rannable

<T>Future<T>submit(Callable task):执行任务,有返回值,一般用来执行Callable

void shutdown():关闭连接池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class TestPool {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);//参数为线程池大小
        //执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        //关闭连接
        service.shutdown();
    }
}
​
class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

代码结果:

线程基础的知识先讲到这里,后续还会继续更新高级线程相关的知识点!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值