第五周总结

目录

8.2日

一.LinkList集合的特有功能

1.LinkList集合的特点:

2.特有功能:

二.Set集合→HashSet集合

1.Set集合特点:

2.HashSet集合特点:

3.HashSet集合储存String类型数据时,怎么做保证元素唯一

4.HashSet集合存储自定义对象时,如何保证元素唯一?HashSet

三.TreeSet集合如何存储自定义数据

1.TreeSet集合的特点:

2.自定义类型元素排序

四.Map结构→HashMap和TreeMap

8.3日

一.Collection集合工具类

1.Collections:针对集合操作的工具类.

2.Collections针对List集合排序

二.异常的使用

1.System类

2.异常:

3.异常处理

4.编译时期异常和运行时期异常的区别

5.面试题:throw和throws的区别

6.面试题: 捕获异常过程中的finally执行问题

8.4日

一.HashMap

二.TreeMap

1.TreeMap的构造方法:

三.二分搜索法

四.多线程

1.创建线程的实现的三种方式之方式1

2.方式一的弊端:

3.守护线程

4.join():抢到执行权,一口气执行到底

5.yield():暂停当前执行的线程,执行对方线程

8.5日~8.6日

一.多线程的实现方式2

2.解决:Java提供同步机制:同步代码块 将多条对共享数据包裹起来(同步锁synchronized)

3.同步方法

4.死锁问题

优化1

优化2:

二.Lock接口

三.线程组:ThreadGroup

四.线程池:

五.面试题:


8.2日

一.LinkList集合的特有功能

1.LinkList集合的特点:

                线程不安全的类,执行效率高,是链接列表的结构,查询快,增删慢

2.特有功能:

                public void addFirst(Object e):在列表的开头插入元素

                public void addLast(Object e):在列表的末尾加入元素

                public Object getFirst():获取列表的第一个元素

                public Object getLast():获取列表的最后一个元素

                public Object removeFirst():删除列表的第一个元素,并获取第一个元素

                public Object removeLast():删除列表的最后一个元素,并获取最后一个元素

ublic class LinkedListDemo {
    public static void main(String[] args) {
        //创建一个LinkedList集合对象
        LinkedList<String> link = new LinkedList<>() ;
        //添加元素
       // public void addFirst(Object e):在列表开头插入元素
       link.addFirst("hello") ;
       link.addFirst("world") ;
       link.addFirst("JavaEE") ;
       link.addFirst("Android") ;
       link.addLast("Php") ;
       //public Object getFirst():获取列表的第一个元素
        System.out.println(link.getFirst());
        System.out.println(link.getLast());
        // public Object removeFirst(): 删除列表的第一个元素,并获取第一个元素
        System.out.println(link.removeFirst());
        System.out.println(link.removeLast());
       System.out.println(link);
    }
}

二.Set集合→HashSet集合

1.Set集合特点:

                Set集合无序(存储和取出不一致),能够保证元素唯一.

2.HashSet集合特点:

                除继承Set集合特点外,其底层数据结构是一个哈希表(桶结构),是线程不安全的类→不同步但是执行效率高.

3.HashSet集合储存String类型数据时,怎么做保证元素唯一

因为String类型本身已经重写了hashCode()和equals方法,一个String类型的数据如果hashCode和equals都相等,那么认为就是同一个元素,集合存储前面的值.

4.HashSet集合存储自定义对象时,如何保证元素唯一?HashSet<Student>

        在自定义的对象类中,需要重写equals()和hashCode()方法.

  @Override//重写两个方法,保证自定义类元素唯一
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        if (age != student.age) return false;
        return name.equals(student.name);
    }
    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + age;
        return result;
    }
}

三.TreeSet集合如何存储自定义数据

1.TreeSet集合的特点:

        无序性,元素唯一(继承自Set集合),其底层依赖于TreeMap集合,具有红黑树结构(也称"自平衡的二叉树结构"),可以实现Map集合的自然排序以及比较器排序,取决于使用的构造方法.

        构造方法:

                public TreeSet():构造一个空的树,实现元素自然排序(取决于存储的元素能否实现Comparable接口)

        自然排序     执行的是TreeSet的无参构造方法,而且前提条件是当前存储的类型必须实现Comparable接口.

2.Integer类型元素按照自然排序(默认升序排序)

                Intger元素已经实现了Comparable接口,直接添加元素排序

ublic class TreeSetDemo {
    public static void main(String[] args) {
        //  public TreeSet()
        //Integer类型
        TreeSet<Integer> ts = new TreeSet<>() ;
        //Intger元素 实现Comparable接口---就能够按照元素自然排序(默认升序排序)
        //添加元素
        ts.add(20) ;
        ts.add(17) ;
        ts.add(17) ;
        ts.add(18) ;
        ts.add(24) ;
        ts.add(23) ;
        ts.add(24) ;
        ts.add(19) ;
        //遍历集合TreeSet
        for(Integer i:ts){
            System.out.println(i);
        }
    }
}

