JavaSE笔记(六)

十 多线程(重)

10.1 线程和进程

进程:

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。——百度百科;

可以简单理解为正在进行的程序。

 

线程:

线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。——百度百科; 简单理解:进程中一个负责程序执行的控制单元,程序的执行路径。

 

一个进程中至少有一个线程。每个线程都有自己的任务。开启多个线程是为了同时运行多个代码;

 

注:这里的同时运行,并不是真正意义上的同时;而是cpu非常快的运行速度达成的“同时”,因为cup是对多个应用程序切换运行的,并且是随机的。

 

优点:解决了多部分同时运行的问题;

缺点:线程过多时,执行效率降低。

 

Jvm启动时就启动了多线程,至少分析出两个线程:执行main()的线程,负责垃圾回收器的线程。jvm创建的主线程的任务都定义在了主函数中。

 

 

 

 

10.2 Thread

Java中创建线程方式一:继承Thread类。

 

步骤:

1,定义一个类继承Thread类。

2,覆盖Thread类中的run方法。

3,直接创建Thread的子类对象创建线程。

4,调用start方法开启线程并调用线程的任务run方法执行。

 

创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。而运行的指定代码就是这个执行路径的任务。

 

自定义的线程它的任务在哪儿呢?

Thread类用于描述线程,线程是需要任务的。所以Thread类也对任务的描述。这个任务就通过Thread类中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数。run方法中定义就是线程要运行的任务代码。

开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法。

将运行的代码定义在run方法中即可。

 

发生异常的线程不影响其他线程的运行

 

在未设置优先级前,各线程随机运行。

 

 

线程的状态图:

225004_iSzG_2541827.png 

10.3 Runnable

Java创建线程的另一种方式。当一个类有了父类,又想具有线程,怎么办呢?针对这种需求, 出现了Runnable接口。可以通过实现Runnable接口来完成。

 

步骤:

1 定义一个类声明实现接口Runnable

2 实现run方法 ,将任务代码封装到run方法中。

3 创建类对象,将对象引用传入Thread 的构造器中,创建线程。

4 启动线程start()

 

 

很多时候,我们需要的仅仅是Thread中的run()方法,如果我们使用继承来创建线程,子类就具有了很多我们不需要的方法。因此,一般情况下,都是使用第二种线程创建方式,即实现Runnable接口。并且Runnable恰恰实现了对线程任务的进行对象封装。

 

优点:1 将线程任务从子类中分离出来,单独封装;按照面向对象的思想。将任务封装成了对象。2 避免了java单继承的局限性。

 

 

10.4 经典买票

 

代码1:

private int count = 100;//票数

 

public void run() {

 

sale();

 

}

 

public void sale(){

while(count>0){

 

System.out.println( Thread.currentThread().getName() + "已买出:"+(200-count)+ "张票" + "-----"+ "剩余票数:" + count--);

 

} System.out.println(  "票已卖完!");

}

 

main(){

SellWindow s1 = new SellWindow();

 

Thread t1 = new Thread(s1);

Thread t3 = new Thread(s1);

 

t1.start();

t3.start();

}

 

 

运行上述代码发现100的票被卖了两次;那是因为线程是随机执行的,两个线程之间没有通信,互相独立造成对共享数据票数的重复操作。

 

造成线程安全问题的原因:1 有多个线程操作共享数据 2 操作共享数据的线程代码有多条。

 

 解决思路:将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不能参与操作。必须等到当前线程把代码执行完毕后,其他线程才能参与运算。

 

如何将代码封装起来呢?使用synchronized关键字!

“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块。

 

格式:

synchronized(对象){

同步代码;

}

 

synchronized(obj){

sale();

}

这就是同步代码块,使用同步代码块可以解决上述问题。

 

括号中的对象可以理解为一种标志,当有线程进入时,该标记值改变可以理解为由“开”到“关”,使其他线程无法进入。当前线程退出后,标志由“关”变会“开”,这样拥有该任务的多个线程才能竞争进入,反复如此。一般这个对象称为对象锁。

 

同步解决了线程安全的问题,但降低了效率。同步外的线程都会判断为同步锁。

 

同步的前提:必须是多个线程使用同一个锁。

 

synchronized也可以作为修饰符修饰函数,public synchronized void xxx(){},这样的函数称为同步函数。如: public synchronized void sale(){} 。但同步函数是对整个函数的全局同步。不适用于仅仅为了同步函数中的部分代码就将函数声明为同步的。适合于将需要同步的代码封装成同步函数。

 

注:同步函数使用的锁是this。同步代码块的锁是任意的对象。一般建议使用同步代码块。静态函数的同步锁是当前的字节码对象,this.getClass()

 

 

同步使用的技巧:找出真正需要同步的代码,是关键!

 

死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。————百度百科

 

Java中一般同步的嵌套会产生死锁,出现两个线程互相等待对方做出某个动作的情形。

 

 

10.5 线程间的通信

线程拥有相同资源,执行不同的任务。并且有一定的执行顺序,那么线程之间就需要通信。

 

通信方法:

wait():让线程进入冻结状态,被wait的线程会被存储到线程池中。

notify():唤醒线程池中的一个线程(任意的,随机的)。

notifyAll():唤醒线程池中的所有线程。

 

我们发现Object类中有wait()、notify()、notifyAll()这些用来操作线程的方法。为什么这些方法会放在Object中呢?

1 这些方法存在与同步 2 使用这些方法时必须要标识所属的同步锁 3锁可以是任意的,所以任意对象的调用方法一定定义在Object中,所有对象都继承Object,所有对象都有这些方法。

 

注:这些方法都是final的,即它们都是不能被重写的,不能通过子类覆写去改变它们的行为。

 

这些方法必须定义在同步中,因为这些方法都是用来控制线程状态的方法,必须明确操作的是那个锁上的线程。即用锁对象来调用这些方法。

 

sleep和wait的最大区别:sleep:释放执行权,不释放锁 wait: 释放执行权,释放锁。

 

停止线程: Thread 提供了停止线程的方法。stop(),但是已过时,千万别用!

 

当run方法结束,线程结束。一般的多线程任务都是用来执行循环的,所以控制循环的结束就能控制线程结束。而控制循环的结束一般用定义标记来完成。如

布尔型flag等。

 

但当线程处于冻结状态是无法读取,标记的,那线程如何结束呢?

使用interrupt()方法。该方法可以将冻结状态的线程恢复到运行状态,让线程具备cpu执行资格,当时该方法是强制动作,会发生中断异常。

 

 setDaemon()方法:将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。 守护线程可以理解为后台线程,前台线程需要手动结束,后台线程会随着前台线程的结束而自动结束。

 

join()方法:等待某个线程终结,临时加入一个线程运算时可以使用jion方法。

 

线程的优先级:1——10 MIN_PRIORITY = 1;NORM_PRIORITY = 5;MAX_PRIORITY=10 ,线程的优先级越大,获得cpu执行权的几率越高。

 

线程组:ThreadGroup 线程组表示一个线程的集合。此外,线程组也可以包含其他线程组。线程组构成一棵树,在树中,除了初始线程组外,每个线程组都有一个父线程组。

 

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

转载于:https://my.oschina.net/ByIjNn/blog/700654

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值