Java:多线程

一、什么是多线程

1、进程与线程

进程:当一个程序运行起来后就是一个单独的进程
线程:一个进程包含很多个线程,它是CPU调度的最小单元,由CPU一条一条的执行指令

2、并行与并发

并行:多核CPU运行多线程,每个核在同一时刻都有线程在运行
并发:单核CPU运行多线程,必须通过时间片切换才能实现,在同一时刻其实只能运行一个线程,但是在宏观上看该核也实现了多线程的运行
上下文切换:对于单核CPU来说,在同一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另一个线程,这个就叫做线程上下文转换。当然为了便于在切换回来时候方便继续计算,需要记录程序计数器、CPU寄存器状态。

3、创建多线程任务的三种方式

继承关系如图:
在这里插入图片描述

1、继承Thread类(将任务和线程合并在了一起)

Thread源码位置:java.lang包
业务代码需要在重写的run()方法中写,当想执行该线程必须在主线程中使用thread.start()执行,才能提交一个线程,不能直接调用run()方法,因为直接调用的话跟调用普通方法没有区别

public class JavaThread {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

class MyThread extends Thread {
    private static int num = 0;

    public MyThread() {
        num++;
    }

    @Override
    public void run() {
        System.out.println("创建的第" + num + "个线程");
    }
}

2、实现Runnable接口(将任务和线程分开了)

Runnable的中文意思是“任务”,顾名思义,通过实现Runnable接口,我们定义了一个子任务,然后将子任务交由Thread去执行。
必须将Runnable对象作为Thread类的一个参数,然后通过Thread的start方法创建一个新线程来执行该任务,如果直接调用Runnable对象的run()方法是不会创建新线程的,跟调用普通方法没区别。

public class JavaRunnable {
    public static void main(String[] args) {
        System.out.println("主线程ID:" + Thread.currentThread().getId());
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

class MyRunnable implements Runnable {
    public MyRunnable() {
    }

    @Override
    public void run() {
        System.out.println("子线程ID:" + Thread.currentThread().getId());
    }
}

注意:
其实使用上边两个方法都可以创建线程去执行任务,但是Java只允许单继承,所以你的自定义类需要继承其它类时候,只能选择实现Runnable接口。

3、实现Callable接口(利用FutureTask执行任务,有返回值)

上边两种实现多线程任务的方式没有返回值,如果你想在主任务中获取其它线程的返回结果,只能使用ExecutorService、Callable、Future实现有返回值的多线程任务。

public class JavaCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("主线程运行开始");
        Date date1 = new Date();
        int taskSize = 5;

        //创建一个线程池
        ExecutorService pool = Executors.newFixedThreadPool(taskSize);
        //创建多个有返回值的任务
        ArrayList<Future> futures = new ArrayList<>();
        for (int i = 0; i < taskSize; i++) {
            MyCallable callable = new MyCallable(i + " ");
            //执行任务并获取Future对象
            Future<Object> future = pool.submit(callable);
            futures.add(future);
        }
        //关闭线程池
        pool.shutdown();

        //获取所有并发任务的运行结果
        for (Future future : futures)
            //打印线程返回结果
            System.out.println(">>>" + future.get().toString());

        Date date2 = new Date();
        System.out.println("主线程运行结束,耗时" + (date2.getTime() - date1.getTime()) + "毫秒");
    }
}

class MyCallable implements Callable<Object> {
    private String taskNum;

    public MyCallable(String taskNum) {
        this.taskNum = taskNum;
    }

