目录
一、多线程
1.进程
正在运行的软件
-
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
-
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
-
并发性:任何进程都可以同其他进程一起并发执行
2.线程
是进程中的单个顺序控制流,是一条执行路径
-
单线程:一个进程如果只有一条执行路径,则称为单线程程序
-
多线程:一个进程如果有多条执行路径,则称为多线程程序
主要方法
方法名 | 返回值类型 | 说明 |
---|---|---|
getName() | String | 返回此线程的名称 线程是默认名称:Thread-编号 |
setName(String name) | void | 设置线程名称 Thread类构造方法 也可,子类需要继承Thread并添加一个无参和一个String参数的构造 |
currentThread() 静态方法 | Thread | 返回对当前正在执行的线程对象的引用 |
sleep() 静态方法 | void | 让线程休眠指定的时间,单位为毫秒 |
setPriority(int newPriority) | void | 设置该线程的优先级,默认是5,范围1到10 |
getPriority() | int | 获取该线程优先级 |
setDaemon(boolean on) | void | 设置为守护线程 |
3.并发
在同一时刻,有多个指令在单个CPU上交替执行
4.并行
在同一时刻,有多个指令在多个CPU上同时执行
5.多线程的实现
-
继承Thread类
-
实现Runnable接口
-
利用Callable和Future接口
(1)继承Thread类
-
定义一个类,继承Thread类
-
在该类中重写run()方法(线程开启后执行的代码)
-
创建该类对象
-
启动线程
public class Application extends Thread{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("线程1开启");
}
}
}
public class ThreadDemo01 {
public static void main(String[] args) {
Application application01 = new Application();
Application application02 = new Application();
application01.start();
application02.start();
}
}
①为什么要重写run()方法?
因为run0是用来封装被线程执行的代码
②run()和start()区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程
start():启动线程;然后由JVM调用此线程的run0方法
(2)实现Runnable接口
-
定义一个类,实现Runnable接口
-
在该类中重写run()方法
-
创建该类对象
-
创建Thread类对象,把上步创建的对象作为构造方法的参数传入
-
启动线程
Application02 application021 = new Application02();
Application02 application022 = new Application02();
//线程对象
Thread thread1 = new Thread(application021);
thread1.start();
Thread thread2 = new Thread(application022);
thread2.start();
(3)利用Callable和Future接口
-
定义一个类,实现Callable接口
-
在该类中重写call()方法
-
创建该类对象
-
创建Future的实现类FutureTask对象,将上步对象作为构造方法的参数传入
-
创建Thread类对象,把上步创建的对象作为构造方法的参数传入
-
启动线程
public class Application03 implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("线程启动"+i);
}
//返回线程运行完成后的结果
return "运行完毕";
}
}
Application03 application03 = new Application03();
FutureTask<String> futureTask01 = new FutureTask<>(application03);
Thread thread01 = new Thread(futureTask01);
thread01.start();
//获得线程运行之后的结果
//如果线程还没有运行结束,那么get方法会在这里死等,在没有获取到结果之前,代码不会往下执行!
System.out.println(futureTask01.get());
Application03 application04 = new Application03();
FutureTask<String> futureTask02 = new FutureTask<>(application03);
Thread thread02 = new Thread(futureTask02);
thread02.start();
System.out.println(futureTask02.get());
6.三种方式的对比
实现方式 | 优点 | 缺点 |
---|---|---|
继承Thread类 | 编程简单,能直接使用Thread类中的方法 | 拓展性低,不可继承其他的类 |
实现Runnable接口,利用Callable和Future接口 | 拓展性高,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
7.线程调度
(1)多线程的并发运行:
计算机中的CPU,在任意时刻只能执行一条机器指令。每个线程只有获得CPU的使用权才能执行代码.各个线程轮流获得CPU的使用权,分别执行各自的任务。
(2)调度模型
-
分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程用CPU的时间片
-
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级一样,那么会随机选择一个,优先级高的线程获得的CPU时间片相对多一些。
java使用的是第二种
方法在线程方法里
(3)后台线程/守护线程
当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了(但不会立即停,如果最后还有CPU时间片,会使用完再结束)
8.类加载器
主要方法
方法名 | 返回值类型 | 说明 |
---|---|---|
getSystemClassLoader() 静态方法 | ClassLoader | 获取系统类加载器 |
getResourceAsStream(String name) | InputStream | 加载某一个资源文件 |
public class ClassLoaderDemo {
public static void main(String[] args) throws IOException {
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
//注意:
//在IO流中,相对路径是相对整个项目而言
//在类加载器当中,路径是在src下的
InputStream resource = classLoader.getResourceAsStream("properties.properties");
Properties properties = new Properties();
properties.load(resource);
System.out.println(properties);
resource.close();
}
}
9.线程安全问题
某电影院目前正在上映电影,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
(1)没有做线程安全
package classloader;
public class Tickets implements Runnable{
private int tickets = 100;
public Tickets() {
}
public Tickets(int tickets) {
this.tickets = tickets;
}
@Override
public void run() {
while (true){
if (tickets<=0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
String name = Thread.currentThread().getName();
System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
}
}
}
}
package classloader;
public class Application {
public static void main(String[] args) {
Tickets tickets = new Tickets();
Thread thread01 = new Thread(tickets);
Thread thread02 = new Thread(tickets);
Thread thread03 = new Thread(tickets);
thread01.setName("第一窗口");
thread02.setName("第二窗口");
thread03.setName("第三窗口");
thread01.start();
thread02.start();
thread03.start();
}
}
(2)问题:
同一个票卖三个人
负的票数
多线程操作共享数据
(3)解决方式一同步代码块
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
使用同步代码块
@Override
public void run() {
while (true){
synchronized (o){
if (tickets<=0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
String name = Thread.currentThread().getName();
System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
}
}
}
}
(4)同步代码块
synchronized (锁对象【任意对象】){//多个线程必须使用同一把锁
//操作共享数据的代码
}
默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭
当线程执行完出来了,锁才会自动打开
①同步的好处和弊端
-
好处:解决了多线程的数据安全问题
-
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
(5)解决方式一同步方法
就是把synchronized关键字加到方法上
锁对象:this
public class Tickets02 implements Runnable{
private int tickets = 100;
@Override
public void run() {
while (true){
if (("第一窗口").equals(Thread.currentThread().getName())){
if (synchronizedMethod()){
break;
}
}
if (("第二窗口").equals(Thread.currentThread().getName())){
synchronized (this){
if (tickets == 0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
String name = Thread.currentThread().getName();
System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
}
}
}
}
}
private synchronized boolean synchronizedMethod() {
if (tickets <= 0){
return true;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
String name = Thread.currentThread().getName();
System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
return false;
}
}
}
public class Application02 {
public static void main(String[] args) {
Tickets02 tickets02 = new Tickets02();
Thread thread01 = new Thread(tickets02);
Thread thread02 = new Thread(tickets02);
thread01.setName("第一窗口");
thread02.setName("第二窗口");
thread01.start();
thread02.start();
}
}
(6)同步代码块和同步方法的区别
-
同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
-
同步代码块可以指定锁对象,同步方法不能指定锁对象
(7)同步静态方法
就是把synchronized关键字加到静态方法上
锁对象:类名.class
public class Tickets03 implements Runnable {
private static int tickets = 100;
@Override
public void run() {
while (true){
if (("第一窗口").equals(Thread.currentThread().getName())){
if (synchronizedMethod()){
break;
}
}
if (("第二窗口").equals(Thread.currentThread().getName())){
synchronized (Application02.class){
if (tickets == 0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
String name = Thread.currentThread().getName();
System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
}
}
}
}
}
private static synchronized boolean synchronizedMethod() {
if (tickets <= 0){
return true;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
String name = Thread.currentThread().getName();
System.out.println(name+"卖出一张票"+"还剩"+tickets+"张票");
return false;
}
}
}
(8)Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
方法名 | 返回值类型 | 说明 |
---|---|---|
lock() | void | 获得锁 |
unlock() | void | 释放锁 |
Lock是接口不能直接实例化,所以采用它的实现类ReentrantLock来实例化
public class Tickets04 implements Runnable {
private int tickets = 100;
private Object o = new Object();
private ReentrantLock lock = new ReentrantLock();
public Tickets04() {
}
public Tickets04(int tickets) {
this.tickets = tickets;
}
@Override
public void run() {
while (true) {
try {
lock.lock();
if (tickets <= 0) {
break;
} else {
Thread.sleep(100);
tickets--;
String name = Thread.currentThread().getName();
System.out.println(name + "卖出一张票" + "还剩" + tickets + "张票");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
(9)死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
代码实现死锁效果
public class ThreadSafe {
public static void main(String[] args) {
Object oA = new Object();
Object oB = new Object();
new Thread(() -> {
while (true){
synchronized (oA){
synchronized (oB){
System.out.println("A进行中");
}
}
}
}).start();
new Thread(() -> {
while (true){
synchronized (oB){
synchronized (oA){
System.out.println("B进行中");
}
}
}
}).start();
}
}
解决:避免锁嵌套~
10.生产者和消费者
(1)生产者消费者模式
生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻
(2)组成
-
生产者
-
商品架
-
消费者
(3)理想状态
-
消费者先抢到执行权,买走商品
-
生产者抢到执行权,生产商品
-
消费者再抢
-
生产者再抢
-
反复
(4)可能出现的状况
消费者等待:
-
消费者先抢到执行权,但商品架无商品
-
消费者等待商品
-
生产者抢到执行权,生产商品并放到商品架上
-
生产者唤醒消费者(给消费者提示消息)
-
消费者买走商品
生产者等待:
-
生产者先抢到执行权,判断商品架是否有商品,没有就生产商品并放到商品架上
-
还是生产者抢到执行权,但因为有商品,不生产,生产者等待
-
消费者抢到执行权,买走商品
-
消费者唤醒等待的生产者
消费者步骤:
-
判断是否有商品
-
有就买走
-
商品架商品消失
-
唤醒等待的生产者继续生产
-
需要的商品总数-1
生产者步骤:
-
判断商品架是否有商品,如果没有就生产商品,否则等待
-
将商品放到商品架上
-
唤醒等待的消费者
(5)等待和唤醒的方法
为了体现生产和消费过程中的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法在Object类中
方法名 | 返回值类型 | 说明 |
---|---|---|
wait() | void | 导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAII()方法 |
notify() | void | 唤醒正在等待对象监视器的单个线程 |
notifyAII() | void | 唤醒正在等待对象监视器的所有线程 |
(6)代码实现
消费者:
package thread.thread_practice;
public class Consumer extends Thread {
@Override
public void run() {
//1.while(true)
//2.synchronized 锁,锁对象要唯一
//3.判断共享数据是否结束,结束
//4.继续判断共享数据是否结束,没有结束
while (true){
synchronized (GoodsShelf.LOCK){
if (GoodsShelf.count == 0){
break;
}else {
if (GoodsShelf.flag){
//有
System.out.println("一共需要"+GoodsShelf.count+"个商品");
System.out.println("消费者买走商品");
GoodsShelf.flag = false;
GoodsShelf.LOCK.notifyAll();
GoodsShelf.count--;
System.out.println("还剩"+GoodsShelf.count+"个商品");
}else {
try {
GoodsShelf.LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
生产者:
package thread.thread_practice;
public class Producer extends Thread{
@Override
public void run() {
while (true){
synchronized (GoodsShelf.LOCK){
if (GoodsShelf.count == 0){
break;
}else{
if (!GoodsShelf.flag){
System.out.println("生产者生产商品");
GoodsShelf.flag = true;
GoodsShelf.LOCK.notifyAll();
}else {
try {
GoodsShelf.LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
商品架:
package thread.thread_practice;
public class GoodsShelf {
/**
* 表示为true时,表示有商品,允许消费者买走
* 表示为false时,表示无商品,允许生产者生产
*/
public static boolean flag = false;
/**
* 商品总数量
*/
public static int count = 10;
/**
* 生产者和消费者的锁对象
*/
public static final Object LOCK = new Object();
}
测试:
package thread.thread_practice;
public class Application {
public static void main(String[] args) {
Consumer consumer = new Consumer();
Producer producer = new Producer();
Thread thread1 = new Thread(consumer);
Thread thread2 = new Thread(producer);
thread1.start();
thread2.start();
}
}