Java子线程如何运行子程序方法,Java入门:线程

相关概念

进程是操作系统管理的,每个进程都拥有自己独立的内存空间,拥有自己独立的一整套变量,进程和进程之间不共享内存。

多线程线程是同一个进程中的多个线程,他们共享内存和变量。

线程是轻量级的进程,线程是进程的组成部分,是进程中某个单一顺序的控制流,又称为轻量进程。

进程是线程组成的,线程只能在一个进程中的内部执行。创建线程的资源消耗比创建进程小很多。进程与进程之间,在内存方面是独立的,而同一个进程的各个线程之间,是共享内存同一内存块的。

为什么一个CPU可以同时执行那么多进程和线程?

根本原因是,CPU将时间分隔成很多的小片,叫做时间片,每个进程分得一定的小片,得到分配的进程就可以运行,时间片用完就还下一个得到时间片的进程来运行。因为CPU的执行速快到飞起,每个时间片都贼短,所以我们就感觉是在同时执行。

创建线程

创建线程,首先需要定义线程类,定义线程类有两种主要方法:

继承Thread类

实现Runable接口

继承Thread类

定义一个线程只要它继承Thread类,那么它就是一个线程类。然后重写public void run()方法,run()方法里的代码就是线程要执行的代码块,方法run()称为线程体。

Thread类封装了线程的行为,定义了很多控制线程的方法。

Thread类的构造函数:

Thread():创建新的Thread对象

Thread(String name):创建线程并设定线程的名称

Thread(Runable target):根据target创建新的Thread对象

Thread(Runable target,String name):根据target创建新的Thread对象,并且指定线程名称

Thread(ThreadGroup group,Runable target):创建新的Thread对象,并指定所属线程组

来个实栗吧:

294438f0d412

DownLoadThread.java

294438f0d412

ThreadTest.java

实现Runnable接口

上面通过继承Thread类实现线程虽然可以用,但是也有缺点,就是类不能再继承其他类了,因为Java类只能继承一个父类嘛。但是!实现Runnable接口来创建线程就没有这么个问题辣。

来第二个实栗:

294438f0d412

DownLoadThread2.java

294438f0d412

ThreadTest2.java

让这个实例跑起来,观察一下他的输出结果,main()方法的输出和新建线程的输出是交替出现的。

通过实现Runnable接口的方法创建的线程,更加灵活,线程类本身还可以继承其他的类。而使用继承Thread类的方法创建的线程,得到线程的名字就更简单了。推荐使用Runnable方法,童嫂无欺。

线程的调度和控制

Java中一个线程从创建开始,有很多种状态,这些状态可以通过Thread类的一些相关方法进行控制转换。

一个线程有如下的状态:

新建状态(new):创建一个线程类的对象后,还没有调用start()方法的线程称为新建状态

可运行状态(Runnable):调用start()方法后,系统为该线程分配了所需要的资源,但是还没有得到CPU的执行权,这是可运行状态。

正在运行状态(Running):由虚拟机线程管理器调度,获得CPU的执行权,正在CPU上执行run()方法中的代码的线程。

对象wait池等待状态:调用wait()方法后线程在对象的等待池中等待

对象lock池等待状态:遇到synchronized关键代码段,无法获得对象的锁,则在该对象的lock池中等待

其他阻塞状态:如执行sleep()方法或者遇到IO访问阻塞等

结束状态(Dead):运行结束的线程

Thread类的常用方法:

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

String getName():返回该线程的名称

int getPriority():返回线程的优先级

void interrupt():中断线程

static boolean interrrupted():测试当前线程是否已经中断

boolean isAlive():测试线程是否处于活动状态

boolean isDaemon():测试线程是否为守护线程

boolean isInterrupted():测试线程是否已经中断

void join():等待该线程中止

void join(long millis):等待该线程终止的时间最长为millis毫秒

void join(long millis,int nanos):等待该线程终止的时间最长为millis毫秒+nanos纳秒

void setDaemon(boolean on):讲线程标记为守护线程或用户线程

void setName(String name):改变线程名称,线程名称为name

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

static void sleep(long millis):让当前正在执行的线程休眠millis毫秒

void stop():停止线程执行

String toString():返回该线程的字符串表示形式,包括线程的名称、优先级、线程

static void yield():暂停当前正在执行的线程对象,执行其他进程去

线程的优先级:

Thread.MIN_PRIORITY:最小优先级1

Thread.MAX_PRIORITY:最大优先级10

Thread.NORM_PRIORITY:默认优先级5

Java虚拟机根据线程的优先级来调度线程的执行,优先级越高的线程得到的运行机会就越多。如果排队等待运行的线程优先级相同,那么排在前面的线程将得到运行机会。

使用下面的方法可以对优先级进行操作:

int getPriority():得到线程的优先级