    @Override
    public Object call() throws Exception {
        System.out.println(">>>" + taskNum + "任务启动了");
        Date dateTmp1 = new Date();
        Thread.sleep(1000);
        Date dateTmp2 = new Date();
        long time = dateTmp2.getTime() - dateTmp1.getTime();
        System.out.println(">>>" + taskNum + "任务终止了");
        return taskNum + "任务运行结束,当前任务运行时间了" + time + "毫秒";
    }
}

打印结果如下:

主线程运行开始
>>>0 任务启动了
>>>2 任务启动了
>>>3 任务启动了
>>>4 任务启动了
>>>1 任务启动了
>>>0 任务终止了
>>>3 任务终止了
>>>0 任务运行结束,当前任务运行时间了1001毫秒
>>>1 任务终止了
>>>1 任务运行结束,当前任务运行时间了1001毫秒
>>>2 任务终止了
>>>4 任务终止了
>>>2 任务运行结束,当前任务运行时间了1001毫秒
>>>3 任务运行结束,当前任务运行时间了1001毫秒
>>>4 任务运行结束,当前任务运行时间了1001毫秒
主线程运行结束,耗时1028毫秒

二、源码及案例

一、Thread类的常用方法

1、public synchronized void start()

使用该方法开启线程,Java虚拟机会调用该线程的run()方法。

2、public void run()

如果该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run()方法;否则,该方法不执行任何操作并返回。

/* What will be run. */
private Runnable target;

@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

3、public final synchronized void setName(String name)

改变线程的名字,使之与参数name相同

public final synchronized void setName(String name) {
        checkAccess();
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
        if (threadStatus != 0) {
            setNativeName(name);
        }
    }

4、public final void setPriority(int newPriority)

更改线程的优先级

5、public final void setDaemon(boolean on)

将该线程标记为守护线程或用户线程
在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。
Daemon的作用是为其他线程的运行提供服务,比如GC线程。

6、join()

public final void join()
主线程等待该线程的终止
public final synchronized void join(long millis)
主线程等待该线程终止的时间最长为millis毫秒
public final synchronized void join(long millis, int nanos)
主线程等待该线程死亡的时间最长为millis毫秒+nanos纳秒

这个的使用场景是,如果主线程需要等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就需要用这个方法。方法join()的作用是等待线程对象并销毁。

public class Thread4 extends Thread {
    public Thread4(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + " " + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //启动子线程
        new Thread4("new thread").start();
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                Thread4 th = new Thread4("joined thread");
                th.start();
                th.join();
            }
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

可以发现,main主线程等待joined thread线程执行完了才结束。

main 0
main 1
main 2
main 3
main 4
new thread 0
new thread 1
new thread 2
new thread 3
new thread 4
joined thread 0
joined thread 1
joined thread 2
joined thread 3
joined thread 4
main 5
main 6
main 7
main 8
main 9

把th.join()这行注释掉,运行结果如下

main 0
main 1
main 2
main 3
main 4
main 5
main 6
main 7
main 8
main 9
new thread 0
new thread 1
new thread 2
new thread 3
new thread 4
joined thread 0
joined thread 1
joined thread 2
joined thread 3
joined thread 4

7、public void interrupt()

中断线程
停止一个线程可以使用Thread.stop()方法,但是最好不要使用它,它是不安全的,已经被弃用。
可以使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。

8、public final native boolean isAlive();

查看线程是否处于活动状态。

9、public static native void yield();

暂停当前正在执行的线程对象,并执行其他线程。
跟sleep方法类似,同样不会释放锁。
调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点和sleep方法不同。

10、public static native void sleep(long millis)

让当前正在执行的线程休眠指定的毫秒数
注意:sleep方法不会释放锁,如果当前线程持有对某个对象的锁,则即使调用sleep方法,其它线程也无法访问这个对象。

public class JavaThread {
    private int i = 10;
    private Object object = new Object();

    public static void main(String[] args) {
        JavaThread test = new JavaThread();
        MyThread thread1 = test.new MyThread();
        MyThread thread2 = test.new MyThread();
        thread1.start();
        thread2.start();
    }

    class MyThread extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                i++;
                System.out.println("i:" + i);
                try {
                    System.out.println("线程" + Thread.currentThread().getName() + "进入睡眠状态");
                    Thread.currentThread().sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程" + Thread.currentThread().getName() + "睡眠结束");
                i++;
                System.out.println("i:" + i);
            }
        }
    }
}

由打印结果发现,Thread-0睡眠时,未释放对object持有的锁。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行。

i:11
线程Thread-0进入睡眠状态
线程Thread-0睡眠结束
i:12
i:13
线程Thread-1进入睡眠状态
线程Thread-1睡眠结束
i:14

11、public static native Thread currentThread()

返回当前正在执行的线程对象的引用。

System.out.println("主线程ID:" + Thread.currentThread().getName());

上述各方法运行状态如图:
在这里插入图片描述

在这里插入图片描述

三、参考文章

万字图解Java多线程
Java多线程干货系列—(一)Java多线程基础

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值