多线程

本文详细阐述了进程与线程的基本概念、创建方式及区别,深入探讨了多线程环境下的同步与通信机制,包括线程的创建、同步代码块、死锁防范、条件变量的应用以及线程间的通信方式,旨在帮助开发者理解和解决多线程编程中的常见问题。
摘要由CSDN通过智能技术生成

 

进程和线程

进程是资源分配的最小单位,线程是CPU调度的最小单位”。

进程:在进程创建时,需要从系统那里申请一定的独立空间进行程序的运行。进程之间的空间是相互独立的,无法互相访问。

线程:准确的说程序执行流的最小单元,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。


包package

建立包

格式:Package pack  // 建立对应的包目录

编译时指定要存放class的位置。

注意:

1、 一个类在继承其他包中的类时,需要包名.类名


2、 要被其他包中的类想要被继承,需要申明为public或protected类型


可访问状态:

状态

Public

Protected

Default

Private

同一包中

OK

OK

OK

NO

同一类中

OK

OK

OK

OK

子类间

OK

OK

OK

NO

不同包中

OK

OK

NO

NO

 

导出包:

格式:import 包名.*;

 

多线程:

进程:正在执行中的程序,每个程序都有一个执行顺序。即执行路径或者叫一个控制单元。(目的:封装控制单元,为整个程序开辟需要的内存)

线程:进程中一个独立的控制单元,线程控制进程的执行,并且一个进程中至少有一个线程。

注:java中至少有一个线程负责java程序执行,而这个线程运行的代码存在于main中,该线程为主线程。

 

创造线程的方法一:继承方法

1、 定义类继承Thread。(Thread用于描述线程,该类定义了一个功能,用于存储要运行的方法,即run()方法)

2、 复写Thread中run方法。(用于存储线程要运行的代码,封装代码)

3、 调用线程start方法;(start作用:(1)启动线程;(2)调用run方法)

注意:start相当于发令枪,一个线程仅有一个发令枪。


线程都有自己的特有名称,用getName获得。

Static  Thread  currentThread():返回当前运行的线程对象

创造线程的方法二:实现方法

1、 定义类实现Runable接口

2、 覆盖Runnable接口中的run方法(存放要运行的代码)

3、 通过Thread建立线程对象。

Thick  t =  new  Ticket();

4、 将Runnable接口子对象作为实参,传递给Thread类构造函数

Thread  t1 = new Thread(t);

t1.start();

(自定义的run方法为Runnable接口的子类对象,所以要让线程去执行指定对象run方法,需要明确所属的对象)

5、 调用Thread类的start方法,开启线程,并调用Runnable接口子类的run方法

 

注:t1其用到的构造函数:

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

 

实现方法VS继承方法

实现方式:避免了单继承的局限性(建议使用)

相同点:都需要复写run方法。

区别:

1、 继承:线程代码存放在thread子类run方法中

2、 实现:线程代码存放在接口子类的run方法中

注意:
1、当使用继承的时候,主要是为了不必重新开发,并且在不必了解实现细节的情况下拥有了父类我所需要的特征。它也有一个很大的缺点,那就是如果我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承 Thread 类,
2、java只能单继承,因此如果是采用继承Thread的方法,那么在以后进行代码重构的时候可能会遇到问题,因为你无法继承别的类了,在其他的方面,两者之间并没什么太大的区别。
3、implement Runnable是面向接口,扩展性等方面比extends Thread好。
4、使用 Runnable 接口来实现多线程使得我们能够在一个类中包容所有的代码,有利于封装,它的缺点在于,我们只能使用一套代码,若想创建多个线程并使各个线程执行不同的代码,则仍必须额外创建类,如果这样的话,在大多数情况下也许还不如直接用多个类分别继承 Thread 来得紧凑。

多线安全问题:

当多条线程语句在操作同一共享数据时,一线程只得到了运行资格,但还未提取数据被另外一个线程打断并取走数据,导致共享数据错误。


在第14行,t1刚执行到此被t2打断,t2执行到此被t3打断。因为此时大家都判定tick>0所以后续会继续执行。结果就是,出现负数。这就产生了多线程错误。

解决方法:对多条操作共享的舞剧让一个线程完全执行玩(其他线程不能打扰)。即使用同步代码块。

 

同步代码块:

语法:

Synchronized(对象)// 这里的对象为持锁的对象,明确谁要对接下来的数据处理

{

           需要被同步的代码;

}

对象如同锁,持锁的线程可以在同步代码快中执行,没有锁额对象的线程,即使获得CPU执行权也进不去。

Eg:火车上的卫生间标志。

 

锁的概念:分为对象锁和类锁

java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的

 

注意:放进去的代码部分应该为所共同操作的资源部分。

同步的前提:

1、 必须有两个或两个以上的线程

2、 必须是多个线程执行同一个锁

目的:保证资源仅被一个线程执行

好处VS弊端

好处:解决了多线程安全问题

弊端:增加了资源的消耗(CPU资源)

 

多线程需要注意的点:

1、 明确哪些代码是多线程运行代码

2、 明确共享数据

3、 明确多线程运行代码中哪些语句操作共享数据

 

同步函数

将Synchronized放在修饰位置上即同步函数;

问题:同步函数的锁是什么?

答:函数要被对象调用,那么函数有一个所示对象的引用,就是this。因此锁为this。

