什么是线程?
线程是一个程序内部中一条执行流程,程序中只有一条执行流程,那这个程序就是单线程,如若是多条执行流程,那就是多线程。
如何创建多线程
JAVA中是通过java.lang.Thread类的对象来代表线程的。
创建方式一:
继承Thread类
1.定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法
2.创建MyThread类的对象
3.调用线程对象的start()方法启动线程
方式一的优缺点
优点:编码简单
缺点:线程类已经继承了Thread,无法继承其他类,不利于功能扩展。
创建方式二
实现Runnable接口
1.定义一个线程任务类MyRunnable接口,重写run()方法
2.创建MyRunnable任务对象
3.把MyRunnable任务对象交给Thread处理
4.调用线程对象的start()方法启动线程
方式二的匿名内部类写法
1.创建Runnable的匿名内部类对象
2.再交给Thread线程对象
3.再调用线程对象的start()方法启动线程
方式二的优缺点
优点:任务类只是实现接口,可以继续继承其他类,实现其他接口,功能扩展强。
缺点:需要多一个Runnable对象
前两种创建方式都存在一个问题,如果线程执行完需要拿取一些数据,他们重写的run()方法均不能返回结果,所有JDK5.0提供了Callable接口和FutureTask类来实现。
创建方式三
利用Callable接口、FutureTask类来实现
1.创建任务对象
定义一个类实现Callable接口,重新call方法,封装想要做的事情,和返回的数据
把Callable类型的对象封装成FutureTask(线程任务对象)
2.把线程任务对象交给Thread对象
3.调用Thread对象的start()方法启动线程
4.线程启动完后,通过FutureTask对象的get方法去获取线程任务执行的结果
方式三的优缺点
优点:线程任务类只是实现接口,可以继续继承其他类,功能扩展强,可以在线程执行结束后获取执行结果
缺点:编码复杂
Thread的常用方法
public void run() :线程的任务方法
public void start() :启动线程
public String getName():获取当前线程的名称,线程名称默认是Thread-索引
public void setName (String name): 为线程设置名称
public static Thread currentThread :获取当前执行的线程对象
public static void sleep(long time) :让当前线程休眠多少毫秒后执行
public final void join() :让调用当前这个方法的线程先执行完
Thread的常见构造器
public Thread(String name) :指定当前线程名称
public Thread(Runnable target) :封装Runnable对象为线程对象
public Thread(Runnable target, String name) :封装Runnable对象为线程对象,并指定名称
什么是线程安全?
当多个线程同时操作同一个资源的时候,可能会出现业务安全问题。
线程同步
线程同步是用来解决线程安全问题,线程同步解决线程安全问题的思想是让多个线程先后依次访问共享资源。
方式一
同步代码块
作用:把访问的共享资源上锁,以保证线程安全
synchronized(同步锁) {
访问共享资源的核心代码
}
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才能进来执行。
同步锁注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
锁对像的使用规范
建议使用共享资源为锁对象,对于实例方法建议用this作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。
方法二
同步方法
作用:把访问共享资源的核心方法上锁,以保证线程的安全。
修饰符 synchronized 返回值类型 方法名称(形参列表) {
操作共享资源的代码
}
原理:每次只能有一个线程进入,执行完毕后自动解锁,其他线程进来执行。
同步方法底层原理
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
如果方法是实例方法:同步方法默认用this作为的锁对象。
如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
方式三
Lock锁
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象
构造器
public ReentrantLock() :获得Lock锁的实现类对象
Lock常用方法
void lock():获得锁
void unlock():释放锁
什么是线程通信?
当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,达到互相协调,避免无效的资源争夺。
Object类的等待和唤醒方法
这些方法对当前同步锁对象就行调用
void wait():让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法
void notify(): 唤醒正在等待的单个线程
void notifyAll():唤醒正在等待的所有线程
线程池
线程池是一个可以复用线程的技术。
线程池工作原理
线程池内部会有一个容器,存储几个核心线程,假设有3个核心线程,这3个核心线程可以处理3个任务。 假设第1个线程的任务执行完了,那么第1个线程就空闲下来了,有新的任务时,空闲下来的第1个线程可以去执行其他任务。
如何创建线程池
自JDK 5.0起提供了代表线程池的接口ExecutorService,这个接口下有一个实现类叫ThreadPoolExecutor类,使用ThreadPoolExecutor类就可以用来创建线程池对象。
ThreadPoolExecutor构造器
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数一:corePoolSize : 指定线程池的核心线程的数量。
参数二:maximumPoolSize:指定线程池的最大线程数量。
参数三:keepAliveTime :指定临时线程的存活时间。
参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)。
参数五:workQueue:指定线程池的任务队列。
参数六:threadFactory:指定线程池的线程工厂。
参数七:handler:指定线程池的任务拒绝策略。
ExecutorService的常用方法
void execute(Runnable command):执行 Runnable 任务
Future<T> submit(Callable<T> task):执行 Callable 任务,返回未来任务对象,用于获取线程返回的结果
void shutdown():等全部任务执行完毕后,再关闭线程池
List<Runnable> shutdownNow():立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务
新任务拒绝策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,默认策略
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务,把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy:主线程负责调用任务的run()方法从而绕过线程池直接执行
Executors
Executors是线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
public static ExecutorService newFixedThreadPool(int nThreads):
创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
public static ExecutorService newSingleThreadExecutor():
创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
public static ExecutorService newCachedThreadPool():
线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):
创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。
这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创造的线程池对象。
大型并发数据系统环境中使用Executors如果不注意可能会出现系统风险。
补充
线程属于进程,一个进程中可以同时运行很多个线程,进程的多个线程其实是并发和并行同时执行的。
并发
进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。(cpu轮询执行线程)
并行
在同一个时刻上,同时有多个线程在被CPU调度执行。
Java线程的状态
Java总共被定义了6种状态,都定义在Thread类的内部枚举类中
线程的6种状态总结
NEW(新建):线程刚被创建,但是并未启动。
Runnable(可运行):线程已经调用了start(),等待CPU调度
Blocked(被阻塞):线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态
Waiting(无限等待):一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒
Timed Waiting(计时等待):同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们将进入Timed Waiting状态
Teminated(被终止): 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。