博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java——线程
阅读量:3966 次
发布时间:2019-05-24

本文共 12162 字,大约阅读时间需要 40 分钟。

Java——线程


本人是个新手,写下博客用于自我复习、自我总结。

如有错误之处,请各位大佬指出。
参考教材:零基础学Java


线程具有提高执行速度的特点,在应用程序中的使用非常广泛。

在说线程之前,先介绍什么是进程和程序。

程序是计算机指令的集合,它以文件形式存储在磁盘上,
而进程就是一个执行中的程序,每一个进程都有其独立的内存空间和系统资源。
也就是说,进程就是一个运行的程序,windows操作系统是支持多进程的操作系统,即同一时间可以执行多个程序,每个程序是在自己独立的内存空间内,使用自己被分配到的系统资源。其实,这种说法并不准确,一个CPU在某个时刻,实际上只能运行一个程序,即一个进程。所谓的支持多进程,其实就是CPU在交替轮流执行多个程序。

说回线程,线程是CPU调度和分配的基本单位,一个进程可以由多个线程组成,而这多个线程共享同一个存储空间,这使得线程间的通信比较容易。在一个多进程的程序中,如果要切换到另一个进程,需要改变地址空间的位置。然后在多线程的程序中,就不会出现这种情况,因为它们位于同一个内存空间内,只需改变运行的顺序即可。而多线程就是指单个程序可通过同时运行多个不同的线程,以执行不同的任务。所谓同时,也要依据CPU。如果是多个CPU,则并发运行。如果是一个CPU,则根据系统具体情况,执行多个线程。

1)创建线程

①通过Runnable接口的方式创建线程

在JAVA中,线程是一种对象,但不是所有的对象都可以称为线程,只有实现了Runnable接口的类,才可以称为线程。

定义:

public interface Runnable{
public abstract void run();}

Runnable接口只有一个抽象方法run(),要实现这个接口,只要实现这个抽象方法就可以。只要实现了这个接口的类,才有资格称为线程。

创建线程的结构:Thread t=new Thread(runnable 对象);

runnable 对象是指实现了Runnable接口类的对象。当线程执行时,runnable对象中的run()方法会被调用,如果想要运行上面创建的线程,还需要调用一个Thread类的方法:t.start();

例:

public class ThreadTest{
public static void main(String args[]){
//创建对象c和c1 compute c=new compute(); compute1 c1=new compute1(); //创建线程对象t和t1 Thread t=new Thread(c); Thread t1=new Thread(c1); t.start(); //启动线程对象t t1.start(); //启动线程对象t1 }}//创建通过循环语句输出数字的类class compute implements Runnable{
public void run(){
for(int i=0;i<10;i++){
System.out.println(i); } }}//创建通过循环语句输出数字的类class compute1 implements Runnable{
public void run(){
for(int i=0;i<10;i++){
System.out.println("这个数字是:"+i); } }}

这个程序段中,创建了两个线程,不过经过多次运行后可以发现,结果可能会不同。不同的原因:程序具体运行时存在一个执行顺序的问题。在JAVA中,线程通常是通过调度模式来执行的。所谓抢占式调度模式是指,许多线程处于可以运行状态,即等待状态,但实际上只有一个线程在运行。该线程一直运行到它终止,或者另一个具有更高优先级变成可运行状态。在后一种情况下,低优先级线程被高优先级线程抢占运行机会。(线程的抢占方式可以控制,本程序段并未对其进行控制)

②通过继承Thread类来创建线程

其实Thread类本身也实现了Runnable接口,所以只要让一个类继承Thread类,并覆盖run()方法,也会创建线程。

例:

public class ThreadTest1{
public static void main(String args[]){
//创建对象t和t1 compute t=new compute(); compute1 t1=new compute1(); //启动线程 t.start(); t1.start(); } } //创建通过循环语句输出数字的类 class compute extends Thread{
public void run(){
for(int i=0;i<10;i++){
System.out.println(i); } } } //创建通过循环语句输出数字的类 class compute1 extends Thread{
public void run(){
for(int i=0;i<10;i++){
System.out.println("这个数字是:"+i); } } }

2)线程的状态

在JAVA中线程的执行过程比较复杂,但线程对象创建后并不是立即执行,需要做些准备工作才有执行的权利,而一旦抢占到CPU周期,则线程就可以运行,但CPU周期结束则线程必须暂时停止,或线程执行过程中的某个条件无法满足时也会暂时停止,只有等待条件满足时才会继续执行,最后从run()方法返回后,线程退出。也就是说,线程的执行过程中涉及一些状态,线程就在这些状态之间迁移。

JAVA规范中定义了线程的4种状态:新建状态、可运行状态、阻塞状态、死亡状态。 (为了更好理解,将线程分为5个状态:新建状态、就绪状态、运行状态、阻塞状态、死亡状态)