2.自定义类型元素排序

        Student类型元素自然排序需要实现Comparable接口,并且重写compareTo()方法

public class Student implements  Comparable<Student>{
    private String name ;//姓名
    private int age ;//年龄
     //排序的代码
    @Override
    public int compareTo(Student s) { //后面需要和学生对象对比
        //主要条件:就是按照年龄从小到大排序
        //定义一个变量
        //年龄int类型
       /* int num = this.age - s.age ;
        // int num = s.age - this.age ;//从大到小
        //次要条件:年龄相同,还要比较姓名的内容是否相同
        int num2 = (num==0)?(this.name.compareTo(s.name)):num;    //字符串的字典顺序比较
                            //gaoyuanyuan
                            //jacky
        return num2;*/
       //主要条件:按照学生姓名的长度:从小到大进行排序
        int num = this.name.length() - s.name.length() ;
        //如果长度相同,还比较内容是否一样          "hello" ,"hel"
        int num2 = (num==0)?(this.name.compareTo(s.name)):num ;
        //如果长度相同,内容一样
        //按照学生的年龄从小到大比
        int num3 = (num2==0)? (this.age - s.age) :num2 ;
        return num3 ;
    }
}

四.Map结构→HashMap和TreeMap

1. Map集合的特点:

                无序的.

          双列集合,Map<K,V>:提供一个键值对元素(两种类型),键必须唯一(Map针对键有效,跟值无关)  键映射到值的对象,Map集合可以多个值,但键必须唯一.       

2. Map集合的功能:

  V   put(K key,V value):添加键值对元素
注意事项:
          如果key是第一次添加,那么返回的结果为null
          如果key是否重复添加,第二次添加,返回的上一次添加的键对应的值

  V   remove(Object key):删除指定的键,返回被删除键对应的值
        void clear()
         boolean containsKey(Object key) :是否包含指定的键 (使用居多)
         boolean containsValue(Object value):是否包含指定的值
3.Map集合的遍历

    1).Set(K) keySet():获取当前Map集合中所有的键的集合.

        V get(Object key):通过键获取值   

    public static void main(String[] args) {
        //创建Map集合对象
        Map<String,String> map = new HashMap<>() ;
        //添加元素
        map.put("令狐冲","东方不败") ;
        map.put("杨过","小龙女") ;
        map.put("陈玄风","梅超风") ;
        map.put("郭靖","黄蓉") ;
        // Set<K> keySet()  :获取当前Map集合中的所有的键的集合
        //方式一
        Set<String> keySet = map.keySet(); //推荐第一种方式
        //增强for遍历
        for(String key: keySet){
            //获取所有的键的元素
            //  V get(Object key):通过键获取值
            String value = map.get(key);
            System.out.println(key+"="+value);
        }

    2)获取所有的键值对对象

            Set<Map.Entry<K,V>> entrySet()

                通过键值对象 获取键 /获取值   

                        K getKey()/V getValue()

 public static void main(String[] args) {
        //创建Map集合对象
        Map<String,String> map = new HashMap<>() ;
        //添加元素
        map.put("令狐冲","东方不败") ;
        map.put("杨过","小龙女") ;
        map.put("陈玄风","梅超风") ;
        map.put("郭靖","黄蓉") ;
             //方式2:
        //Set<Map.Entry<K,V>> entrySet()
        Set<Map.Entry<String, String>> entry = map.entrySet();
        //增强for:遍历键值对对象获取到
        for(Map.Entry<String, String> en: entry){
            //获取键和值
            //K getKey()
           // V getValue()
            String key = en.getKey();
            String value = en.getValue();
            System.out.println(key+"="+value);
        }
    }

4.Map和Collection集合的区别:

        Collection:只能存储一种类型 Collection<E>

        Map集合:可以两种类型的,键的类型,值的类型 Map<K,V> 

遍历方式不同

        Collection:就通过5种方式(List)

        Map:两种方式:

                方式1:获取所有的K的集合(键的集合)通过键获取值

                方式2: 获取所有的键值对对象Map.Entry<K,V> 

                           通过键值对对象获取所有的键

                           通过键值对对象获取所有的值

内在联系:

      Collection---->TreeSet集合(Collection的子实现类)---->间接的使用到了TreeMap集合的put方法

      Collection------>HashSet集合(Collection的子实现类)---->间接使用到了HashMap的put方法

8.3日

一.Collection集合工具类

