JAVA多线程详解(一):线程的创建与常用方法

一、线程与进程的区别

1、进程的概念:

进程(Process)是计算机系统进行资源分配和调度的基本单位,是操作系统结构的基础。进程有自己独立的地址空间,各进程会并发执行,互不影响。由程序、数据和进程控制块三部分组成。

2、线程的概念

线程是进程中实际运作的单位,一个线程就是进程中的顺序执行流程。进程中至少含有一个线程,即主线程。多个线程共享进程的全部系统资源,同时线程还拥有自己独立的虚拟机栈、本地方法栈、程序计数器。往往不同的线程具有不同的任务,有的负责IO,有的负责密集的计算,以此提高程序的运行效率。

3、线程的优势

a、线程间相互独立,且共享进程中的资源,相比进程隔离程度小。
b、线程间的通信更加容易,线程的运行效率更高,创建线程更加容易,性能更好。
c、调度时线程的上下文切换更快。
在这里插入图片描述

二、开启线程的方法

1、继承Thread类

此方法通过继承Thread来定义一个线程类,重写抽象方法run作为执行任务。新建该线程类的实例并调用start()方法进入就绪状态,当该线程被调度时则开始运行。

public class ThreadTest {
    class CountThread extends Thread{
        @Override
        public void run() {
            for(int i = 0; i < 100; i++)
            {
                System.out.println(Thread.currentThread().getName() +" "+ i);
            }
        }
    }

    public static void main(String[] args)
    {
        ThreadTest.CountThread thread = new ThreadTest().new CountThread();
        thread.start();
    }
}

2、实现Runnable接口创建任务

此方法实现Runnable接口,创建一个任务,然后将任务传给一个线程并调用该线程的start()方法,等待执行。当需要创建的线程多时,建议采用线程池的方式来接受任务,线程池会帮助我们启动任务的线程,并管理线程的运行状态。案例如下所示。

//利用Thread类和Runable接口创建线程和任务
public class ThreadTest {
    class Task implements Runnable{
        @Override
        public void run() {
            for(int i = 0; i < 100; i++)
            {
                System.out.println(Thread.currentThread().getName() +" "+ i);
            }
        }
    }

    public static void main(String[] args)
    {
        ThreadTest.Task task = new ThreadTest().new Task();
        new Thread(task).start();
    }
}

