多线程基础

多线程

概念:

程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。

线程相关的API

Thread.currentThread().getName():获取当前线程的名字。
1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活

线程的调度

调度策略:
时间片:线程的调度采用时间片轮转的方式。
抢占式:高优先级的线程抢占CPU。

Java调度方法:
1.对于同优先级的线程组成先进先出队列(先到先服务),使用时间片策略。
2.对高优先级,使用优先调度的抢占式策略。

线程的优先级:
方法:
getPriority():返回线程优先级。
setPriority(int newPriority):改变线程的优先级。
等级:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5

多线程的创建方式:

1.继承Thread类

start方法的作用:1.启动当前线程 2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。

run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

2.实现Runnable接口

1.创建一个实现了Runable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()

开发中,优先选择实现Runable接口的方式
原因1:实现的方式没有类的单继承性的局限性
2:实现的方式更适合用来处理多个线程有共享数据的情况

线程的声明周期

JDK中用Thread.State类定义了线程的几种状态,如下:

线程生命周期的阶段 描述
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

线程的分类

java中的线程分为两类:1.守护线程(如垃圾回收线程,异常处理线程),2.用户线程(如主线程)
若JVM中都是守护线程,当前JVM将退出。(形象理解,唇亡齿寒)

线程的同步

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

Synchronized关键字

两种方式:

方式一:同步代码块
使用同步监视器(锁)
Synchronized(同步监视器){
//需要被同步的代码
}
说明:锁就是一个对象,多线程使用锁时必须确保就是同一个对象。在Thread类中锁记得唯一,可以使用static关键字使对象唯一

方式二:同步方法
public synchronized void 方法名(){}
使用同步方法,对方法进行synchronized关键字修饰
将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

总结:1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
2.非静态的同步方法,同步监视器是this

3.静态的同步方法,同步监视器是当前类本身,继承自Thread。
4.Thread使用同步块,使用类.class充当锁。类.class只加载一次。
5.在Runnable接口的类中,使用同步块,锁可以为this,对象本身。
6.Thread类使用同步方法,需要将同步方法加上static,要变成唯一性。

单例模式之懒汉式

普通单例模式之懒汉式:

class Bank{
private Bank(){}
private static Bank instance =null;
public static Bank getInstance(){
if(instance == null ){
instance=new Bank();
}
return instance;
}
}

同步后的单例模式之懒汉式:

class Bank{
private Bank(){}
private static Bank instance =null;
public static synchronized Bank getInstance(){
if(instance == null ){
instance=new Bank();
}
return instance;
}
}

优化的同步后的单例模式之懒汉式:

class Bank{
private Bank(){}
private static Bank instance =null;
public static synchronized Bank getInstance(){
if(instance==null){
if(instance == null ){
instance=new Bank();
}
}
return instance;
}
}

死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

解决方法:
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步

Lock锁

Lock接口时控制多个线程对共享资源进行访问的工具。
ReentrantLock类实现了Lock。可以显式加锁、释放锁。

lock()和unlock()方法
将需要同步的执行步骤用lock()和unlock()方法来包住
image-20210615205916181

1.面试题: synchronized 与Lock的异同?
相同:二者都可以解决线程安全问题
不同: synchronized 机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()) ,同时结束同步也需要手动的实现(unlock() )
2.优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源) >同步方法(在方法体之外)

线程通信

交替打印1-100

涉及到的三个方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

说明:
1.wait(), notify(), notifyALL()三个方法必须使用在同步代码块或同步方法中。
2.wait(), notify(), notifyALL()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegaLMonitorStateException异常
3.wait(), notify(), notif三个方法是定义在java. lahg. object类中。

面试题:sleep与wait的异同

相同点:一旦执行方法, 都可以使得当前的线程进入阻塞状态。
不同点:
1)两个方法声明的位置不同: Thread类中声明sleep(),object类 中声明wait()
2)调用的要求不同: sLeep()可以在任何需要的场景下调用。 wait()必须使用在同步代码
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait() 会释放锁。

创建多线程的方式三:实现Callable接口

实现步骤:
1.创建一个实现Callable的实现类。
2.实现call方法,将此线程需要执行的操作声明在call()中。
3.创建Callable接口实现类的对象
4.将此CaLLable接口实现类的对象作为传递到FutureTask构造器中, 创建FutureTask的对象
5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
6.获取CaLlabLe中qall方法的返回值

如何理解实现Cal lable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
1.call() 可以有返回值的。
2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
3.Callable是支持泛型的

创建多线程的方式四:线程池

背景:
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,.
对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize:核心池的大小
maximumPoolSize: 最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止 。

JDK 5.0起提供了线程池相关API: ExecutorService 和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command):执行任务1命令,没有返回值,一般用来执行
Runnable
Future submit(Callable task): 执行任务,有返回值,一 般又来执行
Callable
void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool(): 创建个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingle’ ThreadExecutor():创建一个只有 一个线程的线程池
Executors.newScheduledThreadPool(n): 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值