java线程的三种创建方式及对比/多线程的常用操作方法/线程的五种状态

进程与线程

进程:操作系统中一个程序的执行周期称为一个进程

线程:一个程序同时执行多个任务。通常,每一个任务就称为一个线程。与进程相比较,线程更”轻量级”,创建、撤销一个线程比启动一个新进程开销要小的多。没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。

多进程与多线程区别:本质区别在于,每个进程拥有自己的一整套变量,而线程则共享数据。共享变量使得线程之间的通信比进程之间通信更有效、更方便

线程状态

这里写图片描述

Java多线程实现

1、继承Thread类实现多线程
java.lang.Thread是一个线程操作的核心类。新建一个线程最简单的方法就是直接继承Thread类,而后覆写该类中的run()方法(就相当于主类中的main方法)
例:定义线程的主体

class MyThread extends Thread { // 线程主体类
    private String title ;
    public MyThread(String title) {
        this.title = title;
    }
    @Override
    public void run() { // 所有线程从此处开始执行
        for (int i = 0; i < 10 ; i++) {
            System.out.println(this.title+",i = " + i);
        }
    }
}

当现在有了线程的主体类之后,很自然我们就会想到产生线程类的实例化对象而后调用run()方法。实际上,我们不能够直接去调用run()方法。
例::观察调用run()方

public class TestDemo {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("thread1") ;
        MyThread myThread2 = new MyThread("thread2") ;
        MyThread myThread3 = new MyThread("thread3") ;
        myThread1.run();
        myThread2.run();
        myThread3.run();
    }
}

这个时候只是做了一个顺序打印,和多线程一点关系都没有。正确启动多线程的方式是调用Thread类中的start()方法。
启动多线程:public synchronized void start()此方法会自动调用线程的run()方法。
例:正确的线程启动

public class TestDemo {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("thread1") ;
        MyThread myThread2 = new MyThread("thread2") ;
        MyThread myThread3 = new MyThread("thread3") ;
        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}

正确结果之一
这里写图片描述
所有的线程对象变成了交替执行

2、Runnable()接口实现多线程
Thread类的核心功能是进行线程的启动。如果一个类为了实现多线程直接去继承Thread类就会有但继承局限。在java中又提供有另外一种实现模式:Runnable接口。
观察Runnable接口:
这里写图片描述
例:利用Runable接口实现线程主体类

class MyThread implements Runnable { // 线程主体类
    private String title ;
    public MyThread(String title) {
        this.title = title;
    }
    @Override
    public void run() { // 所有线程从此处开始执行
        for (int i = 0; i < 10 ; i++) {
        System.out.println(this.title+",i = " + i);
        }
    }
}

这样写以后,新的问题就产生了。此时MyThread类继承的不再是Thread类而实现了Runnable接口,虽然解决了单继承局限问题,但是没有start()方法被继承了。那么此时就需要关注Thread类提供的构造方法。
Thread类提供的构造方法:public Thread(Runnable target)可以接收Runnable接口对象。
因此,启动一个线程:
例:

public class TestDemo {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("thread1") ;
        MyThread myThread2 = new MyThread("thread2") ;
        MyThread myThread3 = new MyThread("thread3") ;
        new Thread(myThread1).start();
        new Thread(myThread2).start();
        new Thread(myThread3).start();
    }
}

这个时候就启动了多线程。多线程的启动永远都是Thread类的start()方法。
这个时候需要注意的是,对于此时的Runnable接口对象可以采用匿名内部类或者Lambda表达式来定义。
例:使用匿名内部类进行Runnable对象创建

package www.bit.java.testdemo;
public class TestDemo {
    public static void main(String[] args) {
        new Thread( new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World");
            }
        } ).start();
    }
}

这里写图片描述

例:使用Lamdba表达式进行Runnable对象创建

package www.bit.java.testdemo;
public class TestDemo {
    public static void main(String[] args) {
        Runnable runnable = () -> System.out.println("Hello World");
        new Thread(runnable).start();
    }
}

