Java 多线程

线程状态图

线程创建,

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示:

1)继承Thread类创建线程

2)实现Runnable接口创建线程

3)使用Callable和Future创建线程

4)使用线程池例如用Executor框架

在此重点介绍第三种。

使用Callable和Future创建线程

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

  • call()方法可以有返回值

  • call()方法可以声明抛出异常

/*
返回结果并可能抛出异常的任务。
实现者定义了一个不带参数的方法,称为call。  
Callable接口与Runnable接口类似,两者都是为其实例可能由另一个线程执行的类设计的。 然而,Runnable不返回结果,也不能抛出检查异常。  
Executors类包含将其他常见形式转换为Callable类的实用方法。  
自:  1.5  
参见:  Executor  
作者:  Doug Lea  
类型参数:  <V> -方法调用的结果类型  
*/
@FunctionalInterface
public interface Callable<V> {
    
    V call() throws Exception;
    
}

Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。

在Future接口里定义了几个公共方法来控制它关联的Callable任务。

/*
Future表示异步计算的结果。 
提供了一些方法来检查计算是否完成、等待其完成以及检索计算的结果。 
结果只能在计算完成时使用get方法进行检索,必要时阻塞直到准备就绪。 
取消由cancel方法执行。 
提供了其他方法来确定任务是正常完成还是被取消。 
一旦计算完成,计算就不能取消。 
如果为了可取消性而使用Future但不提供可用的结果,可以声明Future<?>和返回null作为底层任务的结果。 

示例用法(注意,下面的类都是虚构的。)  
	interface ArchiveSearcher { String search(String target); }
     class App {
       ExecutorService executor = ...
       ArchiveSearcher searcher = ...
       void showSearch(final String target)
           throws InterruptedException {
         Future<String> future
           = executor.submit(new Callable<String>() {
             public String call() {
                 return searcher.search(target);
             }});
         displayOtherThings(); // do other things while searching
         try {
           displayText(future.get()); // use future
         } catch (ExecutionException ex) { cleanup(); return; }
       }
     }
     
FutureTask类是实现Runnable的Future的实现,因此可以由Executor执行。 例如,上述带有submit的结构可被替换为:  
     FutureTask<String> future =
       new FutureTask<String>(new Callable<String>() {
         public String call() {
           return searcher.search(target);
       }});
     executor.execute(future);
内存一致性影响: 异步计算所采取的动作发生在另一个线程中对应的Future.get()后面的动作之前。
Since: 1.5
See Also: FutureTask, Executor
Author: Doug Lea
Type parameters: <V> – 这个Future的get方法返回的结果类型
*/
public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
  • boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务
  • V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
  • V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException
  • boolean isDone():若Callable任务完成,返回True
  • boolean isCancelled():如果在Callable任务正常完成前被取消,返回True
  1. 创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

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

  3. 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

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

实例:

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		long start = System.currentTimeMillis();
		
		// 等凉菜 
		Callable ca1 = new Callable(){
 
			@Override
			public String call() throws Exception {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				return "凉菜准备完毕";
			}
		};
		FutureTask<String> ft1 = new FutureTask<String>(ca1);
		new Thread(ft1).start();
		
		// 等包子 -- 必须要等待返回的结果,所以要调用join方法
		Callable ca2 = new Callable(){
 
				@Override
				public Object call() throws Exception {
					try {
						Thread.sleep(1000*3);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					return "包子准备完毕";
			}
		};
		FutureTask<String> ft2 = new FutureTask<String>(ca2);
		new Thread(ft2).start();
		
		System.out.println(ft1.get());
		System.out.println(ft2.get());
		
		long end = System.currentTimeMillis();
		System.out.println("准备完毕时间:"+(end-start));
	}

Callable接口和Runnable接口

一、源代码角度分析两接口间的区别

首先给出他们俩的源代码:

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

以及:

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

由他们本身的接口定义我们就能够看出它们的区别:

  1. 如上面代码所示,callable的核心是call方法,允许返回值,runnable的核心是run方法,没有返回值
  2. call方法可以抛出异常,但是run方法不行
  3. 因为runnable是java1.1就有了,所以他不存在返回值,后期在java1.5进行了优化,就出现了callable,就有了返回值和抛异常
  4. callable和runnable都可以应用于executors。而thread类只支持runnable

