• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

lzsykal

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

20220806 第六组 张嘉源 学习笔记一

多线程(全)

多线程

线程是进程的一个执行单位,一个进程包含一个或者多个线程,一个线程不能独立的存在,它必须是进程的一部分

并发:指多个事件在同一时间段内一起发生

并行:指多个事件在同一时刻同时发生

线程的生命周期

从产生到死亡的过程

NEW:这个状态主要是线程未被start()调用执行
RUNNABLE:线程正在JVM中被执行,等待来自操作系统的调度
BLOCKED:阻塞。因为某些原因不能立即执行需要挂起等待。
WAITING:无限期等待。Object类。如果没有唤醒,则一直等待。
TIMED_WAITING:有限期等待,线程等待一个指定的时间
TERMINATED:终止线程的状态,线程已经执行完毕。

线程.drawio

线程的优先级

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

创建线程

1.继承Thread类

创建一个新的类,该类继承Thread类,然后创建一个该类的实例

继承类必须重写run()方法,必须调用start()方法执行

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(2);
    }
}
public class Ch01 {

    public static void main(String[] args) {
        System.out.println(1);
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println(3);
        System.out.println(4);
    }
}

2.实现Runnable接口

class MyThread implements Runnable{
//      重写run方法
    @Override
    public void run() {
        System.out.println(2);
    }
}
public class Ch01 {
    public static void main(String[] args) {
        System.out.println(1);
        MyThread myThread=new MyThread();
//        多态
        Thread t=new Thread(myThread);
        t.start();
        System.out.println(3);
        System.out.println(4);
//        输出顺序为:1342
    }
}

3.通过Callable和Future创建线程

创建Callable接口的实现类,并实现call()方法,该call()方法有返回值

创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 实现Callable接口
class MyThread3 implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println(2);
        return "call方法的返回值";
    }
}
public class Ch04 {
    public static void main(String[] args) {
        System.out.println(1);
// Callable-->FutureTask-->RunnableFuture-->Runnable-->Thread
        FutureTask<String> futureTask = new FutureTask<>(new MyThread3());
        new Thread(futureTask).start();
        System.out.println(3);
        System.out.println(4);
    }
}
区别

Runnable和Callable的异同:

相同:都是接口,都可以写多线程,都调用start()方法

区别:

1.实现Callable接口能返回结果,实现Runnable接口的线程不能返回结果

2.Callable接口的call方法允许抛异常,Runnable的run方法不能抛异常

3.实现Callable接口的线程可调用Future.cancel()取消执行,而Runnable接口线程不能

常用方法

Object类

wait()方法:使当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法

notify()方法:唤醒正在等待对象监视器的单个线程

notifyAll()方法:唤醒正在等待对象监视器的所有线程

sleep()方法:使当前线程睡眠一会,让其他线程有机会继续执行,让线程从运行状态变为阻塞状态,方法调用结束后,线程从阻塞状态变为可执行状态,以毫秒为单位,需要捕捉异常

join()方法:把指定的线程加入到当前线程,可以将两个交替执行线程合并为顺序执行的线程,比如在主线程中调用了线程A的join()方法,线程A执行完毕后,才会继续执行线程B

Thread类

start():启动当前线程,执行run方法

run():创建线程后重写run方法

currentThread:静态方法,获取当前正在执行的线程

getId():返回此线程的唯一标识

setName(String):设置当前线程的name

getName():获取当前线程的name

getPriority():获取当前线程的优先级

setPriority(int):设置当前线程的优先级

getState():获取当前线程的声明周期

interrupt():中断线程的执行

interrupted():查看当前线程是否中断

yield():释放当前CPU资源

区别

sleep()和wait()区别:

1.sleep是Thread的静态方法,wait是Object的方法,任何对象实例都能调用

2.sleep不会释放锁,也不需要占用锁,wait会释放锁,单调用它的前提是当前线程占有锁

3.都可以被interrupted方法中断

线程调度

常量:

常量名 备注
static int MAX_PRIORITY 最高优先级(10)
static int MIN_PRIORITY 最低优先级(1)
static int NORM_PRIORITY 默认优先级(5)

方法:

方法名 作用
int getPriority() 获得线程优先级
void setPriority(int newPriority) 设置线程优先级
  • main线程的默认优先级是:5
  • 优先级较高的,只是抢到的CPU时间片相对多一些。大概率方向更偏向于优先级比较高的。

加锁

