一.多线程
- 概述:
为了提高程序的运行效率. - 进程和线程的区别 :
- 进程:是指 正在运行的程序 .
- 线程:是指进程的实际运行单位.也是直接被操作系统调度
- 一个软件的运行依赖一个或者多个进程,一个进程包含一个或者多个线程
- 并行和并发的区别
- 并发: 是多个程序 抢占 CPU的执行权
- 并行: 是多个CPU,对应多个程序,每个CPU执行一个程序,不用抢
- 效率: 并发 > 并行
- 模拟多线程编程方式
- 继承Thread:好处是可以使用父类的所有功能,坏处是单继承/强耦合
- 实现Runnable接口:好处是解耦合,可以多继承多实现,坏处是??
二,继承Thread类
- 概述:
线程 是程序中的执行线程,Java 虚拟机允许应用程序并发地运行多个执行线程。 - 创建对象
- Thread()
分配新的 Thread 对象。 - Thread(Runnable target)
分配新的 Thread 对象。 - Thread(Runnable target, String name)
分配新的 Thread 对象。 - Thread(String name)
分配新的 Thread 对象。
- 常用方法
- static Thread currentThread()
返回对当前正在执行的线程对象的引用。 - long getId()
返回该线程的标识符。 - String getName()
返回该线程的名称。 - void run()
- void setName(String name) 改变线程名称,使之与参数 name 相同。
- static void sleep(long millis)
- void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
- void stop() 已过时。
- 测试(模板代码)
package cn.chen.thread;
//测试 Thread工具类
public class Test1_Thread {
public static void main(String[] args) throws InterruptedException {
//1,创建对象
Thread t = new Thread();
//2,调用方法
System.out.println( t.getId() );//获取线程的标志
t.setName("懂王");//设置线程名称
System.out.println( t.getName() );//获取线程名称 Thread-0 -> 懂王
t.run();//执行任务
t.start();//开启线程
t.stop();//结束线程
Thread.sleep(10);//让程序睡10ms
//获取正在执行任务的 线程对象
Thread t2 = Thread.currentThread();
System.out.println(t2.getName());//获取线程名称
System.out.println( Thread.currentThread().getName() );
//TODO 面试题:run() 和 start()的区别
}
}
- 复杂测试
package cn.chen.thread;
//模拟多线程编程 -- 继承Thread类
public class Test2_Thread {
public static void main(String[] args) {
//第二步:::创建对象测试
MyThread t = new MyThread();//1,创建线程 -- 新建状态
// t.run();//只是普通的方法的调用过程,根本不是多线程.
t.start();//2, 开启线程 -- 可运行状态
//2,模拟多线程
MyThread t2 = new MyThread();
// t2.run();
t2.start();
// TODO 创建100个线程
for (int i = 0; i < 10; i++) {
new MyThread().start();//创建线程并启动
}
/*
3,多线程的随机性,,因为程序的执行交给了CPU去调度,,我们无法控制
Thread-0===1
Thread-1===1
Thread-0===2
Thread-1===2
Thread-0===3
Thread-0===4
Thread-0===5
*/
}
}
//第一步:::自定义线程类
//1, 继承Thread类
class MyThread extends Thread{
//2, 把业务 写在重写的run()
//重写::子类的方法声明必须和父类一样 + 有权限
@Override
public void run() {
//运行状态
//打印100次线程名称
for (int i = 0; i < 100; i++) {
//3,子类可以通过super关键字 调用 父类的 功能
System.out.println( super.getName()+"==="+i);
}
}//终止状态
}
三 实现Runnable接口
- 概述
Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称 为 run 的无参数方法。 - 常用方法
void run()
- 测试
package cn.chen.thread;
//测试 Runnable接口
public class Test3_Runnable {
public static void main(String[] args) {
//第二步:测试
MyRunnable target = new MyRunnable();
Thread t = new Thread(target);//1,创建线程对象
t.start(); //2,启动线程 start() ....Thread
//3,模拟多线程程序
Thread t2 = new Thread(target);
t2.start();
/*4, 多线程结果的随机性
Thread-1~~55
Thread-1~~56
Thread-0~~11
Thread-0~~12
Thread-0~~13
Thread-0~~14
Thread-0~~15
Thread-1~~57
Thread-1~~58
*/
}
}
//第一步:自定义多线程类
//1,实现 Runnable 接口
class MyRunnable implements Runnable{
@Override
public void run(){
//2,把业务放在 重写的 run()里
//3, 打印100次线程名称 Thread....getName()
for (int i = 0; i < 100; i++) {
//Thread.currentThread()获取当前正在执行任务的线程对象
//.getName()获取线程对象的名字
System.out.println( Thread.currentThread().getName()+"~~"+i );
}
}
}
- 优劣势比较
- 继承Thread类
- 优势: 方法多,创建对象的方式多
- 劣势: 强耦合,程序设计理念相对不灵活
- 实现Runnable接口
- 优势: 松耦合,方便程序设计.因为接口可以多继承多实现,还能继承时多实现
- 劣势: 方法少只有一个run(),也不能new
四 售票案例
- 需求:设计4个售票窗口,总计售票100张。
- 继承Thread类
package cn.chen.thread;
//模拟 多线程售票
//设计4个售票窗口,总计售票100张
public class Test4_Ticket {
public static void main(String[] args) {
//TODO 问题1 : 4个线程,卖了400张票 TODO 为什么??解决方案???
//原因: tickets是成员变量,每次new都会初始化一份,new了四次,就四份,总共400张
//解决方案: 把tickets变成 共享资源(被多个对象共享,同一份资源) -- 静态
MyTickets t1 = new MyTickets();
t1.start();//启动线程
//TODO 模拟多线程售票
MyTickets t2 = new MyTickets();
t2.start();
MyTickets t3 = new MyTickets();
t3.start();
MyTickets t4 = new MyTickets();
t4.start();
/*
多线程结果的 随机性:
Thread-0===58
Thread-0===57
Thread-1===100
Thread-0===56
Thread-1===99
Thread-0===55
Thread-1===98
Thread-0===54
Thread-1===97
Thread-0===53
Thread-1===96
*/
}
}
//实现方式1::::继承Thread类
class MyTickets extends Thread{
//1,定义变量,记录票数
static int tickets = 100 ;
//2,开始卖票 -- 放在重写的run()
@Override
public void run() {
//3,一直卖票
while (true){
//死循环,,,必须设置出口,,找地方结束循环break
if(tickets>0){
//有票才卖
//TODO 问题2:: 超卖: 卖出了0 -1 -2号票
//TODO 问题3:: 重卖: 同一张票卖了3次
try {
Thread.sleep(10);//TODO 让程序休眠一会儿
} catch (InterruptedException e) {
e.printStackTrace();
}
//重卖的原因: 同一张票卖了4次
//假设 tickets=100 , t1 t2 t3 t4 , 四个人准备卖票
//假设t1先醒,开始卖票了, 执行tickets-- ,输出100,,,,还没来得及变没自减变成99,,,
//假设t3醒了,开始卖票了, tickets还是100,执行tickets-- ,输出100,,,,还没来得及变没自减变成99
//假设t4醒了,开始卖票了,tickets还是100,执行tickets-- 输出100,,,,还没来得及变没自减变成99
//假设t2醒了,开始卖票了,tickets还是100,执行tickets-- 输出100,,,,并且自减变成99
//超卖的原因: 卖出了0 -1 -2号票
//假设 tickets=1 , t1 t2 t3 t4 , 四个人准备卖票
//假设t1先醒,开始卖票了, 执行tickets-- ,输出1,,,然后自减变成 0
//假设t3醒了,开始卖票了, 此时tickets=0,执行tickets--,输出0,,,然后自减变成-1
//假设t4醒了,开始卖票了, 此时tickets=-1,执行tickets--,输出-1,,,然后自减变成-2
//假设t2醒了,开始卖票了, 此时tickets=-2,执行tickets--,输出-2,,,然后自减变成-3
System.out.println(super.getName()+"==="+tickets--);
}else{
//没票就结束
break;
}
}
}
}
- 实现Runnable接口
package cn.chen.thread;
//模拟 多线程售票 --实现Runnable接口
//TODO 为啥没有问题1 ??
public class Test5_Ticket {
public static void main(String[] args) {
MyTickets2 target = new MyTickets2();
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
Thread t4 = new Thread(target);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//实现方式2::::实现Runnable接口
class MyTickets2 implements Runnable{
//1,定义变量,记录票数
int tickets = 100 ;
//2,开始卖票 -- 放在重写的run()
@Override
public void run() {
//3,一直卖票
while (true){
//死循环,,,必须设置出口,,找地方结束循环break
if(tickets>0){
//有票才卖
//TODO 问题2:: 超卖: 卖出了0 -1 -2号票
//TODO 问题3:: 重卖: 同一张票卖了3次
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"==="+tickets--);
}else{
//没票就结束
break;
}
}
}
}
五 同步锁
- 概述
- 同步是指: 有钥匙的可以开锁,没钥匙的就只能等待…
- 锁是指: 把程序中的 共享资源 加锁
- 同步和异步的区别
- 同步是指 排队等待的现象 ,是指 牺牲了效率提高了安全性
- 异步是指 不排队 发生了 抢的现象,是指 提高了效率牺牲了安全
- 效率上讲: 异步 > 同步
- 安全性上讲: 同步 > 异步
-
用法
- 使用关键字实现锁 synchronized
- 用在方法上
synchronized public void eat(){…} - 用在代码块上
synchronized(锁对象){ 有问题的代码… }
- 用在方法上
- 使用关键字实现锁 synchronized
-
改造售票案例
- 实现Runnable接口
package cn.chen.thread;
//同步锁改造 售票案例
public class Test1_Synch {
public static void main(String[] args) {
MyTickets2 target = new MyTickets2();
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
Thread t4 = new Thread(target);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class MyTickets2 implements Runnable{
int tickets = 100 ;
Object o = new Object() ;
String a ="abc";
@Override
//TODO 1, 在方法上加锁 -- 相当于,同一时刻只能有一个线程来访问方法,没人抢占
// synchronized public void run() {
public void run() {
while (true){
//TODO 2,在代码块上加锁 -- 考虑两个问题:锁的位置 + 锁对象
//锁的位置--合理位置就是从共享资源第一次被操作开始,用完结束
//锁对象--可以任意,但是,必须是 同一个锁对象
// synchronized (new Object()){ //不是同一个锁对象
// synchronized (o){ //同一个对象
// synchronized (a){//也是同一个对象
synchronized (this){
//也是同一个对象
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===" + tickets--);
} else {
break;
}
}
}
}
}
- 继承Thread类
package cn.chen.thread;
public class Test2_Synch {
public static void main(String[] args) {
MyTickets t1 = new MyTickets();
t1.start();
MyTickets t2 = new MyTickets();
t2.start();
MyTickets t3 = new MyTickets();
t3.start();
MyTickets t4 = new MyTickets();
t4.start();
}
}
class MyTickets extends Thread {
static int tickets = 100;
//TODO 给方法上锁:会自动分配锁对象
//给 普通方法 分配的锁对象是 this
//给 静态方法 分配的锁对象是 类名.class
@Override
// synchronized public void run() {//默认的锁对象是this
public void run() {
//默认的锁对象是this
while (true) {
//TODO 同步代码块--考虑:锁的位置和锁对象
//锁对象 是谁??
//如果锁的是 普通的资源,锁对象可以任意,但是保证同一个对象就性
//如果锁的是 静态的资源,锁对象 必须是固定的 类名.class
// synchronized (this){//不行,静态资源的对象不能随意!
synchronized (MyTickets.class){
//锁静态资源必须是类名.class
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(super.getName() + "===" + tickets--);
} else {
break;
}
}
}
}
}
1,面试题:
--同步和异步的区别
--锁的原理和用法
2,了解JUC并发包
--java.util.concurrent
扩展:
- 了解 线程池 技术
- 面试题
- 进程和线程的区别
- 并行和并发的区别
- 同步和异步的区别
- 锁的原理和用法
- 线程状态
- Map的复杂数据结构
Map<Integer,List<String>> map2 = new HashMap<>();
//准备value
List<String> list = new ArrayList<>();
//向list里添加多个数据
Collections.addAll(list,"tony","tommy","jerry");
//存入map
map2.put(1,list);
//根据key获取value
List<String> value = map2.get(1);
//获取每个value
for(String s : value){
//如果是tony就替换成 杨幂
if(s.equals("tony")){
String str = s.replace("tony","杨幂");
System.out.println(str);
}
}
- 了解JUC并发包
- java.util.concurrent