运行结果如下(与下方线程池创建的结果比较
Thread-0 0
Thread-0 1


//利用线程池和Runnable接口创建线程和任务
public class ThreadTest {
    class Task implements Runnable{
        @Override
        public void run() {
            for(int i = 0; i < 100; i++)
            {
                System.out.println(Thread.currentThread().getName() +" "+ i);
            }
        }
    }

    public static void main(String[] args)
    {
        ThreadTest.Task task = new ThreadTest().new Task();
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(task);
		service.shutdown();
    }
}

此方法创建的线程运行结果(线程的名称会自动加上线程池的标志)
pool-1-thread-1 0
pool-1-thread-1 1

3、实现Callable接口创建任务

同样是创建任务,Runnable的run方法没有返回值,但是Callable的call方法是有返回值的。由于Callable对象不能直接作为Thread类的target,所以采用FutureTask来包装Callable对象。

public class ThreadTest {
    class CallTask implements Callable<String> {
        @Override
        public String call() {
            return Thread.currentThread().getName();
        }
    }

    public static void main(String[] args)
    {
        ThreadTest.CallTask task = new ThreadTest().new CallTask();
        FutureTask<String> futureTask = new FutureTask<String>(task);
        Thread thread = new Thread(futureTask);
        thread.start();
//        while(thread.isAlive());
        try {
            System.out.print("返回值是:" + futureTask.get());
        }catch (InterruptedException | ExecutionException e)
        {
            e.printStackTrace();
        }
    }
}

注意:此处将程序中的注释行删掉,运行结果依然正常。由于futureTask.get()会使得当前所在线程阻塞,等待call方法返回。所以耗时操作得注意,防止主线程长时间等待。此处FutureTask之所以能够充当Thread的target是因为其继承了Runnable接口。

public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>

同样可以采用线程池的方式接受Callable任务开启一个线程,实现代码如下所示。

public class ThreadTest {
    class CallTask implements Callable<String> {
        @Override
        public String call() {
            return Thread.currentThread().getName();
        }
    }

    public static void main(String[] args)
    {
        ThreadTest.CallTask task = new ThreadTest().new CallTask();
        ExecutorService service = Executors.newCachedThreadPool();
        Future<String> result = service.submit(task);
        service.shutdown();
        try {
            System.out.print("返回值是:" + result.get());
        }catch (InterruptedException | ExecutionException e)
        {
            e.printStackTrace();
        }
    }
}

注意:采用线程池接受任务,线程池默认的ThreadFactory创建的线程并非是守护线程,而是用户线程。线程池里面的核心线程是一直会存在的,如果没有任务则会阻塞,所以线程池里面的用户线程一直会存在。如果不调用线程池的shutdown()方法,这个JVM进程将会一直存在。所以最终需要调用线程池的shutdown()来最终终止线程池(所有线程运行完成后终止)。

三、线程的状态转换

JDK中线程定义了6种状态,分别是NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED六中状态。
1、NEW:当线程被创建但是还未启动时处于这个状态。
2、RUNNABLE:当线程在虚拟机中运行时,或者等待系统调度时处于此状态。
3、BLOCKED:当前线程处于等待监视器锁的状态。等待进入同步代码块或者方法。
4、WAITING:当调用Object.wait()/join()/LockSupport.park()方法后线程处于等待状态,此状态需要等待其他线程执行操作才能唤醒。
5、TIMED_WAITING:当调用Object.wait(long)/join(long)/LockSupport.parkNanos()方法后线程处于超时等待状态。此状态当时间达到时则自动唤醒。
6、TERMINATED:当线程完成时处于此状态。
线程的状态转换图如下图所示。
在这里插入图片描述

四、线程的常用方法

1、currentThread()
这是一个Thread类的静态方法,返回的是此条语句运行在的当前线程对象。当这条语句在自定义线程类的构造函数中时,返回的线程是创建该线程类所在的线程对象。

2、isAlive()
判断调用线程是否是活动状态,该线程调用start()后到线程死亡之间该方法返回的都是true。

3、sleep()
此方法为Thread类的静态方法,是让当前所在线程(该语句运行所在的线程)等待设定的时间。线程进入阻塞状态,其不会释放锁。

4、getId()
此方法获得线程的唯一标识。

5、interrupt()
此方法并不能将线程中断,只是将线程中断标志设置为true,则线程内部可以根据这个标志位来处理线程。当线程处于WAITING/TIMED_WAITING状态时,调用此方法会抛出InterruptedException异常并将中断标志恢复为false。

6、中断标志位判断
interrupted()方法为Thread的静态方法,返回运行该语句所在的线程的中断标志。当中断标志位true时,此方法会将该标志恢复为false,则下次再调用则返回false。
isInterrupted()方法为对象普通方法,判断调用线程的中断标志位。其不会修改中断标志。

7、yield()
此方法会放弃系统资源,由运行状态回到就绪状态,等待系统调度。其不会释放锁。

8、setPriority()与getPriority()
此方法设置和获得线程的优先级,优先级越高,则获得系统调度的机会越大。线程的优先级具有继承性,也就是创建的线程会和创建时所在线程的优先级相同。

9、LockSupport.park()
此方法会使得当前线程进入WAITING状态,但是如果当前线程的中断标志位位true时,则此方法无效。此类内部有一个标志位若该标志位大于0,则表明之前调用过unpark()方法,则此方法将标志位改为0则退出,并不使线程进入WAITING状态。
注意:当调用interrupt()方法是同样可以唤醒线程。

10、LockSupport.unpark(Thread t)
此方法会使得调用了park()的线程t 唤醒。此类内部有一个标志位,若为1时则表示调用过unpark()方法,则unpark()不做任何修改。若为0且线程在WAITING状态则唤醒线程t,并将标志位改为1。无论调用多少次unpark()方法,标志位最大为1。

public class ParkTest {
    public static void main(String[] args) throws InterruptedException
    {
        ThreadRun r1 = new ThreadRun();
        r1.start();
        Thread.sleep(1000);
        System.out.println("r1状态:"+ r1.getState());
//        LockSupport.unpark(r1);
        r1.interrupt();
    }


}
class ThreadRun extends Thread{
    @Override
    public void run() {
        System.out.println("运行1.....");
        LockSupport.park();
        System.out.println("运行2.....");
        LockSupport.park();
        System.out.println("运行3.....");
        LockSupport.park();
        System.out.println("运行4.....");
    }
}
1、当采用r1.interrupt();时,运行结果为
运行1.....
r1状态:WAITING
运行2.....
运行3.....
运行4.....
Process finished with exit code 0
/2、当采用 LockSupport.unpark(r1);时运行结果为
运行1.....
r1状态:WAITING
运行2.....

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值