三种加锁方式:
修饰实例方法,作用与当前实例加锁,进入同步代码前要或得当前实例的锁
修饰静态方法,作用于当前类加锁,进入同步代码前要获得的当前类对象的锁
代码块,指定加锁对象,进入同步代码块之前要获得给定对象的锁

实例方法:调用该方法的实例
静态方法:类对象
this:调用该方法的实例对象
类对象:类对象

线程同步

java允许多线程并发控制,当锁哥线程同时操作一个可共享的资源变量时,会导致数据不准确,相互之间产生冲突,因此加锁避免在该线程没有完成操作之前被其他线程调用。

共享数据

多个线程共同操作的变量,可以充当锁

volatile实现线程同步

(1)volatile关键字为域变量的访问提供了一种免锁机制;
(2)使用volatile修饰域,相当于告诉JVM,该域可能会被其它线程更新,因此每次使用该域时就要重新计算,而不是使用寄存器中的值;
(3)volatile不会提供任何原子操作,也不能用来修饰final类型的变量。

注:关于Lock对象和synchronized关键字的选择
(1)如果synchronized能满足用户需求,就用synchronized,因为它能简化代码;
(2)如果需要更高级的功能,就用ReentrantLock,此时需要注意及时释放锁,否则会出现死锁,通常在finally中释放锁。

线程安全

线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

1.数据不可变:java中一切不可变的对象一定是线程安全的
对象的方法的实现方法的调用者,不需要再进行任何的线程安全的保障措施
比如:final关键字修饰的基本数据类型,字符串
只要一个不可变的对象被正确的创建出来,那外部的可见状态永远都不会改变

2.互斥同步:加锁,悲观锁

3.非阻塞同步:无锁编程,自旋

4.无同步方案:多个线程需要共享数据,但是这些数据又可以在单独的线程中计算,得出结果
可以把共享数据的可见范围限制在一个线程之内,这样就无需同步,把共享的数据拿过来
你用你的,我用我的,从而保证线程安全

解决方法

1.同步代码块

Synchronized关键字

控制线程同步,确保数据的完整性,一般加在方法上

public synchronized void save(){}

synchronized(object) {
    //需要同步的代码块
}

当使用同步方法时,synchronized锁的东西是this(默认的)(谁调的锁谁,锁对象调用的方法)

1.选好同步监视器(锁)推荐使用类对象,第三方对象,this

2.在实现接口创建的线程类中,同步代码块不可以用this来充当同步锁

2.同步方法

有synchronized关键字修饰的方法

public synchronize void method{
    //要写的方法的代码
}

由于Java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法之前,需要获取内置锁,否则就处于阻塞状态。

3.同步锁

Lock锁:一个接口,Lock接口的实现类ReentrantLock可重入锁

方法:
public void lock(): 加同步锁
public void unlock():释放同步锁

区别

Lock和synchronize的区别:

1.synchronize是关键字,Lock是一个Java类

2.synchronize无法判断是否获取锁的状态,Lock可以判断是否获取到锁

3.synchronize可以自动释放锁,Lock需要在finally中手动释放锁(因此最佳实践是执行 lock() 后,首先在 try{} 中操作同步资源,如果有必要就用 catch{} 块捕获异常,然后在 finally{} 中释放锁,以保证发生异常时锁一定被释放)

4.synchronize如果修饰线程1和线程2 ,如果当前是线程1获得锁,线程2等待,如果线程1 阻塞状态,线程2会一直等待下去。而Lock锁不一定会等待下去,如果尝试获取不到锁,线 程可以不同一直等待下去

5.Lock锁适合大量同步代码的同步问题,synchronize锁适合代码少量的同步问题

阻塞线程

LockSupport :线程阻塞的工具类,其中所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有唤醒的办法

方法:
park():阻塞
unpark():释放

区别:
1.park不需要获取某个对象的锁(不释放锁)
2.因为中断park不会抛出InterruptedException异常,需要在park之后自行判断中断状态,然后做额外的处理。
总结:
1.park和unpark可以实现wait和notify的功能,但是并不和wait和notify交叉使用。
2.park和unpark不会出现死锁。
3.blocker的作用看到阻塞信息的对象

守护线程

java中提供两种类型的线程:
用户线程:我们平常创建的普通线程
守护线程:用来服务用户线程,我么也可以手动创建一个守护线程
当存在任意一个用户线程的时候,JVM就不会退出

