JAVA多线程操作基础




java中的线程操作都是就基于Thread类以及派生类实现的。
学习java多线程知识的时候,建议阅读java的源码,了解实现原理,而且源码包本身也提供了一些example。
阅读本文需要预先学习lambda,函数式接口等基础知识。这不算什么教材,并逼近于笔记,以知识点为主。

1.继承Thread类实现创建并启动线程

Thread的start方法(使用synchronized修饰 )是异步调用方法,如果使用run方法,那么还是同步的。

new  Thread(){
    @Override
    public void run() {
        super.run();
    }
}.start();

独立的派生类例子

class PrimeThread extends Thread {
    long minPrime;

    PrimeThread(long minPrime) {
        this.minPrime = minPrime;
    }

    public void run() {
        // compute primes larger than minPrime
    }
}

每个线程都是独立的存在,所以他们都有一个唯一的名字以作区分,Thread类包含了setName和getName方法来操作。

2.实现Runnable接口实现创建并启动线程

Runnable接口包含了抽象方法run作为线程的线程执行体。但具体运行的时候依旧是使用Thread类实例执行run方法(Thread本身就实现了Runnable接口)


class Mythread implements Runnable {
    @Override
    public void run() {
    }
}

new Thread(new Mythread()).start();

或者使用lambda(Runnable是函数式接口 @FunctionalInterface)

new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();

3.使用Callable和Future实现创建线程
Callable接口和Runnable接口类似,但提供了一个call方法作为线程执行体,但call方法可以有返回值且可以声明抛出异常。我们可以使用Callable作为Thread的执行体。
但Callable不是Runnable的子类,Thread不接受它作为构造参数。

所以,java提供了FutureTask来实现对Callable的管理,FutureTask是Future接口的实现类,并实现了Runable接口。他包含了cancel方法取消任务,get方法读取返回值 isCanceled方法和isDone方法查看状态。

FutureTask task = new FutureTask<>(()->1);
new Thread(task).start();
System.out.println(task.get());

5.总结
线程可以通过继承Thread类或者实现Runnable,Callable接口实现。Thread本身就实现了Runnable,具有创建线程的能力,而Runnable和Callable的实现类只能在Thread实例中运行。
Callable相比Runnable,可以有返回值且能抛出异常。

6.线程生命周期
每个线程都要经历 新建-就绪-运行-阻塞-死亡 5种状态。
当使用bew关键词创建线程时,该线程就进入了新建状态,这时仅仅分配了内存和初始化变量,当调用start方法后,线程进入就绪状态,但还没运行。至于什么时候真正的开始跑,取决于jvm了。

当线程阻塞时,线程将会暂停运行。线程阻塞的运行的原因有很多,比如主动调用sleep方法,suspend方法,等待同步锁,IO阻塞等等。
最后,线程里的代码运行完了,抛异常了,直接stop了,线程就结束了。

java中子线程并不会受主线程死亡的影响,哪怕主线程退出了,子线程还是会继续跑直到退出。已经死亡的线程,也没有办法重新调用start方法运行。

7.线程控制

join方法
join方法可以阻塞当前线程,直到指定的线程退出。

后台线程
后台线程指的是那些后台运行,为其他线程提供服务的线程,常称之为守护线程(与此类似的还有守护进程,linux系统里出现的比较多)
后台线程创建后会一直运行,直到前台进程全部死亡。是否是后台进程,可以通过Thread的setDeamon方法指定,通过isDeamon识别。

sleep方法
主动阻塞线程

Thread.sleep(1000); //阻塞1秒

yield方法
类似sleep,可以让线程暂停,但sleep是通过阻塞线程实现的,yield是通过设置线程为就绪状态实现的,然后jvm的线程调度器又让线程继续运行,这个过程非常短,只有优先级比当前线程高德处于就绪状态的线程才会获得执行的机会。yield一遍很少用。

线程优先级
每个线程都有默认的优先级(继承父线程的优先级),优先级高的线程有更多的执行机会。使用Thread的setPriority方法设置线程的优先级

8.线程同步

线程安全问题

多个线程操作同一个资源时可能引发的问题,课堂上老师应该讲过银行取款的问题,有一个人在银行存款了1000元,他非常快的同时执行了2次取款操作,每次取款600元,这2次操作被转化为2个操作线程,
当线程1判断用户有足够余额取款的时候,第二个线程也读取了用户的余额。线程1在确认用户取款但还未来得及修改账户余额的取款下,线程2也读取了余额信息并判断用户还有足够余额(1000元),然后又确认了取款操作,最后,账户余额400元(执行了2次1000-600的计算)。

当然这并不是我想要的结果,理想状态下,当第一次取款操作的时候,应该锁定用户的余额信息,谁都不能动,直到执行完取款操作。

暂无评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.