void setPriority(int newPriority):设置线程的优先级

294438f0d412

设置线程的优先级

294438f0d412

设置线程的优先级

线程控制:让CPU休息一会

就是使线程放弃CPU的执行一会。有三种情况:

线程调用了yield(),sleep()方法

由于当前线程进行访问,等待用户输入等操作,导致线程阻塞

有高优先级的线程参与调度,导致当前线程放弃CPU

yield实例:

294438f0d412

yield实例

线程控制:就要你等我,这么骄傲没毛病

当前等待另一个线程完成的方法:join()方法。当调用join()时,调用线程将阻塞,直到目标线程完成为止,调用线程在目标线程结束后才能重新得到运行。

join()通常由使用线程的程序调用,用于将主程序划分成许多子程序,每个子程序分配一个线程。当所有的子程序都得到运行后,再调用主程序来进一步操作。

isAlive()方法,是用来判断线程是否在活动状态,返回布尔值。如果线程已经运行结束,将返回false。

一个简单的例子,主要是操作体会一下过程吧。

294438f0d412

join实例

输出结果:

join现在的状态是:false

我是JoinThread的线程

跑起来后的状态是:true

join运行结束,现在状态是:false

程序运行结束

线程守护神:Daemon线程

它是为其他线程提供服务的线程,它一般应该是一个独立的线程,它的run()方法是一个无线循环。

可以通过public boolean isDaemon()方法确定一个线程是否为守护线程。也可以通过public void setDaemon(boolean)方法设定一个线程为守护线程,注意设置守护进程方法要在线程启动方法start()之前。

守护线程与其他线程的区别是,如果守护线程是唯一运行着的线程,程序会自动退出。

典型的比如垃圾回收线程,如果虚拟机都退出了,那么为虚拟机提供收集内存垃圾线程就没有必要再运行了,它会自动停止运行。

来个关于守护神的例子:

守护神类里面写了一个无线循环输出,测试类里面有个循环,测试类的循环执行结束,守护神的运行也随即终止了。

294438f0d412

线程守护

别睡了线程:中断线程

对于睡眠状态sleep()或等待状态wait()的线程,如果我们需要中止其睡眠或者等待状态,可以调用interrupt()方法。

如果线程在睡眠或等待状态下被调用了interrupt()方法,那么线程将抛出interruptException异常,需要将异常捕获并处理。

如果线程没有睡眠或等待,调用interrupt()方法并不会产生异常,也不会对线程有任何的影响。

来一个非常有意思例子:

294438f0d412

中断线程实例

跑起来会输出:

我就睡一会

被吵醒了

干嘛打断我休息?

终止线程

API中有如下方法,不过IDE会提示这个方法已过时,还是建议大家不要使用,按照官方提示来吧。

了解一下public final void stop()。

线程组

线程组表示一个线程的集合。线程组也可以是包含其他线程组。线程组构成一棵树,每个线程组都有一个父线程组。

ThreadGroup类表示一个线程组,构造函数如下:

ThreadGroup(String name):构建一个名字为name的新线程组

ThreadGroup(ThreadGroupparent,String name):创建一个名字为name的新线程组,它的父线程组为parent。

为啥要用线程组哦?

因为方便控制,只需要单个命令即可完成对整个线程组的操作。有种号令全军如沐春风的感觉。

线程组常用方法如下:

int getMaxPriority():返回此线程组的最高优先级

String getName():返回此线程组的名称

ThreadGroup getParent():返回此线程组的父线程组

void interrupt:中断此线程组中的所有线程

boolean isDaemon():测试此线程组是否为一个守护线程线程组

boolean isDestroyed:测试此线程组是否已经被摧毁

void setDaemon(bollean daemon):设置组里的线程都为守护线程

void setMaxPriority(int pri):设置线程组的最高优先级

getThreadGroup():返回该线程所属的线程组

老习惯,例子:

294438f0d412

线程组实例

输出:

线程组名是:我的线程组

线程名是:Thread-0

th2线程的优先级为:5

我负责疯狂输出

我负责疯狂输出

我负责疯狂输出

我负责疯狂输出

我负责疯狂输出

我负责疯狂输出

我负责疯狂输出

我负责疯狂输出

我负责疯狂输出

我负责疯狂输出

我负责疯狂输出

我负责疯狂输出

线程同步

如果涉及多个线程访问同一个数据的情况,就容易出现问题。比如一个int型数组int[] a={2,1,4,3},如果线程a对它升序操作,另一个线程b对它降序操作。两个线程同时运行,a线程刚刚把它的2和1排好,正好发生了时间片轮换,就是这么突然狗屎运,b线程得到CPU时间片,马上把4和3放到前面。这样a线程和b线程访问共享的数据a,就会出现结果不正确的情况,这很尴尬。

一个卖书的例子:

294438f0d412

卖书

