Java多线程

Java多线程

为什么要在代码中引入多线程?

  • 可以使用多个线程来处理任务,提高效率
  • 如果阻塞点过多,一个线程会处理不过来;例如TCP服务器在等待建立连接的时候会阻塞,而整个流程不能因为这个而卡死在这里,所以引入另外的线程去处理另外的任务

哪些地方是线程安全问题的风险点?

线程对共享数据修改的部分,必须考虑是否线程安全!!!

并发编程的优缺点

为什么要使用并发编程?(优点)

  • 充分利用多核CPU的计算能力:通过并发编程的形式可以将多核CPU的计算能力发挥到极致,大大的提高性能
  • 方便业务拆分,提高系统的并发能力和性能:面对复杂的业务模型,并发程序会比串行程序更适合业务需求

并发编程有什么缺点?

并发编程的目的是为了能提高程序的执行效率,提高运行速度,但是并发编程有时候还会遇到很多问题,如:内存泄漏,上下文切换,线程安全,死锁等问题.

并发编程的三要素是什么?在Java程序中怎么保证多线程安全?

并发编程的三要素:

  • 原子性:指一个或多个操作要么全部执行成功,要么全部执行失败
  • 可见性:一个线程对共享变量的修改,另一个线程能立刻看到
    • 为了提高效率,JVM通常会将数据在的工作内存中执行,这样就造成了共享变量的改变在多线程之间不能被及时看到,就会造成可见性问题.
    • 例如共享变量(sum)的++操作,
        1. 读:线程读取主内存中的sum的值,拷贝到工作内存
        2. 修改:线程在工作内存中修改sum的值
        3. 写:线程把sum修改后的值写回主内存

Java多线程_第1张图片

  • 有序性:程序执行的顺序按照代码的先后顺序执行(处理器可能会对指令进行重排序)
    • 例如:new()对象的操作可以分解为三条指令,第2和3步就有可能会发生重排序:
        1. 分配对象内存空间(堆);
        2. 对象的初始化;
        3. 对象赋值给变量(变量引用);

解决多线程安全的办法:

  1. synchronized,Lock可以解决原子性问题
  2. volatile,Lock可以解决可见性和有序性问题;

为什么代码会重排序?

在执行程序的时候,为了提供性能,处理器和编译器常常会对指令进行重排序,但是不能随便进行重排序,需要满足两个条件

  1. 在单线程环境下不能改变程序运行的结果
  2. 存在数据依赖关系的不允许进行重排序

重排序不会影响单线程环境的执行结果,但是会破坏多线程的执行语意

并行,并发,串行有什么区别?

  • 并发:多个任务在同一个CPU上执行,系统按时间片轮转调度使得这些任务交替轮流执行,从宏观上看这些任务是同时执行的
  • 并行:同一个时间点,多个处理器或多核处理器同时处理多个任务,这才是真正意义上的"同时执行"
  • 串行:多个任务按顺序一个一个执行

什么是上下文切换?

当前任务在执行完CPU时间片后切换到另一个任务之前会先保存自己的状态,以便下次时间片轮转再次回到这个任务的时候,可以再次加载这个任务的状态.任务从保存到再加载的过程就是一次上下文切换

Linux相比于其他操作系统有很多优点,其中有一项就是:其上下文切换和模式切换的时间消耗非常小

守护线程和用户线程有什么区别?

  • 用户(User)线程:运行在前台,执行具体的任务,如程序的主线程等都是用户线程
  • 守护(Daemon)线程:运行在后台,为其他前台线程服务.一旦所有的用户线程都结束运行,守护线程也会结束工作

最明显的区别是:用户线程结束,JVM退出,不管这个时候还有没有守护线程运行.守护线程不会影响JVM的退出.

注意事项:

  1. setDaemon(true)必须在start()方法之前执行,否则会抛出ILLegalThreadStateException异常
  2. 在守护线程中产生的新线程也是守护线程
  3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑;
  4. 守护线程不能依靠finally()语句块中的内容来确保关闭或者释放资源,因为一旦用户线程结束,守护线程会随着JVM一起结束工作,所以守护线程中的finally()语句块可能无法执行

创建线程的四种方式?

  • 继承Thread类

  • 实现Runnable接口

  • 实现Callable接口

  • 使用Executors工具类创建线程池

  • 继承自Thread类

​ 步骤:

	1. 定义一个Thread类的子类,重写run()方法,将相关操作实现,run()方法就是线程要执行的业务逻辑方法;
	1. 创建自定义的线程子类对象
	1. 调用子类实例的start()方法来启动线程
public class MyThread extends Thread {
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() +
" run()方法正在执行...");
	}
} 