①新建状态:

线程对象(通过new关键字)已经建立,在内存中有一个活跃的对象,但是没有启动该线程,所以它仍然不能做任何事情,此时线程处在新建状态,程序没有运行线程中的代码,如果线程要运行需要处于就绪状态。

②就绪状态:

一个线程一旦调用了start()方法,该线程就处于就绪状态。此时线程等待CPU时间片,一旦获得CPU时间周期线程就可以执行。这种状态下的任何时刻线程是否执行完全取决于系统的调度程序。

③运行状态:

一旦处于就绪状态的线程获得CPU执行周期,就处于运行状态,执行多线程代码部分的运算。线程一旦运行,只是在CPU周期内获得执行权利,而一旦CPU的时间片用完,操作系统会给其他线程运行的机会,而剥夺当前线程的执行。在选择哪个线程可以执行时,操作系统的调度程序考虑线程的优先级。

④阻塞状态:

该状态下线程无法运行,必须满足一定条件后才可以执行.如果线程处于阻塞状态,JVM的调度机不会为其分配CPU周期。而一旦线程满足一定的条件就解除阻塞,线程处于就绪状态,此时就获得了被执行的机会。当发生以下情况时会使得线程进入阻塞状态。

1、线程正等待一个输入、输出操作,该操作完成前不会返回其调用者。
2、线程调用了wait()方法或sleep()方法。
3、调用了线程的suspend()方法,该方法已经不推荐使用。
4、线程需要满足某种条件才可以继续执行。

也就是说,如果线程处于阻塞状态,别的线程就可以被CPU执行,而当一个线程解除阻塞状态,如线程等待输入、输出操作而该操作已经完成,线程调度就会检查该线程的优先权,如果优先权高于当前的运行线程(就绪状态的线程和运行状态的线程)该线程将抢占当前线程的资源并开始运行。

⑤死亡状态:

线程一旦退出run()方法就处于死亡状态。在java2中通过调用stop()和destroy()方法使得线程死亡,但这些方法都引起程序的不稳定,不建议使用。

3)线程的优先级

线程的执行顺序是一种抢占方式,优先级高的比优先级低的要获得更多的执行时间,如果想让一个线程比其他线程有更多的时间运行,可以通过设置线程的优先级解决。如用setPriority()方法:

public final void setPriority(int newPriority);

newPriority是一个1-10的正整数,数值越大,优先级别越高,

如 1:最低优先级
10:最高优先级
5:默认优先级

例:

public class ThreadTest2{
public static void main(String args[]){
//创建对象t和t1 compute t=new compute(); compute1 t1=new compute1(); //设置优先级 t.setPriority(10); t1.setPriority(1); //启动线程 t.start(); t1.start(); } } //创建通过循环语句输出数字的类 class compute extends Thread{
public void run(){
for(int i=0;i<10;i++){
System.out.println(i); } } } //创建通过循环语句输出数字的类 class compute1 extends Thread{
public void run(){
for(int i=0;i<10;i++){
System.out.println("这个数字是:"+i); } } }

4)线程的休眠与唤醒

线程的休眠,是指线程暂时处于等待的一种状态,也就是说,线程暂时停止了。实现休眠功能需要调用Thread类的sleep()方法。sleep()方法可以使线程在指定的时间,处于暂时停止的状态,等到指定时间结束后,暂时停止状态就会结束,然后继续执行没有完成的任务。sleep()方法结构如:

public static native void sleep(long millis) throws interruptedException
millis参数是指线程休眠的毫秒数。上述代码涉及抛出异常的问题。

例:

public class ThreadTest3{
public static void main(String args[]){
//创建对象t和t1 compute t=new compute(); compute1 t1=new compute1(); //启动线程 t.start(); t1.start(); } } //创建通过循环语句输出数字的类 class compute extends Thread{
public void run(){
for(int i=0;i<10;i++){
System.out.println(i); try{
sleep(1000); //线程休眠1秒 }catch(Exception e){
} } } } //创建通过循环语句输出数字的类 class compute1 extends Thread{
public void run(){
for(int i=0;i<10;i++){
System.out.println("这个数字是:"+i); try{
sleep(1000); //线程休眠1秒 }catch(Exception e){
} } } }

由例可知,虽然理论结果是第一个线程休眠时,第二个线程输出。第二个线程休眠时,第一个线程输出。但经过一段时间后,仍会有不同的现象出现。将第二个线程休眠时间变为第一个线程休眠时间的二倍,输出结果也不一定得到预期。

线程的唤醒,是指使线程从休眠等待状态进入可执行状态,可以通过调用interrupt()方法实现。

例:

