Java多线程知识点

IDEA:

①idea中的project,相当于eclipse中的workspace

②idea中的module,相当于eclipse中的project

在Idea中最顶级的是project,其次是module

注:

目前主流的大型项目都是分布式部署的,结构都是:一个项目对应多个Module的结构。这类项目一般划分:core Module,web Module,plugion Module,solr Module,模块之间彼此可以互相依赖,通过这些Module的命名就可以看出它们都属于同一项目业务下。

程序、线程、进程、多线程:

程序:为了完成特定任务、用某种语言编写的一组指令集合。即一段静态的代码,静态对象。

进程:程序的一次执行过程,或者说正在执行的一个程序。(具有生命周期)

线程:进程细化为多个线程,一个线程为一个进程的执行路径。

多线程:一个进程可以并行的执行多个线程,这个进程就叫做多线程。

注:

①进程作为资源分配的单位。线程作为调度和执行的单位。每个线程具有独立的运行栈和程序计数器。

②内存中的方法区和堆是一个进程一份,栈和程序计数器是每个线程一份,所以一个进程中的多个线程共享一个进程中的方法区和堆。

③一个Java程序至少有三个线程:main ()主程序线程,gc ()垃圾回收线程,异常处理线程。

并行、并发:

并行:多个CPU同时执行多个任务。

并发:单个CPU做多个任务,一段时间做一下任务1,一段时间做一下任务2.

多线程的优点:

①提高应用程序的响应。

②提升计算机系统CPU利用率。

③改善程序结构,将冗长复杂的进程改为多个线程,独立运行。

什么时候需要多线程:

①程序需要同时执行多个任务。

②程序需要实现一些等待的任务,如:用户输入、文件读写。

③需要后台运行的程序。

线程的创建:

①继承Thread

1.先创建一个继承于Thread类的子类

2.重写Thread类的run()方法。

3.创建子类对象。

4.通过此对象调用父类的start()方法。

//而其中的start()作用:①启动当前线程②调用当前线程的run方法

②实现Runnable接口,

注:

1.如果直接使用对象调用run()方法,则并不会创建新的线程,程序只会在main线程内执行。

2.不可以让已经调用start()的线程再次调用start()方法。否则会报线程状态异常。需要重新创建线程对象,让他调用start()方法。

3.如果创建的线程只用一次,可以创建Thread的匿名子类

new Thread(){

public void run(){

//重写run()方法

}

}.start();

Thread类的有关方法:

①void start():启动线程,执行run()方法。

②run():线程被调度时执行的操作。通常需要重写。

③String getName():返回线程的名称。

④void setName(String name):设置线程名称。

⑤static Thread currentThread():返回当前线程。在Thread子类中就是this。

⑥yield():释放当前CPU的执行权,假如线程1调用了yield(),则此时会释放该线程的CPU执行权,由CPU决定将执行权给下一个线程。

⑦join():在线程A中调用线程B的join()方法,此时线程A进入阻塞状态,直至线程B执行结束,线程A才可以继续执行。

⑧stop():强制使线程结束。//不建议使用

⑨sleep(long million time):使线程进入阻塞状态一段时间,要进行异常处理。形参单位是:毫秒。

⑩isAlive():判断当前线程是否存活。

线程的调度:

调度策略:

①:分配时间片,不同线程分配一段时间占用CPU。

②:抢占式:高优先级的线程抢占CPU。

Java的调度方式:

同优先级采用先来先服务的调度策略,对高优先级采用优先调度策略。

线程优先级等级:

MAX_PRIORITY:10

MIN_PRIORITY:5

NORM_PRIORITY:1 默认优先级

获取当前线程优先级:

getPriority()

设置当前线程优先级:

setPriority(int p)

注:

高优先级的线程要抢占低优先级线程CPU的执行权,但不意味着一定先执行完所有的高优先级线程再执行低优先级线程,只是提高了高优先级线程抢占CPU的概率,而并不是完全被其掌控。

创建线程的方式二:实现Runnable接口

步骤:

①创建一个实现Runnable接口的类

②实现类去实现Runnable内的抽象方法。

③创建实现类对象

④将此对象作为参数传递到Thread的构造器中,创建Thread的对象

⑤通过Thread类对象调用start()方法//这里的start方法会启动当前线程的run()方法

class PrimeRun implements Runnable {

long minPrime;

PrimeRun(long minPrime){

this.minPrime = minPrime;

}

public void run( ) {

//实现Runnable接口内的run方法

}

}

//在main函数中写如下句子:

PrimeRun p = new PrimeRun(143);

new Thread(p).start();

两种创建线程方式的比较:

开发中优先选择实现Runnable接口的方式创建线程。