public class TheadTest {
    public static void main(String[] args) {
		MyThread myThread = new MyThread();
		myThread.start();
		System.out.println(Thread.currentThread().getName() +
" main()方法执行结束");
  • 实现Runnable接口

​ 步骤:

1. 定义一个类实现Runnable接口,并重写run()方法
1. 创建MyRunnable的实例myRunnable,以myRunnable的对象作为target创建Thread对象,该Thread对象才是最终的线程对象
1. 调用线程对象的start()方法
public class MyRunnable implements Runnable {
	@Override
    public void run() {
		System.out.println(Thread.currentThread().getName() +
" run()方法执行中...");
	}
} 
public class RunnableTest {
	public static void main(String[] args) {
		MyRunnable myRunnable = new MyRunnable();
		Thread thread = new Thread(myRunnable);
		thread.start();
		System.out.println(Thread.currentThread().getName() +
" main()方法执行完成");
	}
}
  • 实现Callable接口

​ 步骤:

1. 创建实现Callable接口的类MyCallable
1.MyCallable类的实例为参数创建FutureTask对象
1.FutureTask对象作为参数创建Thread对象
1. 调用线程对象的start()方法
public class MyCallable implements Callable {
	@Override
	public Integer call() {
		System.out.println(Thread.currentThread().getName() +
" call()方法执行中...");
		return 1;
	}
} 
public class CallableTest {
	public static void main(String[] args) {
		FutureTask futureTask = new
FutureTask(new MyCallable());
		Thread thread = new Thread(futureTask);
		thread.start();
		try {
			Thread.sleep(1000);
			System.out.println("返回结果 " + futureTask.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		} 
		System.out.println(Thread.currentThread().getName() +
" main()方法执行完成");
	}
}
  • 使用Executors工具类创建线程池

Executors提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口

主要有newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduleThreadPool这四种线程池

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() +
" run()方法执行中...");
	}
} 
public class SingleThreadExecutorTest {
	public static void main(String[] args) {
		ExecutorService executorService =
Executors.newSingleThreadExecutor();
		MyRunnable runnableTest = new MyRunnable();
		for (int i = 0; i < 5; i++) {
			executorService.execute(runnableTest);
		} 
		System.out.println("线程任务开始执行");
		executorService.shutdown();
	}
}

Runnable和Callable有什么区别?

相同点

  • 都是接口
  • 都可以用来编写多线程程序
  • 都采用Thread.start()启动线程

主要区别

  • Runnable接口的run()方法没有返回值,Callable接口的call()方法有返回值,是个泛型,和FutureTask配合可以用来获取异步执行的结果
  • Runnable接口run()方法只能抛出运行时异常,且无法捕捉处理;Callable接口call()方法允许抛出异常,可以获取异常信息

线程的run()和start()有什么区别?

  • start()方法用于启动线程,run()方法用于执行线程运行时的代码.run()方法可以重复调用,但是start()方法只能调用一次
  • start()方法用于启动线程,真正实现了多线程的运行.调用start()方法时无需等待run()方法方法体代码执行完毕,可以直接继续执行其他代码;此时线程是就绪态,并没有开始运行,然后通过Thread类调用方法run()来完成其运行状态,run()方法运行结束,此线程就终止了
  • run()方法是在本线程里的,只是线程的一个函数.如果直接调用run()方法,就相当于调用了一个普通的函数;直接调用run()方法时,必须等待run()方法执行结束才能执行下面的代码,所以执行路径还是一条,没有多线程的特征.所以在线程启动时,要调用start()方法而不是run()方法

为什么调用start()方法时会执行run()方法,为什么不能直接调用run()方法?

多线程工作:new 一个Thread类,线程就会进入新建状态;调用start()方法,会启动一个线程并使线程进入就绪状态;当分配到时间片后就可以开始运行了.start()会执行线程的准备工作,然后自动调用run()方法执行其里面的内容

如果直接调用run()方法,会把run()方法当成是一个main线程下的普通方法去执行,并不会在某个线程中执行它,不属于多线程的工作

什么是Callable和Future?

Callable接口类似于Runnable,但是Runnable接口的run()方法不会返回结果,并且无法抛出返回结果的异常;而Callable接口的功能更强大一些,call()方法有返回值,这个返回值可以被Future拿到(Future可以拿到异步执行任务的返回值),并且可以抛出异常获得异常信息

Future接口表示异步任务,是一个可能还没有完成的异步任务的结果.所以说Callable用于产生结果,Future用于获取结果
什么是Callable和Future?

Callable接口类似于Runnable,但是Runnable接口的run()方法不会返回结果,并且无法抛出返回结果的异常;而Callable接口的功能更强大一些,call()方法有返回值,这个返回值可以被Future拿到(Future可以拿到异步执行任务的返回值),并且可以抛出异常获得异常信息

Future接口表示异步任务,是一个可能还没有完成的异步任务的结果.所以说Callable用于产生结果,Future用于获取结果

你可能感兴趣的:(Java多线程,java,开发语言)