3、 Callable实现多线程
从JDK1.5开始追加了新的开发包:java.uti.concurrent。这个开发包主要是进行高并发编程使用的,包含很多在高并发操作中会使用的类。在这个包里定义有一个新的接口Callable。
Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是很多时候需要一些返回值,例如某些线程执行完成后可能带来一些返回结果,这种情况下就只能利用Callable来实现多线程。
例:使用Callable定义线程主体类

class MyThread implements Callable<String> {
    private int ticket = 10 ; // 一共10张票
    @Override
    public String call() throws Exception {
        while(this.ticket>0){
            System.out.println("剩余票数:"+this.ticket -- );
        }
        return "票卖完了,下次吧。。。" ;
    }
}

不管何种情况。如果要想启动多线程只有Thread类中的start()方法。
来看Callable的继承树:
这里写图片描述
例:启动并取得多线程的执行结果

public class TestDemo {
    public static void main(String[] args) throws InterruptedException,      ExecutionException {
        FutureTask<String> task = new FutureTask<>(new MyThread()) ;
        new Thread(task).start();
        new Thread(task).start();
        System.out.println(task.get());
    }
}

运行结果
这里写图片描述

三种启动线程的方法已经介绍完毕了

介绍一下Thread与Runnable区别
首先从使用形式来讲,明显使用Runnable实现多线程要比继承Thread类要好,因为可以避免但继承局限。
除了这点以外,Thread和Runnable还有什么区别呢?
来看Thread类的定义

public class Thread implements Runnabl

Thread类是Runnable接口的子类,那么Thread类一定覆写了Runnable接口的run()方法

//Thread的构造函数
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

这里写图片描述
在多线程的处理上使用的就是代理设计模式。除了以上的关系之外,实际上在开发之中使用Runnable还有一个特点:使用Runnable实现的多线程的程序类可以更好的描述出程序共享的概念(并不是说Thread不能)
例:使用Thread实现数据共享(产生若干线程进行同一数据的处理操作)

package www.bit.java.testdemo;
class MyThread extends Thread {
    private int ticket = 10 ; // 一共10张票
    @Override
    public void run() {
        while(this.ticket>0){
        System.out.println("剩余票数:"+this.ticket -- );
        }
    }
}
public class TestDemo {
    public static void main(String[] args) {
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
    }
}

运行结果
这里写图片描述
此时启动三个线程实现卖票处理。结果变为了卖各自的票。

例:使用Runnable实现共享

package www.bit.java.testdemo;
class MyThread implements Runnable {
    private int ticket = 10 ; // 一共10张票
    @Override
    public void run() {
        while(this.ticket>0){
            System.out.println("剩余票数:"+this.ticket -- );
        }
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread() ;
        new Thread(mt).start();
        new Thread(mt).start();
    }
}

运行结果
这里写图片描述
Runnable实现的多线程的程序类可以更好的描述出程序共享的概念。

多线程的常用操作方法

1、线程的命名与取得
多线程的运行状态是不确定的,所以对于多线程操作必须有一个明确标识出线程对象的信息,这个信息往往通过名称来描述。在Thread类中提供有如下的线程名称方法:
这里写图片描述
要想取得线程的对象,在Thread类中提供有一个方法取得当前线程对象:

public static native Thread currentThread();

例:观察线程名称取得

package www.bit.java.testdemo;
class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10 ; i++) {
        System.out.println("当前线程:"+Thread.currentThread().getName()+" ,i = "+i);
        }
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread() ;
        new Thread(mt).start(); // 没有设置名字
        new Thread(mt).start(); // 没有设置名字
        new Thread(mt,"yuisama").start(); // 有设置名字
    }
}

通过上述代码发现,如果没有设置线程名字,则会自动分配一个线程名字。需要主要的是,线程名字如果要设置请避免重复,同时中间不要修改。

package www.bit.java.testdemo;
class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println("当前线程:" + Thread.currentThread().getName());
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.run(); // 直接通过对象调用run()方法
        new Thread(mt).start(); // 通过线程调用
    }
}

