one-16 set接口 进程与线程 同步锁

1.set接口

  1. set接口父接口为Collection接口
  2. set集合无序,不能存入重复元素
  3. set集合没有下标
/**本类用于测试Set*/
public class TestSet {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        set.add("紫霞仙子");
        set.add("至尊宝");
        set.add("蜘蛛精");
        set.add("紫霞仙子");
        set.add(null);
        set.add(null);
        /*1.set集合中的元素都是没有顺序的
        * 2.set集合中的元素不能重复
        * 3.set集合可以存null值,但是最多只有一个*/
        System.out.println(set);
        System.out.println(set.contains("唐僧")); //false,判断是否包含指定元素
        System.out.println(set.isEmpty());//false,判断是否为空
        System.out.println(set.remove(null));//true,删除指定元素
        System.out.println(set);
        System.out.println(set.size());//3,获取集合中元素的个数
        System.out.println(Arrays.toString(set.toArray()));//将集合转换为数组

        Set<String> set2 = new HashSet<>();
        set2.add("小兔子");
        set2.add("小老虎");
        set2.add("小海豚");
        set2.add("大角牛");
        System.out.println(set2);

        System.out.println(set.addAll(set2));//将set2集合的所有元素添加到set集合中
        System.out.println(set.contains(set2));//判断set集合中是否包含set2集合的所有元素
        System.out.println(set.removeAll(set2));//删除set集合中属于set2集合的元素
        System.out.println(set.retainAll(set2));//只保留set和set2集合的公共元素
        System.out.println(set);
    }
}
/**本类用于进一步测试Set*/
public class TestSet2 {
    public static void main(String[] args) {
        Set<Student> set = new HashSet<>();
        Student s1 = new Student("张三",3);
        Student s2 = new Student("李四",4);
        Student s3 = new Student("李四",4);
        set.add(s1);
        set.add(s2);
        set.add(s3);
        /*如果set中存放的是自定义的类型
        * 需要给自定义类中添加重写的equals()与hashCode(),才会去重
        * 不然会认为s2和s3的地址值不同,是两个不同的对象,不会去重*/
        System.out.println(set);
    }
}
class Student1 {
    String name;
    int id;

    public Student1(String name, int id) {
        this.name = name;
        this.id = id;
    }

    @Override
    public String toString() {
        return "Student1{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student1 student1 = (Student1) o;
        return id == student1.id &&
                Objects.equals(name, student1.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id);
    }
}

2.进程与线程

2.1 进程的特点

  1. 独立性:进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
  2. 动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
  3. 并发性:多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.

2.1.1 进程、程序之间的区别

  1. 程序:数据与指令的集合,程序是静态的
  2. 进程:给程序加入了时间的概念,不同的时间进程有不同的状态
  3. 进程是动态的,就代表OS中正在运行的程序

2.1.2 并行、串行、并发

CPU:电脑的核心处理器,类似于“大脑”

  1. 并行:相对来说资源比较充足,多个CPU可以同时处理不同的多件事,类似于多车道
  2. 串行:是指同一时刻一个CPU只能处理一件事,类似于单车道
  3. 并发:相对来说资源比较紧缺,多个进程同时抢占公共资源,比如多个进程抢占一个CPU

2.2 线程的特点

  1. 线程是操作系统OS能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.
  2. 一个进程可以开启多个线程,其中有一个主线程来调用本进程中的其他线程。
  3. 多线程可以让同一个进程同时并发处理多个任务,相当于扩展了进程的功能。
  4. 在宏观上,一个CPU看似可以同时处理多件事
    在微观上,一个CPU同一时刻只能处理一件事
    结论:线程的执行具有随机性,我们控制不了,是由OS底层的算法来决定的

2.2.1 线程与进程之间的关系

一个操作系统中可以有多个进程,一个进程中可以包含一个线程(单线程程序),也可以包含多个线程(多线程程序)
在这里插入图片描述

