多线程 一、相关概念 1.程序、进程、线程三者的区别? 程序:我们每天写的代码(代码是死的) //比如:没有运行的飞秋,它就是一个程序 进程:运行起来的代码 //比如:运行起来的飞秋,它就是一个进程
线程:是"进程"中的单个顺序控制流,是一条执行路径
//我们使用qq可以一边聊天、一边下载 聊天时一个线程、下载也是一个线程
2.进程和线程之间的关系
(1)线程是依赖于进程的 //我想和乔梦佳同学聊天,我就必须先起启动qq这个进程
(2)一个进程中可以有多个线程 //电脑管家这个进程,里面包含病毒查杀、垃圾清理等多个线程
进程是线程的一个集合(容器),真正干活的是线程
3.小问题
(1)电脑管家中病毒查杀、垃圾清理、电脑加速多个线程,请问他们是同时执行的吗?
不是,在同一个时间点CPU只能做一件事。之所以你看起来像是在同时做多件事,是因为CPU在做着高速的切换
(2)电脑管家中病毒查杀、垃圾清理,他们之间的执行有没有顺序?
没有顺序,抢占式调度模型(谁抢到谁执行)
4.多线程的好处
我们可以一边玩游戏,一边听音乐。无形中提搞了CPU的使用效率。提高了CPU的执行效率
二、实现多线程的第一种方式-----继承Thread类 //☆☆☆☆☆
1.步骤
(1)定义一个类MyThread继承Thread类
(2)在MyThread类中重写run()方法
(3)创建MyThread类的对象
(4)启动线程
代码实现:
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
------------------------------------------------------
public class Demo1 {
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();
m2.start();
}
}
2.在多线程中run方法和start方法的区别? //面试题
void run() :里面的内容是线程需要执行的逻辑,直接调用相当于普通方法
void start(): 使此线程开始执行,Java虚拟机(进程)会调用run方法()
//Java的运行原理
编写代码----编译(javac)----运行(java)
当我们执行java命令的时候,启动一个JVM(Java虚拟机),这里的JVM其实就是一个进程,然后去创建一个名为main的线程,调用该线程里面的main方法
3.设置和获取线程的名称
(1)如果我不给线程设置名称,有没有名称
有
Thread-x x从0开始的
(2)线程设置名称的方式
*使用setName(String name)方法
//一定要在创建线程对象之后再使用该方法
MyThread m1 = new MyThread();
m1.setName("飞机");
MyThread m2 = new MyThread();
m2.setName("火车");
*使用构造方法的方式
//需要在线程类MyThread中添加一个带String类型的构造方法
MyThread m1 = new MyThread("飞机");
MyThread m2 = new MyThread("火车");
(3)获取线程名称的方式
*使用getName()方法获取。
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName()+":"+i); //在线程类中的run方法里来获取线程名称
}
}
*使用Thread.currentThread().getName()方法
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
4.线程的优先级 //了解
final int getPriority() 返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级 线程默认优先级是5;线程优先级的范围是:1-10
(1)线程优先级的范围
1-10
(2)线程的默认优先级
5
(3)线程的优先级越高,代表获取到CPU的执行权的概率越高,并不能保证一定能抢到
5.线程的控制 //了解
(1)static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数
*使用位置:一般用在run方法中,参数为毫秒值
*使线程进入休眠状态,这个时候就不会去争夺CPU的执行权
(2)void join() 等待这个线程死亡
*使用位置:一般用在main方法中,一定要在start方法之后使用
*设置为加入线程后,会优先执行
(3)void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
*使用位置:一般用在main方法中,一定要在start方法之前使用
*当被守护线程执行结束之后,守护线程将结束,但是在临死前会挣扎着执行一点代码
6.线程的生命周期 //常见的面试题 ☆☆☆☆☆
//至少要知道有几种状态 最好能 "独立" 画图
新建、就绪、运行、死亡、阻塞
三、多线程的第二种实现方式 ------ 实现Runnable接口的方式 //☆☆☆☆☆
1.实现步骤
(1)定义一个类MyRunnable实现Runnable接口
(2)在MyRunnable类中重写run()方法
(3)创建MyRunnable类的对象
(4)创建Thread类的对象,把MyRunnable对象作为构造方法的参数
(5)启动线程
代码实现:
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
-----------------------------------------------------------
public class Demo2 {
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable();
MyRunnable mr2 = new MyRunnable();
Thread t1 = new Thread(mr1);
Thread t2 = new Thread(mr2);
t1.start();
t2.start();
}
}
2.怎么给第二种方式的线程设置名称
*使用setName(String name)来设置名称
Thread t1 = new Thread(mr1);
t1.setName("灰机");
Thread t2 = new Thread(mr2);
t2.setName("火车");
*使用构造方法设置线程名称
Thread t1 = new Thread(mr1,"灰机");
Thread t2 = new Thread(mr2,"火车");
3.获取线程名称
//只能使用一种方式获取
*使用Thread.currentThread().getName()来获取
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i); //只能使用这种方式来获取线程名称,不能使用this.getName()
}
}
4.两种多线程实现方式的对比
(1)继承Thread类
*只能单继承
*增加了类和类之间的耦合性
(2)实现Runnable接口
*支持多实现
*将"线程"(Thread)和"线程任务"(run)之间解了耦合 //run方法不在线程类中了
//------------------------------------------------------------------------ 重点一:properties类 重点二:多线程的两种实现方式
//-----------------------------------------------------------------------
四、买票案例
//问题:多个线程之间是怎么实现共享100张票的?
*实现Runnable接口方式:
将100张票封装在SellTicket类中,创建了一个SellTicket对象,然后将这个对象共享给三个线程,这样的话三个线程就共享了这个对象,也就共享了里面的100张票
*继承Thread类的方式
//使用static修饰ticket,这个时候三个线程类对象就共享了这个静态变量
public class SellTicket extends Thread{
private static int ticket = 100;
@Override
public void run() {
while (true){
if (ticket<=0){
break;
}else{
System.out.println(this.getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
----------------------------------------------------------------------------------------------------------------
public class Demo1 {
public static void main(String[] args) {
SellTicket s1 = new SellTicket();
s1.setName("窗口一");
SellTicket s2 = new SellTicket();
s2.setName("窗口二");
SellTicket s3 = new SellTicket();
s3.setName("窗口三");
s1.start();
s2.start();
s3.start();
}
}
1.卖票案例的两个小问题
(1)卖了重复票
(2)卖了负数票
//出现这两个问题的根本原因:CPU的执行权被哪个线程抢到是随机的,具有不确定性
2.安全问题出现的条件
(1)是多线程环境
(2)有共享数据
(3)有多条语句"操作"共享数据 //操作:增、删、改
3.使用同步代码块解决线程安全问题//☆☆☆☆☆
(1)格式
synchronized(任意对象) {
多条语句操作"共享数据"的代码
}
(2)任意对象指的是什么?
其实就是我们所说的锁对象,对于给出什么样的锁对象都无所谓,//但是一定要保证多个线程使用的是同一把锁
(3)大括号到底要包含哪些内容?
//根据共享数据去找
*怎么使用
使用idea自动生成:选中要包含的代码--------使用快捷键CTRL + ALT + T -----------选择synchronized
4.同步方法
(1)同步非静态方法
*格式
修饰符 synchronized 返回值 方法名(参数列表){
xxxxx
}
举例:
public synchronized void sell(){
xxxxx
}
//同步非静态方法的锁对象是谁?
//this
(2)同步静态方法
*格式
修饰符 static synchronized 返回值 方法名(参数列表){
xxxxxxxxx
}
//同步静态方法的锁对象是谁?
类名.class
五、线程安全的类
StringBuilder(线程不安全的、效率高) ---------- StringBuffer(线程安全的、效率低)
ArrayList(线程不安全的、效率高) ------------------ Vector(线程安全的、效率低)
HashMap(线程不安全的、效率高) ------------------Hashtable(线程安全的、效率低)
//面试题:HashMap和HashTable的区别?
HashMap是线程不安全的、效率高 允许null键和null值
Hashtable是线程安全的、效率低 不允许null键和null值
Collections.synchronizedList(new ArrayList<String>());
//线程安全的类之所以安全,是因为在它的底层方法或者代码上加上了synchronized
六、Lock锁 //(不常用)
1.为什么会出现Lock锁
因为Lock锁更加具有面向对象的意思,是用来替代同步代码块的,同步代码块更加具有一点C语言的面向过程的味道
2.相关的类和方法
*构造方法:ReentrantLock() 创建一个ReentrantLock的实例
*相关的方法
void lock() 获得锁
void unlock() 释放锁
3.怎么使用?
synchronized(任意对象) { //---------------------lock.lock();
多条语句操作"共享数据"的代码
} //---------------------lock.unLock();
代码举例:
public void run() {
while (true){
// synchronized (obj) {
lock.lock();
if (ticket<=0){
break;
}else{
//1 //2 //3
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
// }
lock.unlock();
}
}
4.异常处理
try---finally
因为如果在lock()和unLock()方法之间出现了异常,那么就会导致关闭锁资源的代码无法被执行,为了让锁对象一定会被释放,我们就是用finally
//回忆
(1)try --- catch
(2)try ---catch -- finally
(3)try--finally
(4)catch ---finally
(5)try---catch---catch
七、生产者和消费者
//Object类的等待和唤醒方法
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
特点:死等 不会自动醒来,除非被唤醒
void notify() 唤醒正在等待对象监视器的"单个"线程 //只能唤醒单个线程
void notifyAll() 唤醒正在等待对象监视器的"所有"线程 //可以唤醒所有线程
//注意:这三个方法必须由锁对象来调用 ☆☆☆☆☆
1.在生产者和消费者模式中,一共出现了几个类?
(1)奶箱类
*奶箱状态
*数量
(2)生产者类
判断奶箱的状态
*有牛奶
不生产---进入等待状态
*没有牛奶
生产牛奶
唤醒消费者线程去消费
改变奶箱的状态为有牛奶
(3)消费者类
判断奶箱的状态
*有牛奶
消费牛奶
唤醒生产者去生产
奶箱的状态改成没有牛奶
*没有牛奶
不消费----进入等待状态
(4)测试类
2.套路 ----针对于生产者和消费者
(1)while(true)
(2)synchronized(){}
(3)判断数量
(4)判断状态
3.作业
吃货吃包子
(1)吃货类
(2)老板类
(3)袋子类 //50个包子
(4)测试类
代码实现:
public class Bag {
public int num = 50;
public boolean status = false; // true代表袋子里有包子 false表示袋子里没有包子
}
----------------------------------------------------------------------------------------------
public class Boss implements Runnable {
private Bag bag;
public Boss(Bag bag) {
this.bag = bag;
}
@Override
public void run() {
while (true){
synchronized (Bag.class){
if (bag.num==0){
break;
}
if (bag.status){
try {
Bag.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println("老板正在做第"+bag.num+"个包子");
bag.status=true;
Bag.class.notifyAll();
}
}
}
}
}
--------------------------------------------------------------------------------------
public class ChiHuo implements Runnable {
private Bag bag;
public ChiHuo(Bag bag) {
this.bag = bag;
}
@Override
public void run() {
while (true){
synchronized (Bag.class){
if (bag.num==0){
break;
}
if (!bag.status){
try {
Bag.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println("吃货正在吃第"+bag.num+"个包子");
bag.num--;
bag.status=false;
Bag.class.notifyAll();
}
}
}
}
}
--------------------------------------------------------------------------------------
public class Demo1 {
public static void main(String[] args) {
Bag bag = new Bag();
Boss b1= new Boss(bag);
ChiHuo c1 = new ChiHuo(bag);
Thread t1 = new Thread(b1);
Thread t2 = new Thread(c1);
t1.start();
t2.start();
}
}
|