通过以上程序我们发现,主方法本身就是一个线程,所有的线程都是通过主线程创建并启动的。
疑问:进程在哪?
实际上每当使用了java命令去解释程序的时候,都表示启动了一个新的JVM进程。而主方法只是这个进程上的一个线程而已。
在讲线程的其他常用方法以前我们先来看张图
这里写图片描述
2、线程休眠(sleep()方法)
线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。
线程休眠会交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
方法:

public static native void sleep(long millis) throws InterruptedException

休眠时间使用毫秒作为单位
例:处理休眠操作

package www.bit.java.testdemo;
class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000 ; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        System.out.println("当前线程:" + Thread.currentThread().getName()+" ,i = " +i);
        }
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
    }
}

通过代码观察会错误的认为这三个线程是同时休眠的,但是千万要记住,所有的代码是依次进入到run()方法中的。
真正进入到方法的对象可能是多个,也可能是一个。进入代码的顺序可能有差异,但是总体的执行是并发执行。
3、线程让步(yideld()方法)
暂停当前正在执行的线程对象,并执行其他线程。
意思就是调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
例:观察yield方法

class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 3 ; i++) {
            Thread.yield();
            System.out.println("当前线程:" + Thread.currentThread().getName()+" ,i = " +i);
        }
    }
}
public class Test {
    public static void main(String[] args){
        MyThread mt = new MyThread();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
    }
}

4、join()方法
等待该线程终止。意思就是如果在主线程中调用该方法时就会让主线程休眠,让调用该方法的线程run方法先执行完毕之后在开始执行主线程。
例:观察join()方法

class MyThread implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("主线程睡眠前的时间");
            Test.printTime();
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName());
            System.out.println("睡眠结束的时间");
            Test.printTime();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException{
        MyThread mt = new MyThread();
        Thread thread = new Thread(mt,"子线程A");
        thread.start();
        System.out.println(Thread.currentThread().getName());
        thread.join();
        System.out.println("代码结束");
    }
    public static void printTime() {
        Date date=new Date();
        DateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time=format.format(date);
        System.out.println(time);
    }
}

5、线程停止
多线程中有三种方式可以停止线程。

设置标记位,可以是线程正常退出。
使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了。
使用Thread类中的一个 interrupt() 可以中断线程

例:设置标记为使线程退出

class MyThread implements Runnable {
    private boolean flag = true;
    @Override
    public void run() {
        int i = 1;
        while (flag) {
            try {
                Thread.sleep(1000);
                System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
                i++;
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        Thread thread1 = new Thread(myThread,"子线程A");
        thread1.start();
        Thread.sleep(2000);
        myThread.setFlag(false);
        System.out.println("代码结束");
    }
}

例:使用stop方法使线程退出

MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"子线程A");
thread1.start();
Thread.sleep(3000);
thread1.stop();
System.out.println("代码结束");

使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了。
**为什么说不安全呢?因为stop会解除由线程获取的所有锁定,当在一个线程
对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如
一个线程正在执行:synchronized void { x = 3; y = 4;} 由于方法是同步
的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x
= 3;时,被调用了 stop()方法,即使在同步块中,它也会马上stop了,这样
就产生了不完整的残废数据。**
例:使用Thread.interrupt()

class MyThread implements Runnable {
    private boolean flag = true;
    @Override
    public void run() {
        int i = 1;
        while (flag) {
            try {
                /**
                * 这里阻塞之后,线程被调用了interrupte()方法,
                * 清除中断标志,就会抛出一个异常
                * java.lang.InterruptedException
                */
                Thread.sleep(1000);
                boolean bool = Thread.currentThread().isInterrupted();
                if (bool) {
                    System.out.println("非阻塞情况下执行该操作。。。线程状态" + bool);
                    break;
                }
                System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
                i++;
            } catch (InterruptedException e) {
                System.out.println("退出了");
                /**
                * 这里退出阻塞状态,且中断标志被系统会自动清除,
                * 并且重新设置为false,所以此处bool为false
                */
                boolean bool = Thread.currentThread().isInterrupted();
                System.out.println(bool);
                //退出run方法,中断进程
                return;
            }
        }
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        Thread thread1 = new Thread(myThread,"子线程A");
        thread1.start();
        Thread.sleep(3000);
        thread1.interrupt();
        System.out.println("代码结束");
    }
}

interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程就得以退出阻塞的状态。
然而interrupte()方法并不会立即执行中断操作;具体而言,这个方法只会给线程设置一个为true的中断标志(中断标志只是一个布尔类型的变量),而设置之后,则根据线程当前的状态进行不同的后续操作。如果,线程的当前状态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么在将中断标志设置为true后,还会有如下三种情况之一的操作:
如果是wait、sleep以及jion三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个InterruptedException;

如果在中断时,线程正处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理;例如,一个线程在运行状态中,其中断标志被设置为true之后,一旦线程调用了wait、jion、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志被程序会自动清除,重新设置为
false。

通过上面的分析,我们可以总结,调用线程类的interrupted方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。因此,通过interrupted方法真正实现线程的中断原理是:开发人员根据中断标志的具体值,来决定如何退出线程。
6、线程优先级
线程的优先级指的是,线程的优先级越高越有可能先执行,但仅仅是有可能而已。
在Thread类中提供有如下优先级方法:

设置优先级:
public final void setPriority(int newPriority)
取得优先级:
public final int getPriority()

对于优先级设置的内容可以通过Thread类的几个常量来决定