2.3 线程的五种状态

  1. 就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行
  2. 执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
  3. 阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞
  4. 创建状态:线程的创建比较复杂,需要先申请PCB,然后为该线程运行分配必须的资源,并将该线程转为就绪状态插入到就绪队列中
  5. 终止状态:等待OS进行善后处理,最后将PCB清零,并将PCB返回给系统

在这里插入图片描述

2.4 多线程的创建方式

  1. 方式一:继承Thread类
/**本类用于多线程编程实现方案一:继承Thread类来完成*/
public class TestThread1 {
    public static void main(String[] args) {
        /*4.new对应的是线程的新建状态*/
        /*5.要想模拟多线程,至少得启动2个线程,如果只启动1个,是单线程程序*/
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        MyThread t4 = new MyThread();
        /*6.这个run()如果直接这样调用,是没有多线程抢占执行效果
        * 只是把这两句话看作普通方法得调用,谁先写,就先执行谁*/
//        t1.run();
//        t2.run();
        /*7.start()对应得状态就是就绪状态,会把刚刚新建好的线程加入就绪队列之中
        * 至于什么时候执行,就是多线程执行的效果,需要等待OS选中分配CPU
        * 8.执行的时候start()底层会自动调用我们重写的run()中的业务
        * 9.线程的执行具有随机性,也就是说t1-t4具体怎么执行
        * 取决于CPU的调度时间片的分配,我们是决定不了的*/
        t1.start();//以多线程的方式启动线程1,将当前线程变为就绪状态
        t2.start();
        t3.start();
        t4.start();
    }
}
class MyThread extends Thread{
    /*1.多线程实现的方案1:通过继承Thread类,并重写run()完成的*/
    @Override
    public void run() {
        /*2.super.run()表示的是调用父类的业务,我们现在要用自己的业务,所以注释掉*/
        //super.run();
        for (int i = 0; i < 10 ; i++) {
            /*3.getName()表示可以获取当前正在执行的线程的名称
            * 由于本类继承了Thread类,所以可以直接使用这个方法*/
            System.out.println(i+"="+getName());
        }
    }
}

方式二:实现Runnable接口

package cn.tedu.thread;
/*本类用于多线程编程实现方案二:实现Runnable接口来完成*/
public class TestThread2 {
    public static void main(String[] args) {
        //5.创建自定义类的对象--目标业务类对象
        MyRunnable target = new MyRunnable();
        //6.如何启动线程?自己没有,需要与Thread建立关系
        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();
    }
}

//1.自定义多线程类
class MyRunnable implements Runnable{
    //2.添加父接口中的抽象方法run(),里面是自己的业务
    @Override
    public void run() {
        //3.写业务,打印10次当前正在执行的线程名称
        for (int i = 0; i < 10; i++) {
            /*问题:自定义类与父接口Runnable中都没有获取名字的方法
            * 所以还需要从Thread中找:
            * currentThread():静态方法,获取当前正在执行的线程对象
            * getName():获取当前线程的名称*/
            System.out.println(i+"="+Thread.currentThread().getName());
        }
    }
}

2.5 售票案例

方式一:

/*需求:设计多线程编程模型,4个窗口共计售票100张
 * 本方案使用多线程编程方案1,继承Thread类的方式来完成*/