输出结果:

第10本卖出者: tom

第10本卖出者:Third

第10本卖出者:线程1

第7本卖出者: tom

第6本卖出者:Third

第5本卖出者:线程1

第5本卖出者: tom

第4本卖出者:Third

第3本卖出者: tom

第3本卖出者:线程1

第1本卖出者: tom

第2本卖出者:Third

这个运行结果表明数据出现异常,很是尴尬。我们如何解决这个问题呢?

当然伟大的Java已经为我们考虑好了,Java语言设计了同步机制来保护数据。在任何一个Java对象上,都有一个锁标志。synchronized关键字和对象的锁标志配合完成数据的保护。

就像这个样子去保护:

294438f0d412

synchronized

sysnchronized包围的代码叫关键代码,当线程运行到关键代码处,首先要到o对象上去取得o对象的锁标志,然后才能执行代码。但是锁标志只有一个,线程取得锁标志后,运行关键代码,只有从关键代码离开时,才会归还锁标志。如果线程没有运行完成关键代码,锁标志不会归还。其他线程再次运行到synchronized处,无法从o上取得锁标志,就无法执行关键代码,只能静静的等待锁标志的归还。

咱来改造一下上面的卖书的案例,看是不是如我们所愿解决好问题。

294438f0d412

卖书的案例

输出如下:

第10本卖出者:线程1

第9本卖出者:线程1

第8本卖出者:线程1

第7本卖出者:线程1

第6本卖出者:线程1

第5本卖出者:线程1

第4本卖出者:线程1

第3本卖出者:线程1

第2本卖出者:线程1

第1本卖出者:线程1

上面只看到线程1是因为案例计算复杂度小,不信你可以把i修改成100试试,三个线程就都能看到辣。

上面的代码还可以再优化一下写法,因为:

public synchronized void sell(){}它就相当于如下代码:

public void sell()

{

synchronized(this)

{

}

}

这样写代码可以更清晰,便于理解,上面的SellBook就可以这样写了:

294438f0d412

SellBook案例升级

线程通信

当然,在synchronized关键代码中,也可以主动放弃对象的锁标志。就是通过wait()方法做到这一点。线程放弃锁标志后,线程进入了阻塞状态。

所有的Java对象都有一个wait池,每个池都可以容纳线程。wait()与notify()方法都有是Object中的方法。当线程执行了wait()方法后,线程就释放对象的锁标志并进入该对象的wait池中等待。直到notify()通知后才能运行。

wait()方法

wait()方法关键点:

wait()方法是Object对象的方法,不是Thread类的方法

wait()方法只可能在synchronized块中被调用

wait()方法被调用时,原来的锁对象释放锁,线程进入block状态

wait()被notify()唤醒的线程从wait()后面的代码开始继续执行

notidy()方法

notidy()用来唤醒正在等待的线程,使线程可以重新运行。被唤醒的线程从当时wait候的代码开始执行,但是因为其wait时已经释放了锁标志,所以必须重新获得锁标志。

notidy()方法关键点:

只能在sysnchronized中被调用,即先获得对象锁标志。

notify()方法唤起锁标志对象的等待池中的一个线程。但是,如果有几个线程在等待列表中,它无法决定哪个线程被唤醒。调用notifyAll()方法可以让所有的等待线程被唤醒。

使用notify()唤醒wait()的例子:

294438f0d412

notify()唤醒wait()

notidyAll()方法

notidy()方法注意事项:

只能在synchronized中被调用,即先获得对象锁标志

notidy()方法唤起锁标志所属对象的等待池中的所有的等待线程

Timer和TimerTask

Timer是一种定时器工具。它可以用来启动TimerTask来执行任务一次或者反复多次。

TimerTask是一个抽象类,表示一个可以被Timer执行的定时器任务。它实际上是一种特殊的线程,是Timer来定时启动执行一次任务或者重复执行某个任务。

TimerTask类本身没有实现run()方法,其run()方法由子类实现。

Timer类常用方法如下:

void schedule(TimerTask task,Date time):安排在指定的时间执行执行的任务

void schedule(TimerTask task,Date firstTime,long period):安排指定的任务在指定的时间开始进行重复的执行

void schedule(TimerTask task,long delay):安排在指定延迟后执行指定的任务

void wchedule(TimerTask task,long delay,long priod):安排指定的任务从指定的延迟后开始进行重复的固定延迟执行

很普通的例子:

294438f0d412

Timer案例

死锁

死锁就是所有的线程都无法运行,整个程序处于阻塞状态,并且不可以恢复到运行状态。一旦发生死锁,线程就没有运行的意义了。

两个线程互相拿到了对方需要的资源,此时两个线程都不会放弃自己已经拿到的资源,这时程序就无法继续运行下去,死锁就出现了。我们写程序要注意防止死锁情况的出现。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值