1.Collections:针对集合操作的工具类.

        静态功能有:

        public static <T extends Comparable<? super T>> void sort(List<T> list):按照自然升序排序(针对List集合排序)

        public static <T> void sort(List<T> list,Comparator<? super T> c):按照比较器排序针对List集合

        public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T>:获取当前自然顺序中List的最大值

        public static <T extends Object & Comparable<? super T>> T min(Collection<? extends T>:最小值

        public static void reverse(List<?> list):对List集合顺序反转

        public static void shuffle(List<?> list):随机置换

   public static void main(String[] args) {
        //创建List集合
        List<Integer> list = new ArrayList<>() ;
        //添加元素
        list.add(10) ;
        list.add(50) ;
        list.add(15) ;
        list.add(25) ;
        list.add(5) ;
        list.add(12) ;
        System.out.println(list);
        System.out.println("---------------------------------");
        //按照自然升序排序(针对List集合排序)
        Collections.sort(list);
        System.out.println(list);
        System.out.println("----------------------------------");
        //按照比较器排序针对List集合
        Integer max = Collections.max(list);
        System.out.println(max);
        System.out.println("----------------------------------");
        System.out.println(Collections.min(list));
        System.out.println("-----------------------------------");
        Collections.reverse(list);//反转
        System.out.println(list);
        System.out.println("------------------------------------");
        //  public static void shuffle(List<?> list):随机置换
        Collections.shuffle(list);
        System.out.println(list);

    }

2.Collections针对List集合排序

        需要集合中存储的类型必须实现Comparable接口.或者使用匿名内部类方式实现

public class CollectionsTest {
    public static void main(String[] args) {
        //创建List集合对象
        List<Student> list = new ArrayList<>() ;
        //创建几个学生对象
        Student s1 = new Student("gaogaoyuan",42) ;
        Student s2 = new Student("gaogaoyuan",40) ;
        Student s3 = new Student("liushishi",42) ;
        Student s4 = new Student("wanglihong",45) ;
        Student s5 = new Student("wenzhang",38) ;
        Student s6 = new Student("huazi",35) ;
        Student s7 = new Student("huazi",32) ;
        Student s8 = new Student("zhangjunjie",20) ;
        list.add(s1) ;
        list.add(s2) ;
        list.add(s3) ;
        list.add(s4) ;
        list.add(s5) ;
        list.add(s6) ;
        list.add(s7) ;
        list.add(s8) ;
        //使用比较器排序:针对List集合
        //public static <T> void sort(List<T> list,Comparator<? super T> c):按照比较器排序针对List集合
        //匿名内部类比较推荐
        Collections.sort(list, new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                int num  = s1.getAge() - s2.getAge() ;
                int num2 = (num==0)?(s1.getName().compareTo(s2.getName())):num ;
                return num2;
            }
        });
        for(Student s:list){
            System.out.println(s.getName()+"---"+s.getAge());
        }
    }
}

二.异常的使用

1.System类

        不能被实例化.

成员变量:

        public static final InputStream in:标准输入流

        public static final PrintStream out:标准输出流

        public static final PrintStream err:错误输出流(打印错误信息/一些信息需要用户引起注意:相关的日志)

        System.exit(0) :jvm退出 ,0表示正常终止

        public static void gc():手动开启垃圾回收器,会去回收内存中没有更多引用的对象

2.异常:

Throwable:包含所有的错误以及异常.  它是一个超类(父类)

         这个超类包括:

                        error:非常严重问题  (跟代码没有太大有关系)        

                        Exception:异常

                              异常分为:  编译时期异常和运行时期异常(RuntimeException):程序在运行过程中出现问题(代码书写不严谨)

3.异常处理

        1)标准格式:try...catch...finally

                常用格式:try...catch..catch...catch(要几个写几个)

        2) throws:抛出

 public static void main(String[] args) {
      //使用try...catch进行捕获异常
        try{
            //可能出现问题代码
            int a = 10 ;
            int b = 0 ;  //直接获取到的,以后可能值---->通过一些方法获取到的值
            System.out.println(a/b);
            System.out.println("over");
        }catch(ArithmeticException e){  //捕获异常:可以使用大的Exception,但是捕获:具体异常具体捕获            System.out.println("除数不能为0");
        }
    }

4.编译时期异常和运行时期异常的区别

        运行时期异常:  一般程序员逻辑结构不严谨导致的问题,调用者可以进行显示处理(try...catch.../throws)也可以不进行显示处理,通过逻辑语句进行处理!

        编译时期异常:调用者必须显示处理,不处理,编译通过不了,程序运行不了

                如果在当前方法中已经去捕获了try...catch...,调用者无需进行处理,但是如果在方法中抛出异常的,调用者必须处理(捕获/抛出throws)

开发中:尽量有限使用try....catch处理异常,其次再使用throws.

        1)有的时候没有办法去抛出,继承关系中,如果子类继承父类,父类的该方法没有异常,子类重写该方法的时候,只能try...catch

         2)子类继承父类,如果父类的该方法本身抛出异常了,那么子类重写该方法的时候,要么跟父类的方法的异常类名一致,要么是该异常的类子类.

