spring

Java基础——多线程

2019-10-19  本文已影响0人  So_ProbuING

Java语言提供了非常优秀的多线程支持。可以通过非常简单的方式来启动多线程

线程概述

线程和进程

进程的特征


多线程的创建和启动

Java中使用Thread来代表线程,所有的线程对象都必须是Thread类或其子类的实例

继承Thread类创建线程类

  1. 定义Thread类的子类,重写该类run()方法

  2. 创建该Thread子类的实例

  3. 调用线程对象的start()方法来启动线程
    class ThreadDemo extends Thread{

public void run(){
System.out.println(this.getName+"run");
}

public static void main(String[] args){
new ThreadDemo().start();
}

}</pre>

实现Runnable接口创建线程类

  1. 定义Runnable接口的实现类,并重写该接口的run()方法。run()方法的方法体同样是该线程的线程执行体

  2. 创建Runnable实现类的实例

  3. 调用线程对象的start()方法来启动线程
    class RunnableDemo implements Runnable{
    public void run(){
    System.out.println(this.getName()+"run");
    }

public static void main(String[] args){
new RunnableDemo().start();
}
}</pre>

使用Callable和Future创建线程

自Java5 开始 Java提供了Callable接口,Callable接口提供了一个call()方法作为线程的执行体

Java5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该类实现Future接口并实现runnable,可以作为Thread类的执行target

在Future接口里定义了如下的API来控制关联的Callable任务

创建并启动有返回值的线程步骤

  1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例

  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法

  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程

  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建callable
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(2000);
return 20;
}
});
new Thread(futureTask).start();
System.out.println("线程的返回值"+futureTask.get());</pre>

//创建callable

//lambda
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(2000);
return 20;
}
});
new Thread(futureTask).start();
System.out.println("线程的返回值"+futureTask.get());
FutureTask<String> futureTask = new FutureTask<>((Callable<String>) () -> {
return "thread return";
});
new Thread(futureTask).start();
System.out.println(futureTask.get());

// Thread pool
ExecutorService threadPool = Executors.newFixedThreadPool(5);
System.out.println(threadPool.submit((Callable<Integer>) () -> {
return 20;
}).get());
threadPool.shutdown();</pre>

线程的生命周期

线程被创建并启动后,线程不是立即进入就绪状态,也不是一直处于执行状态,需要经过 新建(New) 就绪(Runnable) 运行(Running) 阻塞(Blocked) 死亡(Dead) 5种状态

新建和就绪状态

运行和阻塞状态

当发生如下情况使,线程将会进入阻塞状态:

当发生如下的情况会让线程重新进入就绪状态

[图片上传失败...(image-5f7d60-1571407713512)]

线程死亡

isAlive() 当线程处于就绪、运行、阻塞三种状态时返回true 当线程处于新建、死亡状态时返回false

控制线程

join线程

Thread提供了一个让一个线程等待另一个线程完成的方法——join

当在某个程序执行流中调用其他线程的Join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止

注意:这里是调用线程将被阻塞

join() 方法的重载

public class JoinThread extends Thread {
private String name;

public JoinThread(String name) {
setName(name);
}

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

}

//main
public static void main(String[] args) throws InterruptedException {
//创建执行线程
new JoinThread("线程1").start();
//主线程
for (int i = 0; i < 100; i++) {
if (i == 30) {
//创建要加入的线程
JoinThread joinThread = new JoinThread("join线程");
joinThread.start();
joinThread.join();

}
System.out.println(Thread.currentThread().getName() + " "+i);
}
}
}</pre>

上面的代码中 在main线程中调用了joinThread的join方法 所以需要主线程等待joinThread执行结束后方可继续执行

后台线程

有一种线程,它是在后台运行的,它的任务是为其他线程提供服务,这种线程称为守护线程。典型的守护线程就是JVM

public class DaemonDemo extends Thread {
public DaemonDemo(String name) {
setName(name);
}

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

public static void main(String[] args) {
DaemonDemo daemonDemo = new DaemonDemo("守护线程");
daemonDemo.setDaemon(true);
daemonDemo.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
}
}</pre>

需要设置为守护线程必须在线程start()之前设置,否则会引发异常

线程睡眠 sleep

使当前正在执行的线程暂停一段时间,进入阻塞状态。

sleep()重载:

当当前线程调用sleep()方法进入阻塞状态后,在睡眠时间内,该线程不会获得执行激活,即使没有线程可以执行,处于sleep的线程也不会执行

线程优先级

每个线程都有一定的优先级,优先级高的线程会获得较高的执行机会,优先级低的线程则获得较少的执行机会

Thread类提供了setPriority(int new)、getPriority()来设置和返回指定线程的优先级

线程同步

同步代码块

为解决线程同步问题,java多线程支持引入了同步监视器来解决

synchronized(obj){
...
}</pre>

sychronized后面的参数obj就是同步监视器。当线程开始执行同步代码块之前,必须先获得同步监视器的锁定

任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,线程会释放对该同步监视器的锁定

同步方法

同步方法就是使用synchronized关键字来修饰的方法

用synchronized修饰的实例方法不需要指定锁对象,同步方法的监视器就是this。

语法格式

public synchronized void method(param){
...
}

tips 线程何时释放同步锁?
tips 线程不会释放同步锁

JAVA5 LOCK

java5开始 java提供了一种功能强大的线程同步机制——通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当

在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁),使用该Lock对象可以显式地加锁、释放锁

class X{
//定义锁对象
private final ReentrantLock lock = new ReentrantLock();
public void m(){
//加锁
lock.lock();
try{
//需要保证线程同步的代码
}finally{
//释放锁
lock.unlock();
}
}
}</pre>

ReentrantLock锁具有可重入性,一个线程可以对已被加锁的ReentrantLock锁再次加锁。ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程每次显式调用lock()加锁后,必须显式调用unlock()来释放锁

死锁

当两个线程相互等待对方释放同步监视器时就会发生死锁。

线程通信

传统方式

传统方式中可以使用Object类提供的wait() notify() notifyAll()来进行线程通信和控制

上一篇下一篇

猜你喜欢

热点阅读