它们的相同点:

  1. 两者都是接口
  2. 两者都需要调用Thread.start启动线程

二、从使用场景来分析两接口间区别:

2.1 Runnnable接口的使用场景

1)传递给线程对象执行任务;

2)作为线程池方法execute()的入口参数来执行任务;

具体的实现又可以细分,具体如下面代码块所示:

public class TheWayOfUsingRunnable {
    public static void main(String[] args) {
        //1)lambda表达式形式传递给线程构造器
        Runnable runnable1 = () -> {
            System.out.println("我是使用lambda表达式实现的Runnable对象实现 version1");
        };

        Thread thread1 = new Thread(runnable1);
        thread1.start();

        Thread thread1_1 = new Thread(() -> {
            System.out.println("我是使用lambda表达式实现的Runnable对象实现 version2");
        });
        thread1_1.start();

        //2)匿名内部类的方式实现Runnable对象的传入Thread构造器

        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("我是使用匿名内部类的方式实现的Runnable对象实现");
                    }
                }
        ).start();

        //3)使用实现了Runnable接口的对象实例传入Thread构造器
        RunnableTask runnableTask = new RunnableTask();
        new Thread(runnableTask).start();

        //4)Runnable类以及其子类作为线程任务提交给线程池,通过线程池维护的工作者线程来执行。
        Runnable runnable2 = () -> {
            System.out.println("我是使用lambda表达式实现的Runnable对象实现 不同的是:用于线程池的实现");
        };
        ExecutorService executor = Executors.newCachedThreadPool();
        executor.execute(runnable2);
        executor.shutdown();
    }

    /**RunnableTask
     * Runnable接口子类的实现类:RunnableTask
     */
    private static class RunnableTask implements Runnable{
        @Override
        public void run() {
            System.out.println("实现了Runnable接口的子类的对象实现");
        }

        //额外写一个方法:get
        public String get(){
            return "I am Fisherman.";
        }
    }

}

2.2 Callable接口的使用场景

callable对象实例可以作为线程池的submit()方法入口参数

public class TheWayOfUsingCallable {
    public static void main(String[] args) {
        //callable对象实例可以作为线程池的submit()方法入口参数
        ExecutorService executor = Executors.newCachedThreadPool();
        IntegerCallableTask integerCallableTask = new IntegerCallableTask();
        Future<Integer> future = executor.submit(integerCallableTask);
        executor.shutdown();
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class IntegerCallableTask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        Thread.sleep(1000);
        for (int i = 0; i < 101; i++) {
            sum += i;
        }
        return sum;
    }
}

从上述代码块的执行结果来看,main线程会一直等到执行完call()方法中的所有代码才会继续执行main线程中接下来的代码(等待发生在方法:future.get())。但是Runnable接口和Callable接口在线程池上的应用实际上是十分类似的。

wait和sleep

  1. 这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类

    sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。

  2. sleep方法没有释放锁,而wait方法释放了锁

    sleep不出让系统资源

    wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。

    一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。

    sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。

    Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。

  3. 使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用

  4. sleep 和wait方法都会抛出一个异常 InterruptedException

    源码如下:

    public static native void sleep(long millis) throws InterruptedException;
    
     public final void wait() throws InterruptedException {wait(0);}
    
总结
  • 两者都可以暂停线程的执行。
  • 对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
  • Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
  • sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。
  • 在调用sleep()方法的过程中,线程不会释放对象锁。
  • 而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。线程不会自动苏醒

run和star

  • 调用 start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run();直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。
  • 一个线程对线的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制。

测试代码

测试 run() 方法

public class TestThreadRunStart {
 