5.面试题:throw和throws的区别

        共同点:都是抛出

         1)使用位置不同

         throws:

                ①将异常抛出在方法声明上

                ②在方法名的后面可以跟多个异常类名,中间逗号隔开

        throw:

                ①在方法的语句体中的某个逻辑语句中

                ②他后面只能跟异常对象,而不是类名

        2)调用者是否需要处理

                 throws:调用者必须进行显示处理(try...catch/throws),否则报错

                 throw:调用者无须显示处理,一般情况都是在逻辑语句进行处理
         3).出现异常是否肯定性

                throws:在方法上的,执行某个方法的代码中,可能有问题(表示出现异常的一种可能性)

                throw:执行某段代码一定会执行这个异常(表示出现异常的一种肯定性)

        4)throws:将具体的处理交给jvm---通过jvm吧异常信息打印控制台上,显示的底层源码而且会显示当前错误消息字符串

        throw:程序中某段代码有问题:只是打印异常类名(jvm处理)

不管使用throws/try...catch...finally:都是要通过jvm调用Throwable里面的功能完成日志(错误信息)打印.

6.面试题: 捕获异常过程中的finally执行问题

如果在某个方法中捕获异常,但是该方法有返回值类型,如果在catch语句出现return语句,finally代码还会执行吗?如果会执行,在return前还是在后?

public class Test {
    public static void main(String[] args) {
        int num = getNum(10) ;// i=10
        System.out.println(num);
    }
    private static int getNum(int i) {
        try{
            i = 20 ; //i = 20 ;
            System.out.println(i/0); //除数为0
        }catch (ArithmeticException e){
            i = 30 ;        //i =30
            return i ;      // return i  = return 30 :已经在catch语句形成返回的路径 返回结果就是30
        }finally {          //finally代码一定会执行,除非jvm退出了
            i = 40 ;        // i = 40
           // return i ;
        }
       return i;       //30这个return语句只是为了保证编译不报错而添加的,并不会改变i的值
    }
}

finally会执行,但是现在这个代码,在catch语句已经形成返回路径,它会记录最终返回就是30;finally是去释放资源用的,很少牵扯业务代码;都会执行的,除非jvm退出!

8.4日

一.HashMap

自定义类型,HashMap的put方法依赖于hashCode()和equals方法,为了保证键的唯一,键的类型必须重写Object类的hashCode和equals方法.

因为重写了hashCode和equals方法,所以HashMap不需要实现Comparable接口,就可以对元素进行排序.

二.TreeMap

红黑树结构,,键属于自定义的情况时,针对Map的键按照条件进行排序.

TreeMap<Student,String>

1.TreeMap的构造方法:

        public TreeMap();针对键进行自然排序

        public TreeMap(Comparator<? super K> comparator):针对键按照比较器进行排序.

1)TreeMap在使用无参构造方法进行自然排序的时候,键的类型必须实现Comparable接口.

2)TreeMap在使用比较器排序时,一般推荐使用匿名内部类的方法,可以不在键的类型中实现Comparable接口.

比较器排序时的代码:

 public static void main(String[] args) {
      //比较器排序:匿名内部类
        TreeMap<Student,String> tm = new TreeMap<>(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                //主要条件:学生的年龄从大到小排序
                int num = s2.getAge() - s1.getAge() ;
                //如果年龄相同,要比较姓名的内容是否相同
                int num2 = (num==0)? (s1.getName().compareTo(s2.getName())):num ;
                return num2;
            }
        }) ;
        //创建学生对象
        Student s1 = new Student("唐伯虎",38) ;
        Student s2 = new Student("唐伯虎",38) ;
        Student s3 = new Student("秋香",30) ;
        Student s4 = new Student("祝枝山",40) ;
        Student s5 = new Student("祝枝山",45) ;
        Student s6 = new Student("文征明",39) ;
        Student s7 = new Student("石榴姐",20) ;
        Student s8 = new Student("东香",18) ;
        Student s9 = new Student("徐香",18) ;

        tm.put(s1,"明朝") ;
        tm.put(s2,"宋代") ;
        tm.put(s3,"清朝") ;
        tm.put(s4,"明朝") ;
        tm.put(s5,"现代") ;
        tm.put(s6,"唐朝") ;
        tm.put(s7,"宋代") ;
        tm.put(s8,"明朝") ;
        tm.put(s9,"现代") ;

        Set<Student> students = tm.keySet();
        for(Student key :students){
            String value = tm.get(key);
            System.out.println(key.getName()+"---"+key.getAge()+"---"+value);
        }
    }
}

三.二分搜索法

前提条件数组必须有序,如果数组本身无序,让我们查询元素,先排序再去查找!(有条件需求先排序,再查,如果没有条件需求,只能使用基本元素查找法:从头查找到尾)

Arrays工具类:

        提供sort(任何数组进行排序):元素升序排序Arrays.sort (arr);

        提供了二分搜索法(任何类型的数组,int key):int index4 = Arrays.binarySearch(arr, 55);

