多线程(一)

线程简介

进程:每个进程都有独立的代码和数据空间,进程之间的切换会有较大的开销,一个进程包含1-n个线程。进程是资源分配的最小单位。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。线程是cpu调度的最小单位。
线程就是进程中一个负责程序执行的控制单元(执行路径)。一个进程中可以有多个执行路径,称之为多线程。
一个进程中至少要有一个线程,开启多个线程是为了同时运行多部分代码。每一个线程都有自己运行的内容,这个内容可以称之为线程要执行的任务。
多线程的好处:解决了多部分同时运行的问题。
多线程的弊端:线程太多会导致效率降低。
应用程序的执行都是CPU在做着快速的切换完成的,这个切换是随机的。
JVM启动时就启动了多个线程,至少有两个线程可以分析出来:
1,执行main函数的线程,该线程的任务代码都定义在main函数中
2,负责垃圾回收的线程,该线程的任务代码都在垃圾回收器中,在内部自动执行。
finalize():是Object类的方法。当垃圾回收器确定不存在该对象的更多引用时,由对象的垃圾回收器调用此方法。子类重写finalize方法,以配置系统资源或者执行其他清除。
System类中有一个gc()方法,功能是运行垃圾回收器,它不是即时启动垃圾回收器的,只是告诉它应该启动。
例一:

class Demo extends Object
{
    public void finalize()
    {
        System.out.println("demo ok");
    }
}   

class ThreadDemo
{
    public static void main(String[] args)
    {
        new Demo();
        new Demo();
        System.gc();
        new Demo();
        System.out.println("hello world!");
    }
}

运行后输出:
hello world!
demo ok
demo ok
出现这样的结果是因为此程序是由两个线程完成的。输出结果不是一定的,因为两个线程的执行顺序不一定。(线程随机切换)
主线程结束,虚拟机不一定结束,还有其他线程在执行。

实现线程的两种方式

在java中主要提供两种方式实现线程,分别继承java.lang.Thread类与实现java.lang.Runnable接口

继承Thread类

Thread类中常用的两个构造方法:

public Thread():创建一个新的线程对象
public Thread(String threadName):创建一个名称为threadName的线程对象。

完成线程的真正功能的代码放在类的run方法中,当一个类继承Thread类后,就可以覆盖run方法,将实现该线程功能的代码写入run()方法中。
步骤:
1,定义一个类继承Thread类
2,覆盖Thread类中的run方法
3,直接创建Thread子类对象创建线程
4,调用start方法开启线程并调用线程的任务run方法执行

可以通过Thread的getName获取线程的名称 Thread-编号(从0开始),线程一创建就带着编号,获取当前线程对象的方法为currentThread()
主线程的名字是main。

创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。而运行的指定代码就是这个执行路径的任务。
jvm创建的主线程的任务都定义在了主函数中。
而自定义的线程它的任务在哪?
Thread类用于描述线程,线程是需要任务的。所以Thread类也对任务的描述。这个任务就通过Thread类中的run方法来实现。也就是说,run方法就是封装自定义线程运行任务的函数。run方法中定义的就是线程要运行的任务代码。
开启线程是为了运行指定代码,所以只要继承Thread类,并复写run方法。
将运行的代码定义在run方法当中。
例二:

class Demo extends Thread
{
    private String name;
    Demo(String name)
    {
        super(name);//线程类的带参数的构造方法
    }
    public void run()
    {
        for(int x=0;x<10;x++)
            System.out.println(name+"...x="+x+"...name="+Thread.currentThread().getName());
    }
}
class ThreadDemo2
{
    public static void main(String[] args)
    {
        Demo d1=new Demo("wangwang");
        Demo d2=new Demo("miaomiao");

        d1.start();//开启线程,调用run方法
        d2.start();
        System.out.println("over");
    }
}

输出为:

over
null...x=0...name=wangwang
null...x=0...name=miaomiao
null...x=1...name=wangwang
null...x=1...name=miaomiao
null...x=2...name=miaomiao
null...x=3...name=miaomiao
null...x=4...name=miaomiao
null...x=2...name=wangwang
null...x=5...name=miaomiao
null...x=3...name=wangwang
null...x=6...name=miaomiao
null...x=4...name=wangwang
null...x=7...name=miaomiao
null...x=5...name=wangwang
null...x=8...name=miaomiao
null...x=6...name=wangwang
null...x=9...name=miaomiao
null...x=7...name=wangwang
null...x=8...name=wangwang
null...x=9...name=wangwang

