目录
1.1 程序、进程、线程的基本概念
- 程序:一段静态的代码
- 进程:程序的一次执行过程。进程是动态的,有自身的创建、运行到消亡。一个进程可以有多个线程。
- 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。多个线程共享进程的堆和方法区,但每个线程有自己的程序计数器、虚拟机栈、本地方法栈。
1.2 线程的创建和使用
(1)方式一:继承于Thread类
* 1.创建一个继承于Thread类的子类
* 2.重写Thread类的run()-->将此线程执行的操作声明在run()中
* 3.创建Thread类的子类的对象
* 4.通过此对象调用start()
(2)方式二:实现Runnable接口
* 1.创建一个实现了Runnable接口的类
* 2.实现类去实现Runnable中的抽象方法:run()
* 3.创建实现类的对象
* 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 Thread t1 = new Thread(实现类);
* 5.通过Thread类的对象调用start()
(3)方式三:实现Callable接口(JDK 5.0新增)
* 1.创建一个实现Callable的实现类
* 2.实现类去实现Callable中的抽象方法:call()
* 3.创建实现类的对象
* 4.将此Callable接口实现类的对象作为传递到FutureTask构造中,创建FutureTask的对象
* 5.将FutureTask类的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
* 6.获取Callable中call()方法的返回值——get()返回值即为futureTask构造器参数Callable实现类重写的call()的返回值
(4)方式四:使用线程池
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务时最多保持多长时间后会终止
(5)比较创建线程的两种方式继承于Thread类/实现Runnable接口——开发中优先选择实现Runnable接口的方式 。 原因:
1)实现的方式没有类的单继承性的局限性
2)实现的方式更适合来处理多个线程有共享数据的情况
(6)Thread中常用的方法:
* 1.start():启动当前线程;调用当前线程的run()
* 2.run():通常需要重写Thread类中的此方法,将创建的线程执行的操作声明在此方法中
* 3.currentThread():静态方法,返回执行当前代码的线程
* 4.getName():获取当前线程的名字
* 5.setName():设置当前线程的名字
* 6.yield():释放当前CPU的执行权
* 7.join():在线程A中调用线程B的join(),此时线程A就进入阻塞状态,直到线程B完全执行以后,线程A才 * 结束阻塞状态
* 8.stop():已过时。当执行此方法时,强制结束当前线程。
* 9.sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前 * 线程是阻塞状态。
* 10.isAlive():判断当前线程是否存活
* 11.线程的优先级:
* 1.三个常量
* MAX_PRIORITY:10
* MIN_PRIORITY:1
* NORM_PRIORITY:5 -->默认优先级
* 2.如何获取和设置当前线程的优先级:
* getPriority():获取线程的优先级
* setPriority(int p):设置线程的优先级 *
* 说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下 * 被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
1.3 线程的生命周期
- 新建:被声明并创建时,即new
- 就绪:调start()方法后,进入线程队列等待CPU时间片
- 运行:线程被调度并获得CPU资源,run()方法运行
- 阻塞:被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行
- 死亡:线程完成它的全部工作或被提前强制性终止或出现异常导致结束
1.4 线程的同步
在Java中,我们通过同步机制,来解决线程的安全问题。
(1)方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1.操作共享数据的代码,即为需要被同步的代码。-->不能包含代码多了,也不能包含代码少了
2.共享数据:多个线程共同操作的变量
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。 要求:多个线程必须要共用同一把锁。Object obj = new Object();obj就可以直接用来当锁。
补充:1) 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器;
2) 在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
(2)方式二:同步方法——操作共享数据的代码完整的声明在一个方法中,即将此方法声明同步的。
1)非静态的同步方法:private synchronized void show(){...} ==》 同步监视器是this
2)静态的同步方法:private static synchronize void show(){...} ==》 同步监视器是当前类本身
(3)方式三:Lock锁(jdk5.0新增)
Lock需要手动的启动同步(.lock()),同时结束同步也需要手动的实现(.unlock())。
(4)优缺点
优点:同步的方式,解决了线程的安全问题。
缺点:操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
(5)死锁问题
理解:不同的线程分别占用对方需要的同步资源不放弃, 都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
解决:专门的算法、原则;尽量
1.5 线程的通信
涉及到的三个方法:
* wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
* notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的那个。
* notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
* 1.wait()、notify()、notifyAll()三个方法必须使用在同步代码块或同步方法中
* 2.wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。 否则会出现IllegalMonitorStateException异常。
* 3.wait()、notify()、notifyAll()三个方法是定义在java.lang.object类中。
面试题:sleep() 和 wait()的异同?
* 1.相同点:一旦执行,都可以使得当前的线程进入阻塞状态
* 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块或同步方法中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