原因:1.实现的方式没有单继承的局限性,实现类可以创建线程,而且不用使其父类为Thread。

2.实现的方式更适合实现多个线程共享数据的情况,无需定义静态变量。

3.Thread类本身也是实现了Runnable接口。

相同点;两种方式都需要重写run(),将线程要执行的内容都写入run()方法中。

线程的生命周期:

JDK中用Thread.State类定义了线程的集中状态:

新建:当一个Thread类或其子类对象被声明并创建时,新生的线程对象处于新建状态。

就绪:处于新建状态的线程Start()之后,将进入线程队列等待CPU分配时间片,此时他以进入就绪状态。

运行:当就绪的线程被调度获得CPU资源时,进入运行状态,run方法重写了线程的操作。

阻塞:在特殊情况下,让该线程让出CPU并临时中止自己的执行,进入阻塞状态。

死亡:线程完成了它全部工作或线程被提前强制性终止或出现异常造成终止。

线程的同步:

线程安全问题:当某个线程正在对一段共享数据进行操作时,尚未操作完成,其他线程参与进来,也进行操作,造成线程不安全。

解决方案:当一个线程在操作共享数据时,其他线程不可参与,指直到其完成操作。/即,上锁。

在Java中,我们通过同步机制,来解决线程安全问题。

同步的方式好坏处:

好处:同步的方式,解决了线程安全问题。

局限性:操作同步代码时,只能有一个线程操作,其他线程等待,相当于单线程操作。

解决线程安全问题方式一:同步代码块

synchronized(同步监视器){

//需要被同步的代码,

}

注:使用synchronized包需要同步的代码时,既不能多也不能少。

说明:

需要被同步的代码:操作共享数据的代码,就是需要被同步的代码。

共享数据:多个线程共同操作的变量。

同步监视器:俗称做锁。任何一个类对象都可以充当锁。

要求:要求多个线程必须共用统一把锁。

通常使用:Object obj = new Object();或者在实现Runnable接口的多线程通常用this作为锁,继承Thread的多线程用XXX.class作为锁。//XXX为当前类名

解决线程安全问题方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法内,我们便将该方法声明为同步方法。

即:

若是实现Runnable创建线程:

将方法声明成:public synchronized void show()这种形式即可。//千万别写成Synchronized

若是继承Thread创建线程:

将方法声明成:public static synchronized void show()这种形式即可。//保证每个对象都能共享数据

同步方法总结:

1.同步方法仍然涉及同步监视器,只是未显式声明。

2.非静态的同步方法,同步监视器是this。

静态的同步方法,同步监视器是类本身,也就是XXX.class

将懒汉式写成线程安全:

public static  Bank getInstance() {
    if (instance == null) {
​
         synchronized (Bank.class) {
             if (instance == null) {
                 instance = new Bank();
             }
         }
    }
    return instance;
}

死锁:

不同的线程分别占用着对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

出现死锁时:不会出现异常,不会出现提示,所有线程处于阻塞状态,无法继续。

解决线程安全问题方式三:Lock锁

Lock(锁):

Java通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口控制多个线程对共享资源访问的工具。

ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义。在实现线程安全的控制中,比较常用ReentrantLock,可以显式加锁、释放锁。

使用步骤:

①实例化ReentrantLock

private ReetrantLock lock = new ReentrantLock();

②将需要同步的代码放入try中,在try内部第一行启动锁,lock.lock();,

③在try外部写上finally{lock.unlock()} //释放锁

释放锁的操作有哪些?

①线程的同步方法、同步代码块执行结束。

②线程的同步方法、同步代码块遇到break、return。

③当前线程在同步代码块、同步方法中出现了未处理的Error、Exception导致程序终止。

④当前线程执行了wait()方法。

⑤使用lock.unlock()方法。

不会释放锁的操作有哪些?

①线程执行同步代码块、同步方法调用Thread.sleep()、Thread.yield(),暂停当前线程执行。

②线程执行同步代码块,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。

面试题:

synchronized与lock实现线程安全的异同?

同:二者都是用来解决线程安全问题的

异:

①Lock时显式锁(需要手动开启或关闭),synchronized是隐式锁。

②Lock只有代码块锁,synchronized既有代码块锁(同步代码块),也有方法锁(同步方法)。

③使用Lock锁,jvm将花费较少的时间调度线程,性能更好,扩展性更好。

优先使用顺序:

Lock → 同步代码块 → 同步方法

线程通信:

例子:使用两个线程交替打印1-100。

涉及到的方法:

wait():一旦执行此方法,当前线程进入阻塞状态,并释放同步监视器(锁)。

notify():一旦执行此方法,就会唤醒被wait()的一个线程。

notifyAll():一旦执行此方法,就会唤醒所有的被wait()的一个线程。