开始的name为空是因为开始的时候name没有初始化的值,默认为null。

直接调用run方法和调用start方法有什么区别?
run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用; 直接调用run()和其他方法的调用没任何不同,main方法会按顺序执行它。
start()方法:启动一个线程,调用该线程对象的run()方法,不能多次启动一个线程;如果start()方法调用一个已经启动的线程,系统将抛出IllegalThreadStateException异常。调用start方法,会告诉JVM去分配本机系统的资源,才能实现多线程。

实现Runnable接口

如果需要继承其他类(非继承类),而且还要使当前类实现多线程,就不能再继承Thread类,因为java语言中不支持多继承,这时候可以通过实现Runnable接口使其具有线程的功能。
实现Runnable接口的程序会创建一个Thread类,并将Runnable对象与Thread对象相关联。Thread类中有以下两个构造方法:

public Thread(Runnable target)
public Thread(Runnable target,String name)

步骤:
1,定义类实现Runnable接口;
2,覆盖接口中的run方法,将线程的任务代码封装到run方法中;
3,通过Thread类方法创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递;
这是因为线程的任务都封装在Runnable接口子类对象的run方法中,
所以要在运行线程对象创建时就必须明确要运行的任务
4,调用线程对象的start方法开启线程。

实现Runnable接口的好处:
1,将线程的任务从线程的子类中分离出来,进行了单独的封装
按照面向对象的思想将任务的封装成对象;
2,避免了java单继承的局限性。
所以,创建线程的第二种方式较为常用。
例三:

class Demo implements Runnable//准备扩展Demo类的功能,让其内容可以作为线程的任务执行
{                        //通过接口的形式来实现
    public void run()
    {
        show();
    }
    public void show()
    {
        for(int x=0;x<20;x++)
        {
            System.out.println(Thread.currentThread().getName()+"..."+x);
        }
    }
}

class ThreadDemo
{
    public static void main(String[] args)
    {
        Demo d=new Demo();
        Thread t1=new Thread(d);
        Thread t2=new Thread(d);
        t1.start();
        t2.start();
    }
}

线程的生命周期

线程具有生命周期,其中包括7中状态:出生状态,运行状态,等待状态,休眠状态,阻塞状态和死亡状态。其中等待状态和休眠状态可以统称为冻结状态。
出生状态就是线程被创建时处于的状态,在用户使用该线程实例调用start()方法之前线程都处于出生状态;当用户调用start方法后,线程处于就绪状态(可执行状态,具有执行资格),当线程得到系统资源后进入运行状态(具有执行权)。
一旦进入可执行状态,它就会在就绪和运行状态下转换(抢夺系统资源,即执行权),同时也可能进入等待、休眠、阻塞或者死亡状态。当处于运行状态的线程调用Thread类中的wait()方法时,该线程便进入等待状态,进入等待状态的线程必须调用Thread类中的notify()方法或者notifyAll()方法才能被唤醒;当线程调用Thread类中的sleep()方法的时候,就会进入休眠状态,休眠时间结束会自动醒来。如果一个线程在运行状态发出输入/输出请求,该线程将进入阻塞状态,在输入输出结束时线程会进入就绪状态。当run()方法执行完毕,线程进入死亡状态。
线程的声明周期状态图
这里写图片描述
这里写图片描述

CPU的执行资格:可以被CPU处理,在处理队列中排队。
CPU的执行权:正在被CPU处理。
运行:具备执行资格,具备执行权。
冻结:释放执行权的同时释放执行资格。
start()方法的调用并不是立即执行多线程代码,而是使得该线程变成可运行状态,什么时候运行是由操作系统决定的。

操作线程的方法

线程的休眠

sleep()方法需要一个参数用于指定该线程休眠的时间,该时间以毫秒为单位。它通常在run方法内的循环中被使用。
sleep方法的执行可能会抛出InterruptedException异常,所以将sleep()方法放在try-catch块中。
wait和sleep区别:
1,wait可以指定时间,也可以不指定
sleep必须指定时间
2,在同步中时,对CPU的执行权和锁的处理不同
wait:释放执行权,释放锁
sleep:释放执行权,不释放锁

线程的加入

当某个线程使用join()方法加入到另外一个线程时,另一个线程会等待该线程执行完毕后继续执行。

线程的礼让