public class ThreadTest4{
public static void main(String args[]){
//创建对象t和t1 compute t=new compute(); //启动线程 t.start(); //线程唤醒 t.interrupt(); } } //创建一个线程类,通过休眠输出不同结果。 class compute extends Thread{
public void run(){
System.out.println("在工作中,不要打扰"); try{
sleep(1000000); //线程休眠1000秒 }catch(Exception e){
System.out.println("噢,电话来了"); } } }

也就是说,用了interrupt()方法后,无论休眠多少毫秒,线程都立刻被唤醒。

5)线程让步

线程让步,就是使当前正在运行的线程对象退出运行状态,让其他线程运行,该功能可调用yield()方法实现。这个方法不能将运行权让给指定的线程,只是允许这个线程把运行权让出来,至于给谁,需要看哪个线程可以抢占到。

例:

public class ThreadTest5{
public static void main(String args[]){
//创建对象t和t1 compute t=new compute(); compute1 t1=new compute1(); //启动线程 t.start(); t1.start(); } } //创建通过循环语句输出数字的类 class compute extends Thread{
public void run(){
for(int i=0;i<10;i++){
System.out.println(i); yield(); //线程暂停 } } } //创建通过循环语句输出数字的类 class compute1 extends Thread{
public void run(){
for(int i=0;i<10;i++){
System.out.println("这个数字是:"+i); } } }

虽然第一个线程放弃运行权,但是也仍有可能得不到想要的结果,但第一个线程肯定会比第二个线程运行的几率要小,因为它总是放弃运行权。

6)线程同步

目前可知,线程的运行权通过抢占的方式获得,那么当一个程序运行到一半时,突然被另一个线程抢占了运行权,此时这个线程数据处理到一半,而另一个线程也正在处理这个数据,那么就会出现重复操作数据的现象,最终导致整个系统的混乱。而实现同步有两种方法:同步块和同步化方法

①同步块:

同步块是使具有某个对象监视点的线程,获得运行权限的一种方法,每个对象只能在拥有这个监视点的情况下,才能获得运行权限。

同步块的结构如下:

synchronized(someobject)     {
代码段 }

someobject是一个监视点对象,可以是实际存在的,也可以是假设的。在很多程序段中,这个监视点对象都是假设的。其实这个监视点就相当于一把锁,给一个线程上了锁,那么其他线程就会被拒之门外,就无法得到这把锁。直到这个线程执行完了,才会将这个锁交给其他线程。其他的线程得到锁后,将自己的程序锁住,再将其他线程拒之门外。

例:

public class ThreadTest6{
public static void main(String args[]){
//创建对象t和t1 compute t=new compute('a'); compute t1=new compute('b'); //启动线程 t.start(); t1.start(); } } //创建通过循环语句输出数字的类 class compute extends Thread{
char ch; static Object obj=new Object(); //创建静态对象obj compute(char ch){
this.ch=ch; } public void print(char ch){
for(int i=0;i<10;i++){
System.out.println(ch); } } public void run(){
synchronized(obj){
for(int i=1;i<10;i++){
print(ch); System.out.println(); } } } }

由例可知,在运行程序中添加一个监视点,那么将锁内的程序段执行完后,就会自动打开锁,再由另外几个线程抢占这个锁,然后反复执行这个同步块中的程序,这样一个程序执行完后,才会执行另一个线程。对于多线程操作同一个数据,就不会出现混乱的现象。

②同步化方法:

同步化方法就是对整个方法进行同步。结构如下:

synchronized void f()     {
代码 }

例:

public class ThreadTest7{
public static void main(String args[]){
//创建对象t compute t=new compute(); //启动3个线程对象 new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } } //创建通过循环语句输出数字的类 class compute extends Thread{
int i=10; static Object obj=new Object(); //创建静态对象obj synchronized void print(char ch){
System.out.println(Thread.currentThread().getName()+":"+i); i--; } public void run(){
while(i>0){
print(); try{
sleep(1000); }catch(Exception e){
} } } }

7)进程的死锁和饥饿

由于比较复杂,这里只简单介绍。

当一个或多个进程,在一个给定的任务中,协同作用、互相干涉,而导致一个或者更多进程永远等待下去,死锁就发生了。当一个进程永久性地占有资源,使得其他进程得不到该资源,饥饿就发生了

8)多线程的死锁问题

对于JAVA语言,没有预防死锁的方法,只有依靠编程者谨慎的设计来避免死锁的发生。但有避免死锁的基本原则。

①避免使用suspend()和resume()方法,这些方法具有与生俱来产生死锁的缺点。
②不要对长时间I/O操作的方法施加锁。
③使用多个锁时,确保所有线程都按相同的顺序获得锁。

9)多线程的缺点

多线程的主要目的是对大量并行的任务进行有序的管理。通过同时执行多个任务,可以有效地利用计算机的资源,提高CPU的利用率,或者实现对用户来讲响应及时的程序界面。但多线程也会有缺点,主要包括:

