Java学习第十周
1.线程和进程
线程是依赖于进程存在的。
进程概述
什么是进程呢?通过任务管理器我们就可以看到进程的存在。
概念:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
多进程的意义
单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),
所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。
因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,
所以,我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。
多线程有什么意义呢?
多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
那么怎么理解这个问题呢?
我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到
CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序
中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们
中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性.
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
2.多线程程序实现的方式1(继承Thread类)
Thread类的基本获取和设置方法
继承Thread类需要重写run()方法
利用start()来启动该方法
public final String getName()//获取线程名称
public final void setName(String name)//设置线程名称
public static Thread currentThread()//获取当前执行的线程
3.线程有两种调度模型:
分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
设置和获取线程优先级
public final int getPriority() //获取线程的优先级
public final void setPriority(int newPriority)//设置线程的优先级(线程的优先级的范围是: 1 - 10)
4.多线程其他方法
public static void sleep(long millis) 线程休眠
public final void join() 等待该线程执行完毕了以后,其他线程才能再次执行(注意:需要在线程启动之后,在调用方法)
public static void yield(): (礼让线程)暂停当前正在执行的线程对象,并执行其他线程。(礼让效果不是很明显)
守护线程: public final void setDaemon(boolean on):
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。 (效果也不是很明显)
public final void stop(): 停止线程的运行(已经过时)
public void interrupt(): 中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞
5.多线程(多线程程序实现的方式2)
实现Runnable接口 这种方式扩展性强 实现一个接口 还可以再去继承其他类(可以避免由于Java单继承带来的局限性。)
6.多线程(多线程程序实现的方式3)
实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常
执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
C:实现步骤
1.创建一个类实现Callable 接口
2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
3.创建Thread类,将FutureTask对象作为参数传进去
4.开启线程
7.多线程(买电影票出现了同票和负数票的原因分析)
A:加入延迟
我们前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,
售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟
改实现接口方式的卖票程序,每次卖票延迟100毫秒
B:出现问题了问题
public class SellTickets extends Thread {
private static int tickets = 100 ;
private static final Object obj = new Object() ;//设置成静态的 让所有线程对象共享
@Override
public void run() {
// 卖票
while(true){
// 同步代码块
synchronized(obj){//这个对象相当于一把锁 哪个线程先进来先锁住 其他线程进不来 等我进来的这个线程执行完了 释放了这个锁 其他线程才能进来
if( tickets > 0 ){
// 模拟网络延迟
try {
Thread.sleep(100) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "正在出售" + (tickets--) + "张票");
}
}
}
}
}
-------------------------------------
public class SellTicketsTest {
public static void main(String[] args) {
// 创建对象
SellTickets t1 = new SellTickets() ;
SellTickets t2 = new SellTickets() ;
SellTickets t3 = new SellTickets() ;
// 给线程设置名称
t1.setName("窗口1") ;
t2.setName("窗口2") ;
t3.setName("窗口3") ;
// 启动线程
t1.start() ;
t2.start() ;
t3.start()
}
}
8.多线程(线程安全问题的产生原因分析)
判断一个多线程应用程序是否有问题的标准:
a: 是否是多线程环境
b: 是否存在共享数据
c: 是否存在多条语句同时操作共享数据
使用同步代码块:
格式:
synchronized(对象){//不能在括号了直接new 对象 new 了 就没效果
要被同步的代码 ;
}
这个同步代码块保证数据的安全性的一个主要因素就是这个对象
注意这个对象 要定义为静态成员变量 才能被所有线程共享
需要这个对象被所有的线程对象所共享
这个对象其实就是一把锁.
这个对象习惯叫做监视器
9.Lock锁的概述和使用
Lock和ReentrantLock
void lock() 上锁
void unlock() 解锁
public class MyThread implements Runnable {
// 定义票数
private static int tickets = 100 ;
// 创建Lock锁对象
private static final Lock lock = new ReentrantLock() ;
@Override
public void run() {
while(true){
// 添加锁
lock.lock() ;
if(tickets > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售" + (tickets--) + "张票");
}
// 释放锁
lock.unlock() ;
}
}
}
10.多线程(死锁问题概述和使用)
死锁问题概述
如果出现了同步嵌套,就容易产生死锁问题
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
同步代码块的嵌套案例
死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态
举例: 中国人和美国人一起吃饭
中国人使用的筷子
美国人使用的刀和叉
中国人获取到了美国人的刀
美国人获取到了中国人的一根筷子
11.多线程(线程的状态、匿名内部类的方式、定时器)
新建 , 就绪 , 运行 , 冻结 , 死亡
新建:线程被创建出来
就绪:具有CPU的执行资格,但是不具有CPU的执行权
运行:具有CPU的执行资格,也具有CPU的执行权
阻塞:不具有CPU的执行资格,也不具有CPU的执行权
死亡:不具有CPU的执行资格,也不具有CPU的执行权
匿名内部类的方式实现多线程程序
new Thread(){代码…}.start();
new Thread(new Runnable(){代码…}).start();
Timer
public void schedule(TimerTask task, long delay) 在指定的时候到达后, 指定给定的任务
public void schedule(TimerTask task,long delay,long period) 在指定的时候到达后,执行给定的任务, 再等待period时间后,再循环执行给定的任务
TimerTask
public abstract void run()
public boolean cancel()