import java.util.Arrays;
public class BinarySearch {
    public static void main(String[] args) {
        //已知数组,静态初始化
        int[] arr = {11,22,33,44,55} ;
        //调用二分搜索方法查询
        int index = binarySearch(arr, 22);
        System.out.println(index);
        int index2 = binarySearch(arr,66) ;
        System.out.println(index2);
        int index3 = binarySearch(arr,44) ;
        System.out.println(index3);

        System.out.println("-----------------------------------");
        int index4 = Arrays.binarySearch(arr, 55);//导包以后,直接调用二分搜索法
        System.out.println(index4);
        int index5 = Arrays.binarySearch(arr, 66);
        System.out.println(index5);
    }
    //返回值int
    //方法参数:数组,查询的元素
    public static int binarySearch(int[] arr,int target){
        //防止空指针异常
        if(arr!=null){
            //定义数组的最小索引:
            int min = 0 ;
            //定义最大索引
            int max = arr.length -1 ;
            //使用循环while
            while(min<=max){
                //计算中位点索引
                int mid = (min+max)/2 ;
                //如果当前中位点对应的元素小于要要查找的元素
                if(target < arr[mid]){
                    //左半区域:继续折半
                    max = mid -1 ;
                }else if(target > arr[mid]){
                    //右边区域:继续折半
                    min = mid + 1 ;
                }else{
                    //查询到了
                    return mid ;
                }
            }
        }
        //循环结束之后,还没有找,则返回-1
        return -1 ;
    }
}

四.多线程

1.创建线程的实现的三种方式之方式1

      方式1:

        1)将一个类声明为Thread的子类

        2) 这个子类应该重写Thread类的run方法

        3)然后可以分配并启动子类的实例。

                注意:启动线程用的是start()而不是run(),run()知识一个普通方法,不会出现互相抢占CPU执行权的情况.

public class MyThread extends Thread {
    //重写Thread类的方法
    @Override
    public void run() {
        //run方法里面:一般情况耗时的操作
        for(int x = 0 ; x < 200 ; x ++){
            System.out.println(x);
        }
    }
}


public class ThreadDemo {
    public static void main(String[] args) {
        //3)创建Thread类的子类对象
        MyThread my1 = new MyThread() ;//第一个线程对象
        MyThread my2 = new MyThread() ; //第二个线程对象

        //4)启动
       /*   my1.start();
        my1.start();
            my1只是代表一个线程对象
            my1将start方法调用两次---->IllegalThreadStateException:非法线程状态异常
            start()原码: 校验当前线程状态:如果线程已经启动了,就不能再启动
           */
     my1.start();//start():有jvm调用底层run方法,出现并发执行
     my2.start();
    }
}

2.方式一的弊端:

        1)它是一个继承关系, 具有"局限性"

        2)不能够体现资源共享的概念:st1,st2,st3 :三个栈内存变量,分别需要new对象.

Thread类的构造方法:

        Thread(String name):创建线程类对象,设置名称

Thread类的成员方法:

        public final void setName(String name):设置线程名称

        public final String getName():获取线程名称

线程中的优先级:

        public static final int MAX_PRIORITY 10     最大优先级

        public static final int MIN_PRIORITY 1      最小优先级

        public static final int NORM_PRIORITY 5     默认优先级

        public final void setPriority(int newPriority):设置线程的优先级

        public final int getPriority():获取优先级

        优先级越大的抢占CPU执行权的几率越大

3.守护线程

        public final void setDaemon(boolean on),参数为true,表示标记当前线程为守护线程,当正在运行的线程如果都是守护线程,则jvm自动退出,运行的线程不会立即停止  这个方法必须在启动线程之前调用(start()之前)

            如下:刘备运行完后,关羽张飞运行一段时间后自动停止

public class ThreadDaemonDemo {
    public static void main(String[] args) {
        //创建两个线程
        ThreadDaemon td1 = new ThreadDaemon() ;
        ThreadDaemon td2 = new ThreadDaemon() ;
        //设置名称
        td1.setName("张飞");
        td2.setName("关羽");
        //设置为守护线程
        td1.setDaemon(true) ;
        td2.setDaemon(true) ;
        //启动线程
        td1.start();
        td2.start();
//        public static Thread currentThread():获取正在运行的线程执行对象的引用
        Thread.currentThread().setName("刘备");
        //提供for循环:
        for(int x = 0 ; x < 5 ; x ++){
            System.out.println(Thread.currentThread().getName()+":"+x);
        }
    }
}

4.join():抢到执行权,一口气执行到底

public final void join() throws InterruptedException:等待该线程终止

        被线程调用join()方法后,只要抢到CPU执行权,就会执行完毕线程,期间其他线程不会被执行.

        底层依赖于线程安全的方法join(0):永远等待(当前执行完毕结束!).又依赖于wait(long time)

