文章目录
多线程
多线程的优点:
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率。
- 改善程序结构,将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
程序、进程、线程
程序:为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程,有它自身的产生、存在和消亡的过程(生命周期)。进程是资源分配的最小单位,系统在运行时会为每个进程分配不同的内存区域。
线程:进程进一步细化为线程,是程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,即为支持多线程。
- 线程是调度和执行的最小单位,每个线程拥有独立的运行栈和程序计数器,线程切换的开销小(进程拥有独立的堆和方法区)。
- 一个进程中的多个线程共享相同的内存单元/内存地址空间,从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源会带来安全隐患,
并行和并发
并行:多个CPU同时执行多个任务,比如多个人同时做不同事情。
并发:一个CPU采用时间片同时执行多个任务,比如一个人做多件事。(宏观上并行,微观上串行)
同步与异步
传送门:(讲的不错)
https://blog.csdn.net/hasijingsi/article/details/105285426
线程常用方法
- void start():启动线程,并执行对象的run()方法。
- run():线程在被调度时执行的操作。通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中。
- String getName():返回线程的名称。
- void setName(“xxx”):设置该线程的名称(或者根据构造函数命名)。
- static Thread currentThread():返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类。
- yield():释放当前CPU的使用权(可能释放后又得到/抢到了使用权)。
- join():在线程A调用线程B的join()方法,此时线程A进入阻塞状态,直到线程B执行完毕。
- stop():强制线程生命期结束(不建议使用)。
- sleep(long millitime):阻塞线程millitime毫秒(睡眠)。
- isAlive():判断线程是否存活。
线程优先级
同优先级线程组成先进先出队列(先到先服务),使用时间片策略。
对高优先级,使用优先调度的抢占式策略。
优先等级:
public static final int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public static final int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public static final int MAX_PRIORITY = 10;
涉及到的方法:
getPriority():获取当前线程优先级;
setPriority(int n):设置当前线程优先级;
高优先级线程要抢占低优先级线程CPU的执行权,但是只能从概率上讲,高优先级线程高概率下被执行,但不意味着必须高优先级线程执行完后低优先级线程才会执行。
线程分类
守护线程、用户线程
二者唯一区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
Java垃圾回收就是一个典型的守护线程。
如果JVM中都是守护线程,则当前JVM将退出。
线程生命周期
JDK中Thread.State类定义了以下几种线程状态:
新建、就绪、运行、阻塞、死亡。
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
线程安全
多个线程执行的不确定性引起执行结果的不稳定;多个线程对数据的共享,会造成操作的不完整性,会破坏数据,即线程安全问题。
解决:当一个线程在操作共享数据时,其他线程不能参与进来,直到这个线程操作完毕,其他线程才可以参与进来开始操作数据。(这种情况即使该线程出现阻塞,也不能改变)
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
出现死锁后,不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续。使用同步时,要避免出现死锁。
synchronized
Java中,通过同步机制来解决线程的安全问题。
方法一:同步代码块(synchronized)
synchronized (同步监视器){
//需要被同步的代码(操作共享数据的代码)
}
同步监视器:俗称锁。任何一个类的对象都可以充当锁。(要求多个线程必须共用同一把锁)
实现Runnable接口方式中同步监视器常用this,继承Thread类方式中同步监视器常用(类名.class)。(因为this不唯一)
好处:解决了线程安全问题。坏处:效率低。
方法二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,可以将此方法声明为同步的。同步监视器默认为this。
class MyThread2 implements Runnable{
@Override
public void run() {
method();
//相关操作
}
private synchronized void method (同步监视器){
//需要被同步的代码(操作共享数据的代码)
}
}
如果是继承Thread类的方式,则需要将方法声明为static。
同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
非静态的同步方法,同步监视器是this。
静态的同步方法,同步监视器是当前类本身。
利用此方式即可实现线程安全的单例模式。
Lock(锁)
JDK5.O开始,Java通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyThread2 implements Runnable{
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try{
//加锁
lock.lock();
//相关操作
...
}finally {
//释放锁
lock.unlock();
}
}
}
Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock锁对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁,释放锁。
synchronized和lock的异同?
同:二者都可以解决线程安全问题。
异:synchronized机制在执行完相应的同步代码后可以自动释放锁,lock则需要手动启动同步(加锁lock()),同时结束同步(释放锁unlock())也需要手动实现。
线程通信
线程通信的三个方法,定义在java.lang.Object类中:
- wait():让当前线程进入阻塞状态,同时,wait()也会让当前线程释放它所持有的锁。直到其他线程调用notify()或notifyAll(),当前线程才能被唤醒(进入“就绪状态”)。
- notify() :唤醒优先级高的那个进程。
- notifyAll():唤醒全部进程。
三个方法必须使用在同步代码块或同步方法中。
三个方法的调用者必须是同步代码块或同步方法中的同步监视器(默认this,通常省略)。
sleep()和wait()的异同?
相同点:二者都可以使得当前线程进入阻塞状态。
不同点:
- 声明位置不同:Thread类中声明sleep(),Object类声明wait()。
- 调用的要求不同,sleep()可以在任何需要的场景下调用,wait()必须在同步代码块或同步方法中。
- 如果二者都使用在同步代码块或同步方法中,sleep()不会释放锁,但wait()会释放锁。
创建线程的四种方法
Thread
1.创建一个继承于Thread类的子类;
2.重写Thread类的run()方法,将此线程执行的操作声明在run()方法中;
3.创建Thread类的子类的对象;
4.通过此对象调用start()方法。
public class Day06 {
public static void main(String[] args) {
MyThread myThread = new MyThread("线程一");
MyThread myThread1 = new MyThread("线程二");
myThread.start();
myThread1.start();
//如下操作仍然是在main线程中执行
for (int i = 0; i < 100; i++) {
if (i % 2 == 1) System.out.println(i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) System.out.println(i);
System.out.println(Thread.currentThread().getName());
}
}
}
start方法启动当前线程,调用当前线程的run()方法。
不能通过直接调用run()方法的方式启动线程。
继承方式(创建Thread类的匿名子类的方式):
new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) System.out.println(i);
System.out.println(Thread.currentThread().getName());
}
}
}.start();
Runnable
1.创建一个实现了Runnable接口的类;
2.实现类去实现Runnable中的抽象方法;
3.创建实现类的对象;
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象;
5.通过Thread类的对象调用start()方法;
MyThread2 myThread2 = new MyThread2();
Thread myThread3 = new Thread(myThread2);
myThread3.start();
class MyThread2 implements Runnable{
@Override
public void run() {
//相关操作
}
}
两种创建线程方式的比较:
开发中,优先选择实现Runnable接口的方式。
原因:实现方式没有类的单继承性的局限性;实现方式更适合来处理多个线程有共享数据的情况;
二者联系:Thread类本身也实现了Runnable接口;两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()中。
Callable接口
JDK5.0后新增Callable接口来创建线程。和Runnable相比,Callable功能更强大:
- 相比run()方法,可以有返回值;
- 方法可以抛出异常;
- 支持泛型的返回值;
- 需要借助FutureTask类,比如获取返回结果。
FutureTask类来自Future接口:
- 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
- FutrueTask是Futrue接口的唯一的实现类
- FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
public class Day06 {
public static void main(String[] args) {
//3.新建Callable实现类的对象
MyThread3 thread3 = new MyThread3();
//4.将此对象作为参数传入FutureTask的构造器中,创造FutureTask对象。
FutureTask<Object> objectFutureTask = new FutureTask<Object>(thread3);
//5.将创建的 FutureTask对象作为参数传入Thread类中,创建Thread对象,并调用其start()方法
new Thread(objectFutureTask).start();
try {
//获取重写后Call()方法中的返回值
//get方法的返回值即为FutureTask构造器参数Callable实现类重写的call方法的返回值
Object o = objectFutureTask.get();
System.out.println("总和为"+o);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
//1.创建实现类实现Callable接口
class MyThread3 implements Callable<Object> {
@Override
public Object call() throws Exception {
//2.将操作写入call()方法
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小 ;
- maximumPoolSize:最大线程数 ;
- keepAliveTime:线程没有任务时最多保持多长时间后会终止 ;
- ……
JDK 5.0起提供了线程池相关API:ExecutorService和Executors
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable
- Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable
- void shutdown() :关闭连接池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运 行命令或者定期地执行。
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) System.out.println(i);
System.out.println(Thread.currentThread().getName());
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//设置线程池属性
// ThreadPoolExecutor service = (ThreadPoolExecutor) executorService;
// service.setCorePoolSize(15);
// service.setMaximumPoolSize(15);
//2.执行指定的线程操作,需要提供实现Runnable接口或Callable接口的对象
executorService.execute(new NumberThread());//适合Runnable
//executorService.submit();//适合Callable
//3.关闭线程池
executorService.shutdown();
}
}