JAVA 多线程讲解

本文详细介绍了在Java中创建多线程的两种方法:通过继承Thread类和实现Runnable接口,以及如何设置线程任务、使用线程名称、暂停和唤醒。此外,讲解了线程安全问题、同步代码块、同步方法和Lock锁的使用,以及线程状态和通信机制。
摘要由CSDN通过智能技术生成

创建多线程的方法

创建多线程程序的第一种方式:创建Thread类的子类

​ java.lang.Thread类:是描述线程的类,我们想要实现多线程的程序,就必须继承Thread类

实现步骤:

​ 1.创建一个Thread类的子类

​ 2.在Thread类的子类种重写Thread类中的run方法,设置线程任务(开启线程要做什么)

​ 3.创建Thread类的子类对象

​ 4.调用Thread类中的方法start方法,开启新的线程,执行线程的run()方法。

​ void start() 使该线程开始执行;Java虚拟机调用该线程的run方法。

​ 结果是两个线程并发地运行,当前线程(main线程)和另外一个线程(创建地新线程,执行其run方法)。

​ 多次启动一个线程是非法的。特别当线程已经结束执行后,不能重新启动。

java程序属于抢占式调度,哪个线程的优先级比较高,哪个线程就会优先执行;同一个优先级,随机选择一个执行

创建多线程程序的第二种方式:采用Runnable

java.lang.Runnable

​ Runnable接口应该由那些打算通过某一线程执行其真实例的类来实现。类必须定义一个成为run的无参数方法。

java.lang.Thread类的构造方法

​ Thread(Runnable target)分配新的Thread对象。

​ Thread(Runnable target, String name) 分配新的Thread对象。

实现步骤:

​ 1.创建一个Runnable接口的实现类

​ 2.在实现类中重写Runnable接口的run方法,设置线程任务。

​ 3.创建一个Runnable接口的实现类对象

​ 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象(句柄)

​ 5.调用Thread类中的start方法,开启新的线程执行run方法

实现Runnable接口创建多线程程序的好处:

​ 1.避免了单继承的局限性

​ 一个类只能继承一个类,类继承了Thread类就不能继承其他的类

​ 实现了Runnable接口,还可以继承其他类,实现了其他的接口

​ 2.增强了程序的可扩展性,降低了程序的耦合性(解耦)

​ 实现了Runnable接口方式,把设置线程任务和开启新线程进行了分离(解耦)

​ 实现类中,重写了run方法:用来设置线程任务

​ 创建Thread类对象,调用start方法,用来开启新的线程

Thread的一些方法

获取线程的名称:

​ 1.使用Thread类中的方法getName()

​ string getName() 返回该线程的名称

​ 2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称

​ static Thread currentThread() 返回当前正在执行的线程对象的引用

System.out.printl(Thread.currentThread().getName());

设置线程的名称:

​ 1.使用Thread类中的方法setName(名字)

​ viod setName(String name)改变线程名称,使之与参数name相同。

​ 2.创建一个带参数的构造方法。参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字

​ Thread(String name)分配新的Thread对象。

使线程进行短时间的暂停:

public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。毫秒数结束之后,线程继续执行。

匿名内部类方式实现线程的创建

匿名内部类实现线程的创建

匿名就是没有名字的类,内部类是写在其他类内部的类

匿名内部类作用:简化代码

​ 把子类继承父类,重写父类的方法,创建子类对象合一步完成

​ 把实现类,实现类接口,重写接口中的方法,创建实现类对象合成一步完成

匿名内部类的最终产物是:子类/实现类对象,而这个类没有名字

格式:

​ new 父类/接口{

​ 重复父类/接口中的方法

}

new Thread() {

​ //重写run方法,设置线程任务,线程的父类是Thread。 new thread().start();

}.start();

//线程的接口是Runnable, RunnableImpl r=new RunnableImpl();

接口等于实现了一个类…

Runnable r=new Runnable() {

​ 方法;

}

new Thread®.start();

线程安全问题

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。要解决上面的多线程并发访问一个资源的安全问题:也就是解决重复票和不存在的票问题,Java中提供了同步机制(Synchronized)来解决。

为了保证每个线程都能正常执行原来的操作,Java引入了线程同步机制。

有三种方式完成同步操作:

  1. 同步代码块
  2. 同步方法
  3. 锁机制