 最高优先级:public final static int MAX_PRIORITY = 10;
 中等优先级:public final static int NORM_PRIORITY = 5;
  最低优先级:public final static int MIN_PRIORITY = 1;

例:设置优先级

package www.bit.java.testdemo;
class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5 ; i++) {
            System.out.println("当前线程:" + Thread.currentThread().getName()+" ,i = " +i);
        }
    }
}
public class TestDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread t1 = new Thread(mt,"1") ;
        Thread t2 = new Thread(mt,"2") ;
        Thread t3 = new Thread(mt,"3") ;
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.MIN_PRIORITY);
        t3.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
    }
}

主方法是一个线程,那么主线程的优先级是什么呢?

public class TestDemo {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getPriority());
    }
}

主方法只是一个中等优先级

线程具有继承性

线程是有继承关系的,比如当A线程中启动B线程,那么B和A的优先级将是一样的
例:观察线程继承性

class A implements Runnable {
    @Override
    public void run() {
        System.out.println("A的优先级为:" + Thread.currentThread().getPriority());
        Thread thread = new Thread(new B());
        thread.start();
    }
}
class B implements Runnable {
    @Override
    public void run() {
        System.out.println("B的优先级为:" + Thread.currentThread().getPriority());
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new A());
        thread.setPriority(Thread.MAX_PRIORITY);
        thread.start();
    }
}

7、守护线程
守护线程是一种特殊的线程,它属于是一种陪伴线程。简单点说 java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。典型的守护线程就是垃圾回收线程。只要当前JVM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当最后
一个非守护线程结束时,守护线程才会随着JVM一同停止工作。
注意:主线程main是用户线程。
例:观察守护线程

class A implements Runnable {
    private int i;
    @Override
    public void run() {
        try {
            while (true) {
                i++;
                System.out.println("线程名称:" + Thread.currentThread().getName() + ",i=" + i +
",是否为守护线程:"+ Thread.currentThread().isDaemon());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            System.out.println("线程名称:" + Thread.currentThread().getName() + "中断线程了");
        }
    }
}
public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new A(),"子线程A");
        // 设置线程A为守护线程,此语句必须在start方法之前执行
        thread1.setDaemon(true);
        thread1.start();
        Thread thread2 = new Thread(new A(),"子线程B");
        thread2.start();
        Thread.sleep(3000);
        // 中断非守护线程
        thread2.interrupt();
        Thread.sleep(10000);
        System.out.println("代码结束");
    }
}

从上面的代码可以看出来,B是用户线程当它中断了之后守护线程还没有结束,是因为主线程(用户线程)还没有结束,所以说明是所有的用户线程结束之后守护线程才会结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值