注:①以上三个方法都只能够出现在同步代码块、同步方法中,②调用者必须是其中的同步监视器。③以上三个方法定义在Object中,而不是Thread中。

面试题:sleep()与Wait()的异同

同:一旦执行两种方法,都会使当前线程进入阻塞状态。

异:

①两个方法声明位置不同,Thread类中声明sleep()。Object类中声明wait()。

②调用范围不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块、同步方法,由同步 监视器调用。

③调用sleep()时不会释放同步监视器(锁),而wait()会释放同步监视器(锁)。

多线程经典例题:消费者/生产者问题

生产者Productor将产品交给店员clerk,而消费者Customer从店员手里取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多产品,店员会把生产者叫停,如果店中有空位放置产品则再进行生产,如果店内已经没有产品,店员会让消费者等待一下,直到店中有了商品再次进行消费。

可能出现问题:

①生产者比消费者快,消费者会漏掉一些数据未收到。

②消费者比生产者快,消费者会取相同的数据。

新增线程创建方式:方式一------实现Callable接口

//创建线程方式3-----实现Callable接口
//1.创建一个实现Callable接口的实现类
//2.实现其中的call方法,将线程需要实现的操作写在call()中。
//3.创建一个Callable实现类对象
//4.创建一个FutureTask对象,并将Callable实现类对象作为参数传递到FutureTask的构造器中。
//5.创建一个Thread对象,并将FutureTask对象作为参数传递到Thread的构造器中。
//6.在main()函数中,调用futureTask.get(),使用Thread.start()启动线程。

注:其中futureTask.get(),是返回call的返回值。

为何实现Callable接口创建线程比实现Runnable接口创建线程强大?

①重写的call()方法可以有返回值,run()方法不可以有返回值。

②call()可以抛出异常,被外面操作捕获异常,获取异常信息。

③Callable支持泛型。

新增线程创建方式:方式二------使用线程池

提前创建好多个线程,放入线程池中,使用时直接获取,使用完再放回线程池中,可以避免频繁的创建销毁、实现线程重复利用。

JDK5.0提供了线程池相关API:ExecutorService和Executors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。

①Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池

②Exectutors.newFixedThreadPool(n):创建一个可重用固定线程个数的线程池。

③Exectutors.newSingleThredExectutor():创建一个只有单个线程的线程池

④Exectutors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令,或者定期执行命令

corePoolSize;线程池大小

maxmumPoolSize:最大线程数

keepAliveTime:线程没有任务时最多会保持多长时间后终止线程。

线程池创建步骤:

 //1.提供指定数量线程的线程池
 ExecutorService service = Executors.newFixedThreadPool(10);
​
 //设置线程池属性:
 ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;//将service强转
 service1.setCorePoolSize(15);//设置线程池最大容量
 service1.setKeepAliveTime();//没有任务时线程池最大存活时间。
 
 //2.重写线程内方法,执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
 service.execute(new NumberThread());//适用于Runnable
 service.execute(new NumberThread1());
// service.submit();//适用于Callable
​
 //3.关闭线程池
 service.shutdown();//关闭线程池
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java 多线程是指在一个程序中可以同时执行多个线程,并且每个线程独立运行,互不干扰。Java 多线程Java 平台提供的重要特性,常用于提高程序并发性能和解决复杂问题。 Java 多线程的重点知识点包括以下几个方面: 1. 线程的创建和启动:可以通过继承 Thread 类或实现 Runnable 接口来创建线程,并使用 start() 方法启动线程。 2. 线程的生命周期:线程的生命周期包括新建、就绪、运行、阻塞和死亡等阶段,可以通过调用 sleep()、wait()、join() 等方法来控制线程的状态转换。 3. 线程同步:多个线程之间可能会共享资源,为了保证数据的一致性和避免冲突,需要使用同步机制,如 synchronized 关键字、Lock 接口等。 4. 线程间通信:多个线程之间可以通过共享内存或者消息传递的方式进行通信,如使用 wait()、notify()、notifyAll() 等方法。 5. 线程池:线程池可以管理和复用线程,减少线程的创建和销毁开销,提高系统的性能。 6. 线程安全性:在多线程环境下,存在资源竞争和线程安全性问题,需要采取相应的措施来保证线程的安全性,如使用 synchronized 关键字、volatile 关键字等。 7. 线程调度:Java 提供了线程调度器来控制线程的执行顺序和优先级,可以使用 yield()、join()、setPriority() 等方法进行调度。 8. 线程异常处理:在多线程环境下,线程的异常需要处理和捕获,可以使用 try-catch 块来捕获异常。 综上所述,Java 多线程Java 平台提供的重要特性,掌握多线程的概念和相关知识点可以帮助开发者提高程序的并发性能和解决复杂问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值