同步代码块

同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式如下:

synchronized(锁对象){

​			需要同步操作的代码

}

同步锁:

对象的同步锁只是一个概念,可以想象成在对象上标记一个锁。

1.锁对象,可以是任意类型

2.多个线程对象,要使用用一把锁

注意:

​ 1.通过代码块中的锁对象,可以使用任意的对象

​ 2.但是必须保证多个线程使用的锁对象是用一个

​ 3.锁对象作用:

​ 把同步代码块锁住,只让一个线程在同步代码中执行

同步技术的原理:

使用一个锁对象,这个锁对象也叫做同步锁,或者是叫对象监视器

3个线程一起抢夺cpu的执行权,谁抢到了谁就会去执行自己的run方法来进行卖票

​ t0抢到了cpu的执行权,执行run方法,遇到synchronized代码块,这个

​ 时候t0会检查synchronized代码块是否有锁对象,发现有的话,会获取

​ 到锁对象,进入到同步中执行。

​ t1抢到了cpu的执行权,执行run方法,遇到synchronized代码块,这个

​ 时候t1会检查synchronized代码块是否有锁对象,

​ 发现没有,t1就会进入到堵塞状态,会一直等待t0线程归还锁对象,一

​ 直到t0线程执行完同步的代码,会把锁对象归还给同步代码块,t1才能

​ 获取到锁对象进入到同步中执行

总结:同步中的线程,没有执行完毕是不会释放锁,同步外的线程没有锁进不去同步。

同步保证了只能由一个线程子啊同步中执行共享数据,保证了安全,但是程序频繁的判断锁,释放锁程序的效率就会降低。

同步方法

同步方法:使用synchronized修饰的方法,就叫同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等候着。

格式:

public synchronized void method(){

//可能会产生线程安全问题的代码

}

同步锁是谁:

对于非static方法,同步锁就是this。

对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

定义方法的原理:

定义一个同步方法,同步方法也会把方法内部的代码锁住,只让一个线程执行,

同步方法锁住的对象是谁?

就是实现类对象 new RunnableImpl()

也就是this

静态的同步方法

这个时候的锁对象就不能是this了,因为this是创建对象之后产生的,静态方法优于对象,静态方法的锁对象是本类的class属性–>class文件对象(反射)。

LOCK锁

解决线程安全问题的第三种方法:使用Lock锁

java.util.concurrent.locks.Lock接口

lock实现了提供了比使用synchronized方法和语句可获得的更广泛的锁操作。

Lock接口中的方法:

​ void lock()获取锁

​ void unlock()释放锁

java.util.concurrent.locks.ReentranLock implement Lock接口

使用步骤:

​ 1.在成员为止创建一个ReentrantLock对象

​ 2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁

​ 3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

线程状态

线程一共有六种状态:

NEW 至今尚未启动的线程处于这种状态

RUNNABLE 正在Java虚拟机中执行的线程处于这种状态。

BLOCKED 受阻塞并等待某个监视器锁的线程处于这种状态

WAITTING 无限期地等待另一个线程来执行某一特定操作的线程处于这种状态

TIMED_WAITTING 等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态

TERMINATED 已退出的线程处于这种状态

TIMED_WAITTING以及WAITTING需要Object.notify()来进行操作唤醒他们各自的状态。

线程之间的通信

等待唤醒案例:线程之间的通信

​ 创建一个顾客线程(消费者):告知老板要的包子的种类以及数量,调用wait的方法,放弃cpu的执行,进入到WAITTING状态(无限等地啊)

​ 创建一个老板线程(生产者):挂了5秒钟做包子,做好包子之后,调用notify方法,唤醒顾客吃包子

注意:

​ 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行

​ 同步使用的锁对象必须保证唯一

​ 只有锁对象才能调用wait和notify方法

Object类种的方法

​ void wait() 在其他线程调用此对象的notify() 方法或者是 notifyAll() 方法前,导致当前线程等待

​ void notify()

​ 唤醒在此对象监视器上等待的单个线程

​ 会继续执行wait方法之后的代码

进入到TimeWaiting(计时等待)有两种方式:

​ 1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态

​ 2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程进入到Runnable/Blocked状态

唤醒的方法:

​ void notify()唤醒此对象监视器上等待的单个线程

​ void notifyA()唤醒在此对象监视器上等待的所有线程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值