多线程 & 线程经典案例 &锁 详细笔记

一.多线程

  1. 概述:
    为了提高程序的运行效率.
  2. 进程和线程的区别 :
  • 进程:是指 正在运行的程序 .
  • 线程:是指进程的实际运行单位.也是直接被操作系统调度
  • 一个软件的运行依赖一个或者多个进程,一个进程包含一个或者多个线程
  1. 并行和并发的区别
  • 并发: 是多个程序 抢占 CPU的执行权
  • 并行: 是多个CPU,对应多个程序,每个CPU执行一个程序,不用抢
  • 效率: 并发 > 并行
  1. 模拟多线程编程方式
  • 继承Thread:好处是可以使用父类的所有功能,坏处是单继承/强耦合
  • 实现Runnable接口:好处是解耦合,可以多继承多实现,坏处是??

二,继承Thread类

  1. 概述:
    线程 是程序中的执行线程,Java 虚拟机允许应用程序并发地运行多个执行线程。
  2. 创建对象
  • Thread()
    分配新的 Thread 对象。
  • Thread(Runnable target)
    分配新的 Thread 对象。
  • Thread(Runnable target, String name)
    分配新的 Thread 对象。
  • Thread(String name)
    分配新的 Thread 对象。
  1. 常用方法
  • 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() 已过时。

  1. 测试(模板代码)
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()的区别
		    }

		}
  1. 复杂测试
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接口

  1. 概述
    Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称 为 run 的无参数方法。
  2. 常用方法
void run()  
  1. 测试
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 );
	        }
	    }
	}
  1. 优劣势比较
  • 继承Thread类
    • 优势: 方法多,创建对象的方式多
    • 劣势: 强耦合,程序设计理念相对不灵活
  • 实现Runnable接口
    • 优势: 松耦合,方便程序设计.因为接口可以多继承多实现,还能继承时多实现
    • 劣势: 方法少只有一个run(),也不能new

四 售票案例

  1. 需求:设计4个售票窗口,总计售票100张。
  2. 继承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;
		            }
		        }
		    }
		}
  1. 实现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;
	            }
	        }
	    }
	}

五 同步锁

  1. 概述
  • 同步是指: 有钥匙的可以开锁,没钥匙的就只能等待…
  • 锁是指: 把程序中的 共享资源 加锁
  • 同步和异步的区别
    • 同步是指 排队等待的现象 ,是指 牺牲了效率提高了安全性
    • 异步是指 不排队 发生了 抢的现象,是指 提高了效率牺牲了安全
    • 效率上讲: 异步 > 同步
    • 安全性上讲: 同步 > 异步
  1. 用法

    • 使用关键字实现锁 synchronized
      • 用在方法上
        synchronized public void eat(){…}
      • 用在代码块上
        synchronized(锁对象){ 有问题的代码… }
  2. 改造售票案例

  • 实现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

扩展:

  1. 了解 线程池 技术
  2. 面试题
    • 进程和线程的区别
    • 并行和并发的区别
    • 同步和异步的区别
    • 锁的原理和用法
    • 线程状态
  3. 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);
            }
        }
  1. 了解JUC并发包
    • java.util.concurrent
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值