多线程(线程的四种创建方式)

1、并发与并行

并行:两个或多个事件在同一时刻发生

并发:两个或多个事件在同一时间段内发生

2、进程与线程

打开浏览器中的百度、淘宝,那么浏览器是一个进程,淘宝和百度是两个线程。

3、线程创建的方式

(1)继承Thread类:

自定义线程:

public class MyThread extends Thread {
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println("thread:"+new Date().getTime());
        }
    }
}

创建测试类:


public static void main(String args []){
MyThread myThread=new MyThread();
myThread.start();
for(int i=0;i<10;i++){
System.out.println("main:"+new Date().getTime());
}
}

main方法也是一个线程被称作主线程,手动创建的线程称为子线程,两个线程中分别书写for循环。

main:1585656576673
main:1585656576674
main:1585656576674
main:1585656576675
main:1585656576675
main:1585656576675
main:1585656576675
main:1585656576675
main:1585656576675
main:1585656576675
thread:1585656576675
thread:1585656576676
thread:1585656576676
thread:1585656576677
thread:1585656576678
thread:1585656576678
thread:1585656576678
thread:1585656576678
thread:1585656576678
thread:1585656576679

继承Thread类,重写run方法,创建线程的对象并调用start开启线程(注意:调用的是start方法,不是run方法)。这里循环的次数较少是先执行main线程,再去执行另外一个线程,当循环的次数较多时会出现两个线程的交叉执行的现象。

 这里只开辟了一条线程,可以开辟多条线程,主线程和子线程是交替执行的

(2)实现Runnable接口:

实现接口,创建线程:

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("myRunnable:"+new Date().getTime());
        }
    }
}

测试类:

public class Test {
    public static void main(String args []){
        Thread thread=new Thread(new MyRunnable());
        thread.start();
        for(int i=0;i<10;i++){
            System.out.println("main:"+new Date().getTime());
        }
    }
}
main:1585657253215
main:1585657253215
main:1585657253215
main:1585657253216
main:1585657253216
main:1585657253216
main:1585657253216
main:1585657253216
main:1585657253216
main:1585657253216
myRunnable:1585657253217
myRunnable:1585657253217
myRunnable:1585657253217
myRunnable:1585657253218
myRunnable:1585657253218
myRunnable:1585657253218
myRunnable:1585657253218
myRunnable:1585657253219
myRunnable:1585657253219
myRunnable:1585657253219

需要创建线程对象,通过线程对象来开启我们的线程,采用的是代理的方式

(3)实现Callable接口

Callable接口:

Callable需要使用FutureTask类帮助执行,FutureTask类结构如下:

 RunnableFuture接口需要实现Future和Runnable两个接口。

Future接口的方法:

判断任务是否完成:isDone()

能够中断任务:cancel()

能够获取任务的执行结果:get()

创建线程:

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"执行时间"+new Date().getTime());
        }
        return "MyCallable接口执行完成!!";
    }
}

在创建线程的时候设置了返回值,可以通过get方法获取。

主线程:

public class Test {
    public static void main(String args[]){
        FutureTask<String> futureTask =new FutureTask<String>(new MyCallable());
        Thread thread=new Thread(futureTask,"Mycallable");
        thread.start();
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"执行时间"+new Date().getTime());
        }
        String result= null;
        try {
            result = futureTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(result);
    }
}

在主线程中可以获取到手动创建的线程的返回值。

4、创建线程池

分类:

newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需求可灵活回收空闲线程,若无可回收,则新建线程
newFixedThreadPool:创建一个定长线程池,可控制线程的最大并发数,超出的线程会在线程池中等待。
newScheduleThreadPool: 创建一个定长线程池,支持定时及周期性任务处理
newSingleThreadScheduledExecutor:创建一个单线程化的线程池,他只用唯一的工作栈来执行任务

使用:

复制代码
class Mythread implements Runnable {
   public void run(){
       System.out.println(Thread.currentThread().getName());
   }
   public static  void  main(String args []){
           ExecutorService executorService = Executors.newFixedThreadPool(3);// 
           // Executors:线程池创建工厂类,调用方法返回线程池对象
           executorService.submit(new Mythread());
           executorService.submit(new Mythread());
           executorService.submit(new Mythread());
       }
}
复制代码
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3

好处:

限制线程的个数,不会导致由于线程过多导致系统运行缓慢,甚至崩溃

节省了资源:我们用创建的线程,在使用后都会被销毁,频繁地创建和销毁会造成时间和资源的浪费。线程池是一个能够容纳多个线程的容器,里面的线程可以反复使用。

5、实现接口和继承Thread类比较

(1)接口更适合多个相同的程序代码的线程去共享同一个资源。

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("myRunnable:"+new Date().getTime());
        }
    }
}

可以new多个Thread去调用创建的线程中的run方法,代码的复用增强:

 Thread thread=new Thread(new MyRunnable());

(2)接口可以避免Java中的单继承的局限性
(3)接口代码可以被多个线程共享,代码和线程独立
(4)线程池只能放入实现Runable 或callable 接口的线程,不能直接放入继承Thread的类

扩充:

在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。


6、Runnable和Callable接口比较
相同点:
(1)两者都是接口

(2)两者都可用来编写多线程程序
(3)两者都需要调用Thread.start 启动续程

不同点:
(1)实现Callable接口的线程能返回执行结果,而实现Runnable 接口的线程不能返回结果
(2)Callable 接口的call()方法允许抛出异常,而 Runnable 接口的run()方法的不允许批异常

(3)实现Callable 接口的线程可以调用Future.cancel取消执行,而实现runnable 接口的线程不能,不需要控制线程的话没必要用Callable接口

注意点:

Callable 接口支持返回执行结果,此时需要调用FutureTask. get()方法(主线程会被阻塞,不能和main并发执行)实现,此方法会阻塞主线程直到获取“将来’结果;当不调用此方法时,主线程不会阻塞!

7、线程的生命周期

(1)新建

new关键字创建一个线程之后,该线程处于新建状态。jvm为线程分配内存,初始化成员变量值

(2)就绪

当线程对象调用了start()方法后,该线程处于就绪状态。jvm为线程创建方法栈和程序计数器,等待线程调度器调度

(3)运行

就绪状态的线程获得CPU资源,开始运行run方法,该线程处于运行状态

(4)阻塞

线程调用sleep方法主动放弃所占用的处理器资源

线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞

线程试图获得一个同步锁(同步监视器),但是该锁正在被其它线程持有

线程正在等待某个通知

程序调用了线程的suspend()方法将该线程挂起,但是这个方法容易导致死锁,所以应该尽量避免使用该方法

(5)死亡

线程会以如下的三种方式结束,结束后就进入死亡状态:·

run()或call()方法执行完成,线程正常结束

线程抛出一个未捕获的Exception或Error

调用该线程的stop方法来结束该线程,该方法容易导致死锁,不推荐使用

进程的三种基本状态:

就绪:进程已经获得了除CPU以外的所有必要资源,只要获得了CPU便可立即执行。

执行:进程已经获得了CPU,其进程正在执行的状态。

阻塞:正在执行的进程由于IO请求、申请缓冲区失败等(如:访问临界资源)暂时无法继续执行的状态。

 8、线程安全

如果多个线程同时运行同一个实现了Runnable接口的类,程序每次运行的结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的,反之,则是线程不安全的。

原文地址:https://www.cnblogs.com/zhai1997/p/12609211.html