public class ThreadJoinDemo {
    public static void main(String[] args) {
        //创建三个线程
        JoinThread jt1 = new JoinThread() ;
        JoinThread jt2 = new JoinThread() ;
        JoinThread jt3 = new JoinThread() ;
        //设置线程名称
        jt1.setName("李渊") ;
        jt2.setName("李世民") ;
        jt3.setName("李元霸") ;
        //启动线程
        jt1.start();
        //jt1调用join
        try {
            jt1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        jt3.start();
    }
}

5.yield():暂停当前执行的线程,执行对方线程

public static void yield():暂停当前正在执行的线程,执行对方线程

public class YieldThread  extends Thread{
    //yt1/yt2
    @Override
    public void run() {
        for(int x = 0 ; x <100 ; x ++){
            System.out.println(getName()+":"+x);
            Thread.yield(); //暂停当前线程,执行对方线程
        }
    }
}

public class ThreadYieldDemo {
    public static void main(String[] args) {
        //创建两条线程对象
        YieldThread yt1 = new YieldThread() ;
        YieldThread yt2 = new YieldThread() ;
        //设置名称
        yt1.setName("高圆圆") ;
        yt2.setName("赵又廷") ;
        //启动线程
        yt1.start();
        yt2.start();
    }
}

8.5日~8.6日

一.多线程的实现方式2

               步骤:

        1)自定义类实现Runnable接口.

        2)重写Runnable接口中的run方法

        3)在main用户线程中创建类的实例

        4)创建当前类对象,然后在创建Thread类对象,将当前类对象作为参数传递

        5)分别启动线程

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        //耗时的操作
        for(int  x = 0 ; x < 100 ; x ++){
            //public static Thread currentThread()
            System.out.println(Thread.currentThread().getName()+":"+x);
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        //可以分配类的实例(创建类的实例)
        MyRunnable my  = new MyRunnable() ; //资源类:被多线程共享//具体类new 具体类
        //创建两个线程类对象
        Thread t1  = new Thread(my,"张俊杰") ;
        Thread t2  = new Thread(my,"高圆圆") ;
        //分别启动线程
        t1.start();
        t2.start();
    }
}

第二种实现方式即静态代理

        特点是:真实角色和代理角色必须实现同一个接口,其中真实角色专注于自己的功能,代理角色完成对真实角色的功能的拓展.

public class ThreadDemo {
    public static void main(String[] args) {
        //接口多态
        //Mary mary = new You() ;
        You mary = new You() ;
        mary.mary();
        System.out.println("----------------------");
        //静态代理:通过婚庆公司帮助自己You来完成结婚
        //真实角色
        You you2  = new You() ;     // MyRunnable
        WeddingCompany wc = new WeddingCompany(you2) ;// Thread类对象
        wc.mary();
    }
}
//定义一个接口的接口
interface  Mary{
    void mary() ;//结婚
}
//自己:真实角色
class You implements  Mary{
    @Override
    public void mary() {
        System.out.println("结婚了,很开心...");
    }
}
//代理角色:婚庆公司 在你结婚之前,它可以给你布置婚礼线程, 结婚之后,开开心心吃席
class WeddingCompany implements Mary{
    //将真实角色作为参数传递
    private You you ;
    public WeddingCompany(You you){
        this.you = you ;
    }
    @Override
    public void mary() {
        System.out.println("给你布置婚礼现场...");
        you.mary();  //只要专注于自己的事情!
        System.out.println("婚礼线程布置完毕,吃席...");
    }
}

检验多线程安全问题的标准:

        1)是否是多线程环境

        2)是否存在数据共享

        3)是否存在多条语句对共享数据进行操作    (解决点)

2.解决:Java提供同步机制:同步代码块 将多条对共享数据包裹起来(同步锁synchronized)

        synchronized(锁对象){

                将多条对共享数据包裹起来

}

锁对象:必须要多个线程使用的同一个锁对象,而不是分别自己的锁对象!