在调用start()方法前,调用setDaemon(true)把该线程标记为守护线程。
如何检查一个线程是守护线程还是用户线程:使用isDaemon()方法。

  • thread.setDaemon(true) 必须在 thread.start() 之前设置,否则会抛出 IllegalThreadStateException 异常。
  • 在Daemon线程中产生的新线程也是Daemon的。

JVM 中的垃圾回收线程就是典型的守护线程

终止线程

1.强行终止:stop()方法,已过时,不推荐使用
2.打一个布尔标记:

class MyThread extends Thread {
    volatile boolean flag = true;

    @Override
    public void run() {
        while(flag) {
            try {
                System.out.println("线程一直在运行...");
                int i = 10 / 0;
            } catch (Exception e) {
                this.stopThread();
            }
        }
    }
    public void stopThread() {
        System.out.println("线程停止运行...");
        this.flag = false;
    }
}

public class Ch03 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

3.interrupt方法

调用interrupt方法会抛出InterruptedException异常,捕获后再做停止线程的逻辑即可。
如果线程while(true)运行的状态,interrupt方法无法中断线程。

class MyThread02 extends Thread {
    private boolean flag = true;
    @Override
    public void run() {
        while(flag) {
            synchronized (this){
//                try {
//                    wait();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                try {
                    sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    this.stopThread();
                }
            }
        }
    }
    public void stopThread() {
        System.out.println("线程停止运行...");
        this.flag = false;
    }
}
public class Ch04 {

//    public void show() {
//        try {
//            wait();
//            // 线程异常终止     异常
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//    }
    public static void main(String[] args) {
        MyThread02 myThread02 = new MyThread02();
        myThread02.start();
        System.out.println("线程开始...");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 中断线程的执行
        myThread02.interrupt();
    }
}

死锁

死锁条件

1.互斥:当资源被一个线程使用(占有),别的线程不能使用,如果另一资源申请,那么申请进程应等到该资源释放为止
2.不可抢占:资源不能被抢占,资源只能被进程在完成任务后释放
3.请求和保持:一个进程占有一个资源,并同时申请另一个资源,而该资源为其他进程所占有
4.循环等待:存在一个循环等待队列,p1占有p2的资源,p2占有p3的资源,p3占有p1的资源,形成了一个等待环路

线程重入

任意线程在拿到锁之后,再次获取该锁不会被该锁所阻碍,线程是不会被自己锁死的,synchronized可重入锁。

线程池

好处:
1.降低资源消耗,通过重复利用已创建的线程来降低创建和销毁造成的资源消耗
2.提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
3.提高线程的可管理性,线程比较稀缺的资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

JDK自带的四种线程池通过Executors提供的。
1.newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程
若无可回收,创建新线程。
2.newFixedThreadPool:创建一个定长的线程池,可以控制线程最大并发数,超出的线程会在队列中等待。
3.newScheduledThreadPool:创建一个定长的线程池,支持定时及周期性任务执行
4.newSinaleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证
所有的任务按照指定顺字执行

这四种线程池的初始化都调用了同一个构造器
ThreadPoolExecutor(int corePoolsize,
int maximumPoolsize long keepAliveTime, TimeUnit unit,
BlockingQueue workQueue ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数的意义(重要):
corePoolsize:线程池里线程的数量,核心线程池大小
maximumPoolsize:指定了线程池里的最大线程数量
keepAliveTime:当线程池线程数量大于corePoolsize,多出来的空闲线程,多长时间被销毁
unit:时间单位
workQuene:任务队列,用于存放提交但是尚未被执行的任务
threadFactory:线程工厂,用来创建线程,线程工厂就是我们new线程的
handler:拒绝策略,是将任务添加到线程池中时,线程池拒绝该任务多采取的相应的措施。

常见的工作队列
ArrayBlockingQueue:基于数组的有界阻塞队列。FIFO。
LinkedBlockingQueue:基于链表的有界阻塞队列。FIFO

线程池提供了四种拒绝策略:
AbortPolicy:直接抛出异常,默认的策略。
CallerRunPolicy:用调用者所在的线程来执行任务
DiscardoldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务
Discardpolicy:直接丢弃任务

创建线程的4种方式
线程同步(synchronized,ReentrantLock,ReentrantReadwriteLock)
线程之间的通信(wait,notify,notifyAlL)
线程类的常用方法

posted on 2022-08-06 20:28  林钟朔一  阅读(31)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3