并发编程
1、并行和并发有什么区别?
并行和并发都是指多个任务同时执行的概念,但是它们之间有着明显的区别
并行:多个任务在同一时间同时执行,通常需要使用多个处理器或者多核处理器来实现。例如,一个多核CPU的计算机同时执行多个进程或多个线程是,就是采用并行的方式来处理任务的,这样能提高计算机的处理效率。
并发:多个任务同时进行,但是这些任务的执行是交替进行的,即一个任务执行一段时间后,再执行另一个任务。它是通过操作系统的协作调度来实现各个任务的切换,达到看上去同时进行的效果。例如一个多线程程序中的多个线程就是同时运行的,但是因为一个CPU只能处理一个线程,所以在任意时刻只有一个线程在执行,线程之间会通过竞争的方式来获取CPU的时间片。
2、线程有几种创建方式
在Java中,线程的创建主要有以下4种方式:
1、继承Thread类
通过继承Thread
类并重写其run()
方法,可以创建一个新的线程。然后,可以创建这个类的实例并调用其start()
方法来启动线程。
public class Test3 {
public static void main(String[] args) {
// 创建实例。
MyThread myThread = new MyThread();
// 设置线程的name
myThread.setName("myThread");
// 调用start()方法来启动线程
myThread.start();
// 获取主线程的name
System.out.println(Thread.currentThread().getName());
}
}
// 1、通过继承Threadl类创建线程
class MyThread extends Thread {
@Override
public void run() {
// 线程执行的代码,获取线程的name
System.out.println(Thread.currentThread().getName());
}
}
2、实现Runnable接口
通过实现Runnable
接口并重写其run()
方法,可以创建一个任务对象。然后,可以创建Thread
类的实例并将任务对象作为参数传递给其构造函数,最后调用start()
方法来启动线程。这种方式允许你继承其他类,因为Java不支持多重继承。
public class Test3 {
public static void main(String[] args) {
// 创建任务对象。
MyThread2 myThread2 = new MyThread2();
// 创建Thread类的实例并将任务对象作为参数传递给其构造函数
Thread thread = new Thread(myThread2);
// 设置线程的name
thread.setName("myThread2");
// 调用start()方法来启动线程
thread.start();
// 获取主线程的name
System.out.println(Thread.currentThread().getName());
}
}
// 2、通过实现Runnable接口来创建线程
class MyThread2 implements Runnable {
@Override
public void run() {
// 线程执行的代码
System.out.println(Thread.currentThread().getName());
}
}
3、实现Callable接口
Callable
接口类似于Runnable
,但允许线程执行的任务有返回值,并且可以抛出异常。Future
接口表示异步计算的结果;·可以使用ExecutorService
来提交Callable
任务,并使用返回的Future
对象来检索结果或检查任务是否完成。
public class Test3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1、通过FutureTask启动线程
FutureTask<String> futureTask = new FutureTask<>(new MyThread3());
Thread thread = new Thread(futureTask);
thread.setName("myThread3");
thread.start();
// 等待任务完成并获取结果
System.out.println(futureTask.get());
// 2、通过ExecutorService启动线程
// 创建一个ExecutorService
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 提交Callable任务,并获取Future对象
Future<String> future = executorService.submit(new MyThread3());
// 等待任务完成并获取结果
System.out.println(future.get());
// 关闭ExecutorService
executorService.shutdown();
}
}
// 3、通过实现Callable接口来创建线程
class MyThread3 implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName();
}
}
4、使用线程池
尽管线程池本身不是直接创建线程的方式,但它是一种管理线程的高效方式。你可以使用ExecutorService框架(如Executors类提供的静态工厂方法)来创建和管理线程池。线程池中的线程可以被重复利用,以执行多个任务,从而减少了线程创建和销毁的开销。
3、Java线程启动为什么是调用start()方法而不是直接调用run()方法?
JVM
执行start()
方法,会先创建一个线程并使其经历其生命周期的各个阶段(例如,新建、就绪、运行、阻塞、死亡等),当新线程被启动后,它会自动调用run()
方法,这才会起到多线程的效果;如果直接调用run()
方法,就相当于调用了一个普通方法,程序中依然只有主线程这一个线程。
通过调用start()
方法启动新线程,可以实现并发执行多个任务,提高程序的效率。如果我们直接调用run()
方法,那么这些任务将只能在主线程中顺序执行,无法实现并发。但是,一个线程的start()方法不能被重复调用,而run()方法可以。
4、线程有哪些常用的调度方法
1、设置线程优先级:
Java中的线程优先级通过整数表示,范围从1到10。可以使用Thread
类的setPriority()
方法来设置线程的优先级。更高优先级的线程倾向于比低优先级的线程更早地执行,但线程优先级并不保证绝对的执行顺序,而是提供了一个提示,让调度器更可能将CPU时间分配给优先级较高的线程。
2、线程调度相关的线程状态转换方法:
thread.start()
: 启动一个新线程,使其进入就绪状态。调用此方法后,线程将自动调用其run()
方法。
thread.sleep(long millis): 休眠,使当前线程(即调用该方法的线程)暂停执行一段时间(以毫秒为单位),当休眠结束后,线程自动转为就绪状态。
thread.yield(): 让步,暂停当前正在执行的线程对象,并执行其他线程。但具体哪个线程将恢复执行取决于操作系统的线程调度算法。
thread.join(): 等待,等待该线程终止。调用某线程的join()
方法将使得当前线程暂停执行,直到目标线程执行完毕。
object.wait(): 等待,Object类中的wait()方法,使当前线程(即调用该方法的线程)等待,直到其他线程调用此对象的notify()
方法或notifyAll()
方法。
object.wait(long): 等待,相比wait()方法多了一个超时参数,如果线程调用这个方法,没有在指定时间内被其他线程唤醒,那么这个方法会因超时而返回。
thread.interrupt(): 中断线程。如果线程在等待、睡眠或进行某些阻塞操作,则interrupt()方法会将其唤醒并抛出InterruptedException。
thread.isInterrupted()
thread.interrupted()
3、线程等待和通知:
object.notify(): 通知,唤醒在此对象监视器上等待的单个线程。选择是任意性的。
object.notifyAll(): 通知,唤醒在此对象监视器上等待的所有线程。
public class Test4 {
public static void main(String[] args) throws Exception {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
try {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "线程进入永久等待" + LocalTime.now());
// 使线程进入等待,直到被唤醒
lock.wait();
System.out.println(Thread.currentThread().getName() + "线程永久等待被唤醒" + LocalTime.now());
}
} catch (Exception e) {
e.printStackTrace();
}
}, "thread1");
Thread thread2 = new Thread(() -> {
try {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "线程进入永久等待" + LocalTime.now());
// 使线程进入等待,直到被唤醒,没有被唤醒则等待3s
lock.wait(3000);
System.out.println(Thread.currentThread().getName() + "线程永久等待被唤醒" + LocalTime.now());
}
} catch (Exception e) {
e.printStackTrace();
}
}, "thread2");
// 启动线程
thread1.start();
thread2.start();
// 休眠1s,让thread1先执行
Thread.sleep(1000);
synchronized (lock) {
// 随机唤醒在此对象监视器上的一个线程
lock.notify();
// 唤醒在此对象监视器上的所有线程
// lock.notifyAll();
}
// 使主线程暂停执行,直到thread1线程执行完毕。
thread1.join();
thread2.join();
}
}