Thread方法提供了一种礼让方法,使用yield方法表示,它只是给当前正处于运行状态的线程一个提醒,告知它可以将资源礼让给其他线程。
例四:join()方法和yield()方法:

class Demo implements Runnable
{
    public void run()
    {
        for(int x=0;x<50;x++)
        {
            System.out.println(Thread.currentThread().getName()+"..."+x);
            Thread.yield();//释放执行权
        }
    }
}

class JoinDemo
{
    public static void main(String[] args) throws Exception
    {
        Demo d=new Demo();

        Thread t1=new Thread(d);
        Thread t2=new Thread(d);

        t1.start();
        //t1.join();//t1线程要申请加入进来运行。此时主线程会释放执行权和执行资格,等t1执行完终止后才会接着运行。
        t2.start();
        t2.setPriority(Thread.MAX_PRIORITY);//线程优先级
        t1.join();//主线程等待t1运行完再运行。放在这里,t1,t2会交替抢夺执行权
        for(int x=0;x<50;x++)
        {
            System.out.println(Thread.currentThread().getName()+"..."+x);
        }
    }
}

线程的中断

停止线程的方法:
1,stop方法,已过时;
2,run方法结束。

怎么控制线程的任务结束?
任务中都会有循环结构,只要控制住循环,就可以结束任务。
控制循环通常就用定义标记来完成:在run()方法中使用无线循环,然后使用一个布尔型标记控制循环的停止。或者直接定义其他标记控制循环。

但是如果线程处于了冻结状态,无法读取标记,如何结束?
可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格,
但是强制动作会发生了InterruptedException,记得要处理。
例五:

//未处于冻结状态
class StopThread implements Runnable
{
    private boolean flag=true;
    public synchronized void run()
    {
        while(flag)
        {
                System.out.println(Thread.currentThread().getName()+"..."+e);
        }
    }
    public void setFlag()
    {
        flag=false;
    }   
}

class StopThreadDemo 
{
    public static void main(String[] args)
    {
        StopThread st=new StopThread(); 

        Thread t1=new Thread(st);
        Thread t2=new Thread(st);

        t1.start();
        t2.start();

        int num=1;
        for(;;)
        {
            if(++num==50)
            {
                st.setFlag();//没有冻结发生时,可以直接定义标记来结束任务
                break;
            }
            System.out.println("main.."+num);
        }
        System.out.println("over");
    }
}

例六:

//处于冻结状态
class StopThread implements Runnable
{
    private boolean flag=true;
    public synchronized void run()
    {
        while(flag)
        {
            try
            {
                wait();
            }catch(InterruptedException e){
                System.out.println(Thread.currentThread().getName()+"..."+e);
                flag=false;
            }
            System.out.println(Thread.currentThread().getName()+"...");
        }
    }
    public void setFlag()
    {
        flag=false;
    }
}

class StopThreadDemo 
{
    public static void main(String[] args)
    {
        StopThread st=new StopThread();

        Thread t1=new Thread(st);
        Thread t2=new Thread(st);

        t1.start();
        t2.start();

        int num=1;
        for(;;)
        {
            if(++num==50)
            {
                //st.setFlag();
                t1.interrupt();
                t2.interrupt();

                break;
            }
            System.out.println("main.."+num);
        }
        System.out.println("over");
    }
}

守护进程

setDeamon(boolean on):将该线程标记为守护进程或用户线程,可以理解为后台线程,当前台线程结束时,后台线程会自动结束。
例七:

class StopThread implements Runnable
{
    private boolean flag=true;
    public synchronized void run()
    {
        while(flag)
        {
            try
            {
                wait();
            }catch(InterruptedException e){         System.out.println(Thread.currentThread().getName()+"..."+e);
                flag=false;
            }       System.out.println(Thread.currentThread().getName()+"...");     
        }
    }
    public void setFlag()
    {
        flag=false;
    }   
}

class StopThreadDemo 
{
    public static void main(String[] args)
    {
        StopThread st=new StopThread();
        Thread t1=new Thread(st);
        Thread t2=new Thread(st);
        t1.start();
        t2.setDaemon(true);//t2设置为守护线程或用户线程,可以理解为后台线程,当前台线程结束时,后台线程会自动结束
        t2.start();
        int num=1;
        for(;;)
        {
            if(++num==50)
            {
                t1.interrupt();
                break;
            }
            System.out.println("main.."+num);
        }
        System.out.println("over");
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值