(若同步函数被static修饰,同步函数的锁不为this。因为其中不可定义this。但在类创建时,其产生了该类的字节码文件对象,即:类名.class 该对象的类型为本类的calss(字节码对象))

再来看懒汉单例模式:


说明:懒汉模式与恶汉模式最大的区别在于其创建对象的延时性。在多线程操作过程中,容易引起线程错误。为了避免该错误便引入锁。但因为每次创建都要先判锁,导致效率不高,因此在之前加入S判断,减少锁的判断次数,从而减少消耗。其锁为Single的class文件

 

死锁:

Eg:一人一支筷子,相互请求吃饭。

原因:同步中嵌套同步,锁却不同。

程序例子



这里的锁为了好区分,定义了MyLock对象。在完美情况下,t1拿到执行权在21行等待t2。这时t2获取执行权执行接下来的代码。如果运气差的话,t1拿到执行权,去等待锁b,而这时t2也刚刚建立,执行到32行等待锁a,于是两个就互相等,形成死锁。归结到底是执行权的问题

ReetrantLock; await; singal  “VS”  Synchronized ,wait, notigy

1、 Lock lock = new ReetrantantLock(); // 产生一个可冲入的互斥锁


…….
lock.lock()

lock.unlock()

 

=Synchronized()

 

2、 Condition  con =lock.newCondition() ;// 将Object监视器方法分成不同对象,一边同任意lock实现结合,提供多个对象等待。其返回值为绑定该lock的心Condition实例。

Con.await()= this.wait();

Con.singal= this.notify ;

 

多线程间通信

在对线程通信过程中,每个对象都要用到同一个资源库,而Synchrinizied则是为资源库上锁,防止一个资源在同一时间被多个用户使用。资源库需要自带标志,以表明自己此时是否备用。而在唤醒线程之前需要循环查看此标志。

 

关键字:

1、 .join():对调用他的线程,会一直执行到该线程死为止。

2、 setDaemon():将线程设置为守护线程,当前台线程结束后,该线程自动结束。

3、 wait():等待,有执行资格但是没有执行权,一直要到被唤醒。(等待中的线程都被放在线程池中)

4、 interrupt():当进入冻结状态时,将其强制进行恢复为运行。

5、 notify() : 唤醒线程。每次唤醒只唤醒线程池中队列前的线程。

wait() , notify() ,nitifyAll()

都使用同步类中,因为要对持有监视器的的线程操作,所以要用在同步中,因为只有同步才具有锁。

notify():唤醒一个;唤醒队列前的线程。

notifyAll():唤醒全部等待线程;

 

问:为什么定义在Object类中呢?

答:因为所有对象都可以成为锁。

 

JDK1.5新特性:

其提供了多线程升级解决方案。

在资源中,将同步Synchronized替换成显示的Lock操作。将Object中的wait,notify替换了Condition对象。Condition对象可以在同一个对象中存在多个。以表示对不同对象在使用资源时进行唤醒或等待。

Synchronized 和Lock实例函数

升级之前:


升级之后:





在synchronized方法中:

他操作的是Object对象,相对应的函数为(wait,notify,notifyAll)

在他当中锁对象只能有一个;this.class;

 

Lock创建的ReentrantLock对象中:

他操作的是condition对象,相应的函数为(await, signal,signalAll) 

他当中锁对象能有很多个:

Synchronized “VS” ReentrantLock

1.     如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断

如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

2.     synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

3.     在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;


 

 

 

 

线程停止的方法:让run方法结束

一般多线程,运行代码通常是循环结构,只要控制住循环就可以让run方法结束,也就是线程结束。

 

特殊情况:当线程处于冻结状态,就不会结束。当没有方式让线程回复到运行状态时,就需要对冻结进行清除。强制让线程回复到运行中来。这样就可以操作标记让线程结束。(Thread 提供了interrupt方法。)



当t1 interrrupt之后他会跑到自己的run函数中去找异常处理语句。

后台线程(守护线程) VS 前台线程

开启后和前台线程共同抢CPU,但当前台线程结束之后,后台线程自动结束。

Eg:在制造商品和出售商品的例子中,出售商品应该设为制造商品的守护线程,因为没有制造商品就没有出售商品。


线程中一些操作:

1、 设置优先级:setPriority():设置后,让能抢到CPU的可能性更高(总共10级,默认为5,越高则获取cpu执行的几率越大)。

t1.setpriority(Thread_MAX_PRIORITY);

void

setPriority(int newPriority)
          更改线程的优先级。

 

2、 暂停正在执行的形成:.yield()。目的为,平均线程的执行次数,你一下我一下。

Thread.yield();强制释放其执行权

static void

yield()
          暂停当前正在执行的线程对象,并执行其他线程。


3、 让某线程执行到死才释放cpu使用权。t1.join();  

 void

join()
          等待该线程终止。

 

 

 

常用的线程操作:

Thread类:

1.        获取有名字的线程对象

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

如果没有给名字,则其默认的名字为

 

2.        获取线程名称:

 String

getName()
          返回该线程的名称。

 

3.        获取当前正在执行的线程

static Thread

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

 

4.        将该线程进入冻结状态

static void

sleep(long millis)
          在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

注意:


他有抛出异常,需要try  catch进行处理。

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值