	public static void main(String[] args) {
		Thread t = new Thread(){
			@Override
			public void run() {
				//休眠3秒
				try {
					Thread.sleep(3000);
					System.out.println("休眠3秒");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("Thread running...");
			}
		};
		
		testRun(t);
//		testStart(t);
	}
	
	private static void testRun(Thread t) {
		t.run();
		//休眠1秒
		try {
			Thread.sleep(1000);
			System.out.println("休眠1秒");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	private static void testStart(Thread t) {
		t.start();
		//休眠1秒
		try {
			Thread.sleep(1000);
			System.out.println("休眠1秒");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
休眠3秒
Thread running...
休眠1秒

测试 start() 方法

public static void main(String[] args) {
		Thread t = new Thread(){
			@Override
			public void run() {
				//休眠3秒
				try {
					Thread.sleep(3000);
					System.out.println("休眠3秒");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("Thread running...");
			}
		};
		
//		testRun(t);
		testStart(t);
	}
休眠1秒
休眠3秒
Thread running...

线程相关方法

  • start()与run()

    start() 启动线程并执行相应的run()方法
    run() 子线程要执行的代码放入run()方法

  • getName()和setName()

    getName() 获取此线程的名字
    setName() 设置此线程的名字

  • join()和yield()

    yield() 暂停当前方法,释放自己拥有的CPU,线程进入就绪状态。

    它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;

    但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权

    也有可能是当前线程又进入到“运行状态”继续运行

    yield方法也不会释放同步锁

    join() 加入线程(当前执行的线程是A线程,调用join()方法得是B线程) 当前线程阻塞

    执行B线程 B执行结束之后A线程才能执行

    放弃当前线程的执行

  • interrupt()中断线程

    由运行状态到死亡状态

    •     中断线程操作实质上是修改了一下中断标示位为true
      
    •     当前线程正在运行,仅仅修改标示位,不在做其他的事
      
    •     当前线程正在阻塞,修改标识位,如果是join,sleep,yield,则会抛出Interrup异常,修改标示位为false
      

    方法源码:

    /*
    中断线程。  
    除非当前线程中断自身(这总是被允许的),否则将调用该线程的checkAccess方法,这可能导致抛出SecurityException。  
    如果这个线程被阻塞的调用等(),等待(长),或等待(长,int)方法的对象类,或者加入(),加入(长),加入(长,int),睡眠(长),或睡眠(长,int),这个类的方法,那么它的中断状态将被清除,它将接收一个InterruptedException。  
    如果这个线程在一个InterruptibleChannel的I/O操作中被阻塞,那么该通道将被关闭,线程的中断状态将被设置,并且该线程将收到一个java.nio.channels.ClosedByInterruptException异常。  
    如果这个线程在java.nio.channels.Selector中被阻塞,那么线程的中断状态将被设置,并且它将立即从选择操作中返回,可能带有一个非零值,就像调用了选择器的唤醒方法一样。  
    如果前面的条件都不满足,那么这个线程的中断状态将被设置。  
    中断一个非活动的线程不需要有任何影响。  
    抛出:  
    如果当前线程不能修改这个线程  
    */
    
    public void interrupt() {
            if (this != Thread.currentThread())
                checkAccess();
    
            synchronized (blockerLock) {
                Interruptible b = blocker;
                if (b != null) {
                    interrupt0();           // Just to set the interrupt flag
                    b.interrupt(this);
                    return;
                }
            }
            interrupt0();
        }
    
  • setDaemon()设置守护线程

    setDaemon(true) 设置当前线程为守护线程

    • 守护线程和用户线程最主要区别是守护线程脱离终端
    • 当用户 线程不在,只存在守护线程 ,,JVM就不在了,JVM不在了之后守护线程也就不在了
    /*
    将该线程标记为守护线程或用户线程。 当运行的线程都是守护线程时,Java虚拟机将退出。
     必须在线程启动之前调用此方法。
     参数:
     On -如果为true,则将该线程标记为守护线程
     抛出:
     IllegalThreadStateException -如果这个线程是活动的
     SecurityException -如果checkAccess确定当前线程不能修改该线程
    */
    public final void setDaemon(boolean on) {
            checkAccess();
            if (isAlive()) {
                throw new IllegalThreadStateException();
            }
            daemon = on;
        }
    
  • getPriority()

    getPriority() 获取线程优先级 1~10 默认值为5 优先级越高被优先调用的频率越高

posted @ 2022-04-06 10:39  萝卜不会抛异常  阅读(22)  评论(0编辑  收藏  举报