JAVA基础课程
第十六天 线程基础
基础概念:程序,进程,线程
(1)程序(program)完成任务,用某种语音编写的一组指令集合
(2)进程(process)是程序的一次执行过程,或是正在运行的程序,是一个都给动态的过程,他又自己的生命周期。比如运行中的QQ,运行中的程序。是动态的。。进程作为资源分配的单位,系统在运行时为每个进程分配不同的内存区域
(3)线程(thread),进程可以进一步细化为线程,是一个程序内部的一条执行路径。
①若一个进程同一时间并行执行多个线程,就是支持多线程的
②线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计算机。线程切换开销比较小
③一个进程中的多个线程共享相同的内存单元——》它们从同一堆中分配对象,可以访问相同的变量和对象。
(4)单核CPU和多核CPU
单核CPU:一种假的多线程,因为一个时间单元内,也只能执行一个线程的任务
多核CPU:发挥多线程的效率,一个Java程序,至少三个线程:main(),gc(),异常处理。
(5)并行和并发
并行:多个CPU同时执行多个任务
并发:一个CPU同时执行多个任务
线程的创建与使用
方式1:
①创建一个继承Thread的子类
②重写Thread的run()方法
③创建继承Thread的子类的对象
④调用此对象的start()
作用:启动当前线程,调用当前线程的run()f方法
说明:(1)Thread的常用方法
start();启动当前线程
run();重写Thread中的run方法,将需要执行的业务写在此方法中
currentThread():静态方法,返回当前线程
getName():获取当前线程的名称
setName():设置当前线程的名称
yield():释放当前cpu的执行权
join():现在a中调用线程b的join(),此时,线程a进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
stop():强制停止当前线程,已过期
sleep(long milltime):让当前线程睡眠指定时间,在睡眠时间内,线程处于阻塞状态,并且不释放锁
isAlive():判断当前线程是否存活
(2)线程的调度
调度策略:
时间片,同优先级的线程组,使用时间片的策略
抢占式,优先级高的使用抢占式
(3)优先级
①优先级等级
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
②涉及的方法:
/**
* Changes the priority of this thread.
设置优先级
* <p>
* First the <code>checkAccess</code> method of this thread is called
* with no arguments. This may result in throwing a
* <code>SecurityException</code>.
* <p>
* Otherwise, the priority of this thread is set to the smaller of
* the specified <code>newPriority</code> and the maximum permitted
* priority of the thread's thread group.
*
* @param newPriority priority to set this thread to
* @exception IllegalArgumentException If the priority is not in the
* range <code>MIN_PRIORITY</code> to
* <code>MAX_PRIORITY</code>.
* @exception SecurityException if the current thread cannot modify
* this thread.
* @see #getPriority
* @see #checkAccess()
* @see #getThreadGroup()
* @see #MAX_PRIORITY
* @see #MIN_PRIORITY
* @see ThreadGroup#getMaxPriority()
*/
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
/**
* Returns this thread's priority.
* 返回优先级
* @return this thread's priority.
* @see #setPriority
*/
public final int getPriority() {
return priority;
}
(2)代码:
package com.test.course.threadtest;
/**
* 〈继承的Thread实现多线程〉
* @author PitterWang
* @create 2020/5/4
* @since 1.0.0
*/
public class ThreadTest {
public static void main(String[] args) {
HelloThread helloThread = new HelloThread();
helloThread.setName("线程1");
helloThread.start();
try {
helloThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//helloThread.start(); //Exception in thread "main" java.lang.IllegalThreadStateException
HelloThread1 helloThread2 = new HelloThread1();
helloThread2.setName("线程2");
helloThread2.start();
}
}
class HelloThread extends Thread{
static int stack = 100;
@Override
public void run() {
while (stack > 0){
try {
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
yield();
stack--;
System.out.println(Thread.currentThread().getName() + "还剩下"+ stack + "张票");
}
}
}
class HelloThread1 extends Thread{
static int stack = 100;
@Override
public void run() {
while (stack > 0){
try {
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
stack--;
System.out.println(Thread.currentThread().getName() + "还剩下"+ stack + "张票");
}
}
}
方式二:(实现Runnable方法
①创建一个实现Runnable的接口
②实现Runnable中的run方法
③创建实现Runnable类的对象
④作为参数传入到Thread类的构造器总,创建Thread的对象
⑤调用Thread的start的方法
(1)代码
package com.test.course.threadtest;
public class RunnableTest{
public static void main(String[] args) {
HelloRunnable helloRunnable = new HelloRunnable();
Thread thread = new Thread(helloRunnable);
thread.start();
Thread thread1 = new Thread(helloRunnable);
thread1.start();
}
}
class HelloRunnable implements Runnable{
int stack = 100;
@Override
public void run() {
while (stack > 0){
stack--;
System.out.println(Thread.currentThread().getName() + "还剩下"+ stack + "张票");
}
}
}
创建继承Thread和实现Runnable的比较,优先选择Runnable,
原因:1.接口可以实现多个,而继承只能继承一个。
2.实现的方式更适合来处理多个线程有共享数据的情况
线程的生命周期
新建(new)—>就绪(runnable)——>运行(running)——>阻塞(block)——>死亡(dead)
线程的同步
问题:多个线程执行的不确定引起执行结果的不稳定
多个线程对同一个数据进行共享,会照常操作不完整性,会破坏数据
处理:当线程a去操作共享数据时,其他线程不要操作,直到a操作完。在java中就有同步机制来出来线程安全问题
(1)同步代码块:
synchronized (同步监视器){
}
说明:①操作共享的代码时,既为需要被同步的代码
②共享数据,就是多个线程要操作的变量
③同步监视器(锁),任何一个类的对象,都可以存当锁,但是必须多个线程必须用一把锁
(2)同步方法
public synchronized void run() {
}
线程同步的优缺点:
优点:解决线程安全
缺点:效率底
用单例模式中的线程安全的懒汉模式实现:
package com.test.shejimoshi;
/**
* 〈懒汉式〉 --线程安全的
*
* @author PitterWang
* @create 2020/4/29
* @since 1.0.0
*/
public class SingletonLazy {
private static SingletonLazy singletonLazy;
private SingletonLazy(){
}
public SingletonLazy getInstance(){
if(singletonLazy == null){
synchronized (SingletonLazy.class){
if(singletonLazy == null){
singletonLazy = new SingletonLazy();
}
}
}
return singletonLazy;
}
}
(3)LOCK锁
①实例化ReentrantLock
②用实例化的对象加锁 lock.lock();
③解锁 lock.unlock();
package com.test.course.threadtest;
import java.util.concurrent.locks.ReentrantLock;
/**
* 〈Lock〉
*
* @author PitterWang
* @create 2020/5/4
* @since 1.0.0
*/
public class LockTest implements Runnable{
private int tick = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
try {
while (tick > 0){
tick--;
System.out.println(Thread.currentThread().getName() + "还剩下"+ tick + "张票");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
Thread thread = new Thread(lockTest);
thread.start();
Thread thread2 = new Thread(lockTest);
thread2.start();
}
}
synchronized和Lock的区别
两者都可以解决线程安全问题
synchronized可以执行完自动释放锁
Lock需要手动启动同步,并且必须手动解锁。只有代码块锁
线程的通信
涉及到的方法
wait():一旦执行,当前线程就进入到阻塞状态,并且释放同步监视器锁
notify():一旦执行,就唤醒被wait的一个线程,如果多个线程被wait,就唤醒优先级高的
notifyAll():一旦执行,就唤醒所有wait的线程
注意:①三个方法必须使用在同步代码块或者同步方法中
②三个方法的调用者必须时同步代码块或者同步方法的同步监视器对象
③三个方法定义在Object类里
sleep和wait的区别:
相同点:一旦调用,都进入了阻塞状态
不同点:①sleep是Thread的方法,wait是Object的方法
②sleep任何场景都可以调用,wait只能在同步代码块中用
③sleep不释放锁,wait释放锁
代码场景:生产者消费者
package com.test.course.threadtest;
/**
* 〈生产者和消费者--线程间的通信〉
*
* @author PitterWang
* @create 2020/5/4
* @since 1.0.0
*/
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
productor.setName("生产者1");
Customer customer = new Customer(clerk);
customer.setName("消费者1");
customer.start();
productor.start();
//customer.start();
}
}
/**
* 店员
*/
class Clerk{
private int productNum = 0;
/***
* 生产
*/
public synchronized void productProduct() {
if(productNum < 20){
productNum ++;
System.out.println(Thread.currentThread().getName() + "开始生产第"+productNum+"产品");
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 消费
*/
public synchronized void customerProduct() {
if(productNum > 0){
System.out.println(Thread.currentThread().getName() + "开始消费第"+productNum+"产品");
productNum --;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 消费者
*/
class Customer extends Thread{
private Clerk clerk;
public Customer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "消费产品");
while (true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.customerProduct();
}
}
}
/**
* 生产者
*/
class Productor extends Thread{
private Clerk clerk;
public Productor(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "生产产品");
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.productProduct();
}
}
}
JDK5.0新增线程的创建方式
(1)实现Callable
使用方法:
①创建一个实现Callable的实现类
②实现call方法,将逻辑写在call方法中
③创建实现Callable的实现类的对象
④将实现Callable的实现类的对象传到FutureTask构造器中
⑤将FutureTask对象传到Thread构造器中,并用start启动线程
说明:
①与Runnable比,Callable更强大,
》1.有返回值
》2.可以抛出异常
》3.支持泛型
代码实现:
package com.test.course.threadtest;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.ReentrantLock;
/**
* 〈Callable〉
*
* @author PitterWang
* @create 2020/5/4
* @since 1.0.0
*/
public class CallableTest implements Callable {
private int tick = 100;
@Override
public Object call() throws Exception {
try {
while (tick > 0){
tick--;
System.out.println(Thread.currentThread().getName() + "还剩下"+ tick + "张票");
}
}catch (Exception e){
e.printStackTrace();
}
return "WanCheng";
}
public static void main(String[] args) {
CallableTest callableTest = new CallableTest();
FutureTask<String> futureTask = new FutureTask<>(callableTest);
Thread thread = new Thread(futureTask);
thread.start();
FutureTask<String> futureTask1 = new FutureTask<>(callableTest);
Thread thread1 = new Thread(futureTask1);
thread1.start();
try {
String s = futureTask.get();
System.out.println(s);
String s2 = futureTask1.get();
System.out.println(s2);
}catch (Exception e){
e.printStackTrace();
}
}
}
(2)线程池(从网上找到~~~以后等学完基础课件,研究并发的时候深入研究)
① 线程池的创建线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。Java 5+中的 Executor 接口定义一个执行线程的工具。它的子类型即线程池接口是 ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,因此在工具类 Executors 面提供了一些静态工厂方法,生成一些常用的线程池,如下所示:
(a)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
(b)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。如果希望在服务器上使用线程池,建议使用 newFixedThreadPool方法来创建线程池,这样能获得更好的性能。
(c) newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小。
(d)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
②线程池的优点降低资源消耗:重用存在的线程,减少对象创建销毁的开销。
提高响应速度。可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
附加功能:提供定时执行、定期执行、单线程、并发数控制等功能。
③线程池有那些状态RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。
④ Executor 和 Executors 的区别Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求。
Executor 接口对象能执行我们的线程任务。
ExecutorService 接口继承了 Executor 接口并进行了扩展,提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值。
使用 ThreadPoolExecutor 可以创建自定义线程池。
Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的完成,并可以使用 get()方法获取计算的结果
⑤ submit() 和 execute() 方法有什么区别接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和 Callable 类型的任务。
返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()没有
异常处理:submit()方便Exception处理
⑥ ThreaPoolExecutor(阿里推荐)
a.ThreadPoolExecutor构造函数重要参数分析ThreadPoolExecutor 3 个最重要的参数:
corePoolSize :核心线程数,线程数定义了最小可以同时运行的线程数量。
maximumPoolSize :线程池中允许存在的工作线程的最大数量
workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,任务就会被存放在队列中。
ThreadPoolExecutor其他常见参数:
eepAliveTime:线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
unit :keepAliveTime 参数的时间单位。
threadFactory:为线程池提供创建新线程的线程工厂
handler :线程池任务队列超过 maxinumPoolSize 之后的拒绝策略如下ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。