①等待访问共享资源时使得程序运行变慢。
②当线程数量增多时,对线程的管理要求额外的CPU开销。
③死锁难以避免,需要谨慎设计多线程程序。
④随意使用线程技术有时会耗费系统资源,所以程序员要知道何时使用多线程,以及何时避免使用该技术。

实例:

1个汉堡包店,有2个厨师和2个营业员,厨师负责做汉堡,营业员负责卖汉堡,当然还有1个存放汉堡的箱子。厨师不停地做汉堡,做好了就放在箱子里,当每次客人来的时候,营业员就从箱子里取出1个汉堡卖掉。假设前提是客人每隔1秒来1个,也就是说营业员1秒卖1个汉堡,而厨师每3秒做1个汉堡。目前总共只能做10个汉堡,箱子中已经有了5个汉堡,请编写程序代码来显示这个买卖关系。

public class ThreadTest {
public static void main(String args[]){
hmaker1 maker1=new hmaker1(); hmaker2 maker2=new hmaker2(); hassistant assistant1=new hassistant(); hassistant assistant2=new hassistant(); maker1.start(); maker2.start(); assistant1.start(); assistant2.start(); }}class ham //创建把汉堡包的箱子作为监视器类{
//创建对象box1和box2 static Object box1=new Object(); static Object box2=new Object(); //关于制作汉堡包的材料属性totalmaterial1和totalmaterial2 static int totalmaterial1=10; static int totalmaterial2=10; //关于销售多少个汉堡包属性 static int sales11=0; static int sales12=0; static int sales21=0; static int sales22=0; //关于一共有多少个汉堡包属性production1和production2 static int production1=5; static int production2=5;}class hmaker1 extends Thread //第一个厨师线程类{
//使用同步块,在这个函数里会不断地生产汉堡包 public void make(){
synchronized (ham.box1) {
(ham.production1)++; System.out.println("厨师甲:汉堡包来了(总共"+ (ham.production1-ham.sales11-ham.sales12)+ "个A类汉堡包)"); try{
ham.box1.notify(); }catch(Exception e){
} } } public void run(){
//使用循环语句来保证在汉堡包材料用完之前,不断地生产汉堡包 while(ham.production1
(ham.sales11+ham.sales12)){
ham.sales11++; ham.sales21++; System.out.println("营业员甲:顾客好,汉堡包上来" + "了,(总共卖了A类汉堡包"+ham.sales11 +"个,总共卖了B类汉堡包"+ham.sales21+ "个)"); } } } public void sell2(){
//创建营业员卖汉堡包的方法 //当没有汉堡包的时候 if(ham.production2==(ham.sales21+ham.sales22)){
System.out.println("营业员乙:顾客朋友们,请稍微" + "等一下,B汉堡包没了!"); ham.sales21=0; ham.sales22=0; ham.production2=0; try{
ham.box2.wait(); }catch(Exception e){
} } else{
if(ham.production2>(ham.sales21+ham.sales22)){
ham.sales12++; ham.sales22++; System.out.println("营业员乙:顾客好,汉堡包上来" + "了,(总共卖了A类汉堡包"+ham.sales12 +"个,总共卖了B类汉堡包"+ham.sales22+ "个)"); } } } public void run(){
//当盒子里有汉堡包的情况下不断地卖 while((ham.sales12+ham.sales11)

转载地址:http://yxyki.baihongyu.com/

你可能感兴趣的文章
Android 发布到google Play的app搜索不到问题的解决
查看>>
Flutter 网络请求之基于dio的简单封装
查看>>
Flutter UI基础 - 路由之Navigator详解
查看>>
Flutter UI基础 - Widgets 之 InkWell 和 Ink
查看>>
Spring - sentinel和hystrix比较
查看>>
MySQL - 索引之B+树
查看>>
Spring - Dubbo的底层实现原理和机制
查看>>
Flutter Dio引入和简单的Get/Post请求
查看>>
Flutter Dart 和 Flutter json转实体类(插件自动生成)
查看>>
Flutter 路由跳转fluro
查看>>
Flutter 日期插件date_format 中文 国际化 及flutter_cupertino_date_picker
查看>>
Flutter 插件笔记 | 屏幕适配 flutter_screenutil
查看>>
Flutter UI基础 - 侧拉抽屉菜单
查看>>
Flutter UI基础 - AppBar中标题文字如何居中
查看>>
Flutter UI基础 - Drawer 抽屉视图与自定义header
查看>>
Flutter UI基础 - 点击展开和关闭
查看>>
Flutter UI基础 - GridView
查看>>
Flutter UI - 打造一个圆形滑块(Slider)
查看>>
Flutter UI基础 - 分割线效果实现
查看>>
Flutter UI基础 - DecoratedBox组件
查看>>