public class TestThreadV2 {
    public static void main(String[] args) {
        //5.创建多个线程对象
        TicketThread2 t1 = new TicketThread2();
        TicketThread2 t2 = new TicketThread2();
        TicketThread2 t3 = new TicketThread2();
        TicketThread2 t4 = new TicketThread2();
        //6.以多线程的方式启动
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
/*1.多线程中出现数据安全问题的原因:多线程程序+有共享数据+多条语句操作共享数据*/
/*2.同步锁:相当于给容易出现问题的代码加了一把锁,加锁以后,这些代码实现同步效果
 * 同步就是排队,需要考虑两个问题:
 * 1)加锁的范围:不能太大,太大,效率太低;也不能太小,太小,锁不住
 * 2)多个线程对象必须是同一把锁,锁对象只有一个,不然锁不住*/
//1.自定义多线程售票类,继承Thread
class TicketThread2 extends Thread{
    //3.定义变量,保存要售卖的票数
    /*问题:4个线程对象共计售票400张,原因是创建了4次对象,各自操作各自的成员变量
     * 解决:让所有对象共享同一个数据,票数需要设置为静态*/
    static int tickets = 100;
    //创建锁对象,锁对象的类型不做限制,但是得唯一
    //Object o = new Object();
    //2.重写父类的run(),里面是我们的业务
    @Override
    public void run() {
        //4.1循环卖票
        while(true){
            //当前类的字节码对象,这个是肯定唯一的
            synchronized (TicketThread2.class) {//解决的是重卖
                if(tickets > 0){//双重校验,解决超卖
                    try {
                        //7.让每个线程经历休眠,增加线程状态切换的频率与出错的概率
                        //问题1:产生了重卖的现象:同一张票卖了多个人
                        //问题2:产生了超卖的现象:超出了规定的票数100,出现了0 -1 -2这样的票
                        Thread.sleep(10);//让当前线程休眠10ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //4.2打印当前正在卖票的线程名称,并且票数-1
                    System.out.println(getName() + "=" + tickets--);
                }
                //4.3做判断,如果没有票了,就退出死循环
                if (tickets <= 0) break;//注意,死循环一定要设置出口
            }
        }
    }
}

方式二:

/*需求:设计多线程编程模型,4个窗口共计售票100张
 * 本方案使用多线程编程方案2,实现Runnable接口的方式来完成*/
public class TestRunnableV2 {
    public static void main(String[] args) {
        //5.创建Runnable接口的实现类对象,作为目标业务对象
        TicketRunnable2 target = new TicketRunnable2();
        //6.创建多个Thread类线程对象,并将target业务对象交给多个线程对象来处理
        Thread t1 = new Thread(target);
        Thread t2 = new Thread(target);
        Thread t3 = new Thread(target);
        Thread t4 = new Thread(target);
        //7.以多线程的方式启动多个线程对象
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

//1.自定义多线程类实现Runnable接口
class TicketRunnable2 implements Runnable{
    //3.定义一个成员变量,用来保存票数100
    /*由于自定义类对象只创建了一次,所以票数被所有线程对象Thread类的对象共享*/
    int tickets = 100;
    Object o = new Object();
    //2.添加接口中未实现的方法,方法里是我们的业务
    @Override
    public void run() {
        //4.1循环卖票
        while(true){
            synchronized (o) {
                //synchronized (TicketRunnable2.class) {
                if (tickets > 0 ){
                    //8.让线程休眠10ms,增加线程状态切换的概率和出错的概率
                    try {
                        Thread.sleep(10);//让当前线程休眠10ms
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //4.2打印当前正在售票的线程名称 & 票数-1
                    System.out.println(Thread.currentThread().getName() + "=" + tickets--);
                }
                //4.3设置死循环的出口,没票了就停止卖票
                if (tickets <= 0) break;
            }
        }
    }
}

3.同步锁

  1. 判断程序有没有可能出现线程安全问题,主要有以下三个条件:
    在多线程程序中 + 有共享数据 + 多条语句操作共享数据
  2. synchronized同步关键字
synchronized (锁对象){
需要同步的代码(也就是可能出现问题的操作共享数据的多条语句);
}

3.1 特点

  1. synchronized同步关键字可以用来修饰代码块,称为同步代码块,使用的锁对象类型任意,但注意:必须唯一!
  2. synchronized同步关键字可以用来修饰方法,称为同步方法
  3. 同步的缺点是会降低程序的执行效率,但我们为了保证线程的安全,有些性能是必须要牺牲的
  4. 但是为了性能,加锁的范围需要控制好,比如我们不需要给整个商场加锁,试衣间加锁就可以了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值