public class SellTicket implements Runnable {
    //成员变量;100张票
    public static int tickests = 100 ;
    //创建一个锁对象:
    public Object obj = new Object() ;
    //t1,t2,t3
    @Override
    public void run() {
        //模拟一直票
        while(true){
            //t1,t2,t3
            //解决方案:
            //将多条语句对共享数据的操作包裹起来
            //synchronized (new Object()){  //锁对象 :三个线程分别使用自己的锁
                //必须为是同一个锁对象
           synchronized (obj){
                //模拟网络延迟
                //判断
                if(tickests>0){//100>0
                    //t1先进来,睡眠150毫秒,t1已经睡完了,执行下面的操作
                    //t3先进来,睡眠150毫秒,t3醒来之后
                    //t2最后抢占到,醒来之后
                    try {
                        Thread.sleep(100); //单位为毫秒数
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //输出窗口信息
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickests--)+"张票");
                }
 }
public class SellTicketTest {

    public static void main(String[] args) {
        //创建资源类对象SellTicket
        SellTicket st = new SellTicket() ;

        //创建三个线程类对象
        Thread t1 = new Thread(st,"窗口1") ;
        Thread t2 = new Thread(st,"窗口2") ;
        Thread t3 = new Thread(st,"窗口3") ;

        //分别启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

3.同步方法

        什么是同步方法? 如果一个方法的方法体的第一句话就是同步代码块

        可以将synchronized关键字提取到方法声明上,跟在权限修饰符的后面

4.死锁问题

 线程安全问题:可以通过同步方法或者是同步代码块去解决,但是执行过程中就可能出现死锁问题

死锁问题:(使用同步机制解决线程安全时) 线程和线程之间出现了互相等待的情况!

解决方案:

        多个线程之间的通信:必须使用的是一个资源类对象,而不能是每一个线程在使用自己的资源类对象!使用生成者和消费者模式思想去解决,前提条件:生成者线程和消费者线程 必须操作的同一个资源类对象!.

优化1

类属性
public class StuffBun {
    //成员变量不私有化
    String name ;//包子的类型(肉包子,菜包子)
    String bunType ;//大包子/小包子
}
生成者资源类
public class SetBun implements  Runnable {
    //声明这个包子类
    private StuffBun stu ;
    public SetBun(StuffBun stu){
        this.stu = stu ;
    }
    //定义一个统计变量
    int x = 0 ;
    @Override
    public void run() {
        //产生包子
      /*  StuffBun stu = new StuffBun() ;
        stu.name = "肉包子" ;
        stu.bunType = "大类型";*/
      //不断的产生数据
      while(true){
          synchronized (stu){
              if(x % 2 == 0){//t1
                  stu.name = "肉包子" ;
                  stu.bunType = "大包子";
              }else{
                  stu.name = "菜包子" ;
                  stu.bunType = "小包子" ;
              }
          }
          x ++ ;
      }
    }
}
消费者资源类
public class GetBun implements Runnable {
    //声明包子类的变量stb
    private StuffBun stb ;
    public GetBun( StuffBun stb){
        this.stb = stb ;
    }
    @Override
    public void run() {
        //模拟要使用数据
      //  StuffBun stb = new StuffBun() ;
        //不断使用数据
        while(true){
            synchronized (stb){
                System.out.println(stb.name+"---"+stb.bunType);
            }

        }
    }
}
用户线程mian
public class ThreadDemo {
    public static void main(String[] args) {
        //创建一个包子对象
        StuffBun sbu  = new StuffBun() ; //同一个对象
        //创建生产资源类对象
        SetBun sb = new SetBun(sbu) ;
        //消费者资源类对象
        GetBun gb = new GetBun(sbu) ;
        //创建线程了对象
        Thread t1 = new Thread(sb) ;//生产者资源类所在的生产者线程
        Thread t2 = new Thread(gb) ;//消费者资源类所在的消费者线程
        t1.start();
        t2.start();
    }
}

按照上面的方式:模拟生产者产生数据,消费者使用数据出现问题 null---null
生产资源类中和消费者资源类中所操作的包子对象不是同一个对象!
可以将包子通过生产资源类或者消费者资源类 通过构造方法传递
 优化1:
        加入while循环,模拟包子一直生产和一直消费!
                  出现问题:数据紊乱:加入同步代码块给每一个资源类中都加入解决! 将多条语句对共享数据的操作包起来!

优化2:

        想出现依次打印:肉包子---大包子;      菜包子---小包子

         wait()+notify()--->实现同步机制(并且同时信号法:将死锁问题解决!)

public class SetBun implements  Runnable {
    //声明这个包子类
    private StuffBun stu ;
    public SetBun(StuffBun stu){
        this.stu = stu ;
    }
    //定义一个统计变量
    int x = 0 ;
    @Override
    public void run() {
        //产生包子
      /*  StuffBun stu = new StuffBun() ;
        stu.name = "肉包子" ;
        stu.bunType = "大类型";*/
      //不断的产生数据
      while(true){
          synchronized (stu){
              //如果当前生产者没有语句,需要等待生成产生数据
              if(stu.flag){
                  //锁对象调用wait发那个发
                  try {
                      stu.wait();//释放锁对象...
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
              if(x % 2 == 0){//t1
                  stu.name = "肉包子" ;
                  stu.bunType = "大包子";
              }else{
                  stu.name = "菜包子" ;
                  stu.bunType = "小包子" ;
              }
              //如果现在有数据了
              //改变信号
              stu.flag  = true ;//有数据类
              //通知(唤醒)消费者线程,赶紧使用数据
              stu.notify(); //唤醒对方线程
          }
          x ++ ;
      }
    }
}
public class GetBun implements Runnable {
    //声明包子类的变量stb
    private StuffBun stu ;
    public GetBun( StuffBun stu){
        this.stu = stu ;
    }
    @Override
    public void run() {
        //模拟要使用数据
      //  StuffBun stb = new StuffBun() ;
        //不断使用数据
        while(true){
            synchronized (stu){
                //如果当前消费资源类中存在包子数据,先等待消费使用完毕数据
                if(!stu.flag){
                    //等待使用完毕数据
                    try {
                        stu.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(stu.name+"---"+stu.bunType);
                //改变信号值
                //如果包子消费完毕
                stu.flag = false ;
                //唤醒对方线程(生产者资源类,别等了,产生数据)
                stu.notify();
            }
        }
    }
}

二.Lock接口

JDK5以后提供java.util.current.locks.Lock   :提供比syncrhonized方法(/同步代码块)更具体的锁定操作,多个线程并发访问,抢占共享资源数据,通过lock实现多个线程对某个共享资源进行独占访问,不会出现安全问题!

void lock()获取锁

void unlock()释放锁

Lock接口具体的子实现类为ReentrantLock

public class SellTicket implements  Runnable {
    //定义100张票
    private static int tickets = 100 ;
    //创建一个锁对象
    Lock lock = new ReentrantLock() ;
    @Override
    public void run() {
        //模拟一只有票
        while(true){
            //通过锁对象--->获取锁
            lock.lock();
            //try...catch...finaly:捕获异常
            //使用try..finally
            try{
                //判断
                if(tickets>0){
                    //睡眠100毫秒
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
                }else{
                    break ;
                }
            }finally {
                //释放锁
                lock.unlock();
            }
        }
    }
}

三.线程组:ThreadGroup

线程组代表一组线程,线程组也可以包括多个其他线程组.

线程组: 将线程可以都添加一组中,方便管理.线程启动完毕之后,线程终止之后,不会将这个线程对象在内存中重复利用

public class ThreadGroupDemo {
    public static void main(String[] args) { //jvm调用main方法
      //  method1();
        method2() ;
    }
    //设置一个新的线程组名称
    private static void method2() {
        //创建一个线程组对象--同时设置线程组名称
        ThreadGroup tg = new ThreadGroup("myMain") ;
        //创建两条线程对象
        MyThread my = new MyThread() ;
        Thread t1 = new Thread(tg,my) ;
        Thread t2 = new Thread(tg,my) ;
        //获取线程组对象并同时线程组名称
        String name1 = t1.getThreadGroup().getName();
        String name2 = t2.getThreadGroup().getName();
        System.out.println(name1+"---"+name2);
    }
    private static void method1() {
        //创建两个线程
        MyThread my  = new MyThread() ;
        Thread t1 = new Thread(my) ;
        Thread t2 = new Thread(my) ;
        ThreadGroup tg1 = t1.getThreadGroup();
        ThreadGroup tg2 = t2.getThreadGroup();
        String name1 = tg1.getName();
        String name2 = tg2.getName();
        System.out.println(name1+"---"+name2);  //默认线程组名称都是main
    }
}

四.线程池:

1.特点:

        在内存中创建一个固定可重用的线程数,当前线程执行完毕终止了,不会被回收掉,再次
回到线程池中,等待下一次利用!

弊端:维护成本大.

ExecutorService---接口
            通过 工厂类:Exceutors  创建一个固定的可重用的线程数,返回值就线程池对象
          public static ExecutorService newFixedThreadPool(int nThreads)
     ExecutorService
                 提交队列任务(多个线程并发执行)
                  <T> Future<T> submit(Callable<T> task)
     提交值返回任务以执行,并返回代表任务待处理结果的Future。
                    Future<?> submit(Runnable task)
                    submit的返回值:异步计算的结果,如果不做计算,无须返回结果!

public class ThreadPoolDemo {
    public static void main(String[] args) {
        //通过工厂类创建线程池对象
        ExecutorService threadPool = Executors.newFixedThreadPool(2);
        //提交异步方法
        //MyRunnable:打印x的值0-99之间的数据,不需要返回结果
      //  threadPool.submit(new MyRunnable()) ;
      //  threadPool.submit(new MyRunnable()) ;
        //   <T> Future<T> submit(Callable<T> task)
        //Callable:提交异步计算---需要重写Callable的call来计算结果;如果没有结果,直接在call无须返回
        threadPool.submit(new MyCallable()) ;
        threadPool.submit(new MyCallable()) ;
        //void shutdown()关闭线程池
        threadPool.shutdown();
    }
}

五.面试题:

1.什么是线程和进程

线程依赖于进程
进程:能够调用系统资源的独立单位(每一个客户端都会开启对应进程)
    电脑----任务管理---可以看到很多进程----pid(进程的id号)
线程:是程序中执行的最小单元,一个进程有很多线程的组成,一个线程看成是某个任务 
    线程的执行具有随机性(多个线程并发执行)以及原子性
    并发:在一个时间点同时和cpu性能有关系(CPU的逻辑核数)
    并行:在一个时间段内容同时和cpu性能有关系(CPU的逻辑核数)

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值