Java基础4:List的LinkedList子实现类、二分法查找、Vector的特有遍历方式、TreeSet集合(排序)、Map集合、Collections工具类、模拟斗地主发牌、异常、多线程。

1.List的LinkedList子实现类

特点:线程不安全,执行效率高。底层用链表实现,查询慢,增删快。

特有功能:

  •      public void addFirst(Object e):在列表开头插入元素
    
  •      public void addLast(Object e):将元素追加到列表的末尾 
    
  •      public Object getFirst():获取列表的第一个元素
    
  •      public Object getLast():获取列表的最后一个元素
    
  •      public Object removeFirst(): 删除列表的第一个元素,并获取第一个元素
    
  •      public Object removeLast():删除列表的最后一个元素,并获取最后一个元素
    

常根据其功能特点,模拟栈的结构特点

import java.util.LinkedList;

//泛型:定义类上,定义在方法上,定义接口上
public class MyStack<T> {  

    //成员变量,声明LinkedList类型变量
    private LinkedList<T> link ;   //String

    //提供无参构造方法
    public MyStack(){
        //给link初始化
        link = new LinkedList<>() ;
    }

    //提供添加的功能,利用LinkedList的addFirst()
    public void set(T t){//String
        link.addFirst(t);//每一次往列表的开头添加元素
    }
    //提供删除的功能,利用LinkedList的removeFirst():删除并返回第一个元素
    public <T> T get(){//将泛型定义在方法上 //获取的元素 String类型
            return (T) link.removeFirst();  //removeFirst()---->本身Object类型
    }

    //提供一个判断功能
    public boolean isEmpty(){
        return link.isEmpty() ;
    }
}

2.泛型的高级通配符

<?> :任意Java类型,包括Object <? super E> : 向上限定:E类型以及E父类 <? extends E> : 向下限定:E以及它的子类 ## 3.Set集合 特点:无序(存储和取出不一致),能够保证元素唯一。 ### 3.1 HashSet:底层数据结构基于哈希表(桶结构) 线程不安全的类,即不同步,执行效率高。 JDK8以后;提供了juc(并发包:java.util.concurrent):ConcurrentHashMap
  •      public static void shuffle(List<?> list):随机置换
    
public class CollectionsDemo1 {
    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("---------------------------------");
        //public static <T extends Comparable<? super T>> void sort(List<T> list):
        Collections.sort(list);
        System.out.println(list);
        System.out.println("----------------------------------");
        //public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T>
        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);

    }
}

9.利用Collection集合和Map集合模拟斗地主发牌

import java.util.*;

public class PokerTest {
    public static void main(String[] args) {
        String[] strNum = {"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
        String[] strPattern = {"♥","♠","♣","♦"};

        //牌盒
        Map<Integer, String> map = new HashMap<>();
        int index = 0;
        for (int x = 0 ; x < strNum.length ; x ++ ){
            for (int y = 0 ; y < strPattern.length ; y ++){
                map.put(index,strNum[x].concat(strPattern[y]));
                index++;
            }
        }
        map.put(index,"小王") ;
        map.put(index+1,"大王") ;

        //洗牌
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 54; i++) {
            list.add(i);
        }
        Collections.shuffle(list);

        //发牌
        Set<Integer> player1 = new TreeSet<>();
        Set<Integer> player2 = new TreeSet<>();
        Set<Integer> player3 = new TreeSet<>();
        Set<Integer> dipai = new TreeSet<>();

        for (int i = 0 ; i < list.size()-3; i+=3){
            player1.add(list.get(i));
            player2.add(list.get(i+1));
            player3.add(list.get(i+2));
        }
        dipai.add(list.get(list.size()-1));
        dipai.add(list.get(list.size()-2));
        dipai.add(list.get(list.size()-3));

        //看牌
        lookPoker("player1",player1,map) ;
        lookPoker("player2",player2,map) ;
        lookPoker("player3",player3,map) ;
        lookPoker("底牌",dipai,map) ;

    }

    private static void lookPoker(String name,Set<Integer> list,Map<Integer, String> map) {
        System.out.print(name+"的牌:");
        for (Integer key : list){
            System.out.print(map.get(key)+" ");
        }
        System.out.println();
    }
}

10.System类

System类不能被实例化!!!

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():手动开启垃圾回收器,会去回收内存中没有更多引用的对象!
    

public class SystemDemo {
    public static void main(String[] args) throws IOException {
       // System.err.println("这是错误输出流");

        InputStream in = System.in ; //字节输出流
        //键盘录入的就可以用到Scanner     / BufferedRead 字符缓冲输入流:读数据
        Scanner sc = new Scanner(in) ; //里面传递参数InputStream参数

        System.out.println("helloworld");


        System.out.println("--------------------------------------");

        PrintStream ps = System.out ; //字节打印流
        //println(任何数据类型):PrintStream流中的一些工
        ps.println(10);
        ps.println(12.56);


        System.out.println("----------------------------------------");

        //创建Person对象
        Person p = new Person("高圆圆",41) ;
        System.out.println(p.getName()+"---"+p.getAge());
        p = null ;

        //手动垃圾回收器(不需要手动开启垃圾回收器,自动回收)!
        System.gc() ;


        //protected void finalize() throws Throwable:需要通过jvm启动垃圾回收器回收没有更多引用的对象


        /*int num = Runtime.getRuntime().availableProcessors();
         System.out.println(num);*/
       // Runtime.getRuntime().exec("notepad") ;
       // Runtime.getRuntime().exec("calc") ;


    }
}

11.Java异常

异常指程序出现错误或者警告异常信息,表明当前书写程序存在语法或逻辑错误!

java的异常类,Throwable:包含所有的错误以及异常!是所有异常类的超类(父类)

11.1.异常类别:

error:

非常严重问题 (跟代码没有太大有关系)OOM Out Of Memory:内存溢出 (严重问题)

例如在生活中 “地震了,不可抗力的因素”,表示计算机中一般由硬件条件所引起的异错误。

Exception:

编译时期异常和运行时期异常(RuntimeException):程序在运行过程中出现问题(代码书写不严谨)只要不是RuntimeException的子类都是属于编译时期异常。

编译时期异常: 在程序,运行前需要检查的! 在生活中 “长途旅行之前,检查你的车胎情况”

运行时期异常:在程序.程序代码逻辑问题(代码不严谨) 在生活中 "too young ,too simple "

11.2.异常的处理方式

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

变形格式:  try{
      			//可能出现问题的代码
           }catch(异常类名 变量名){
            //处理异常
             }
 
            try{
            //可能出现问题的代码
              }catch(异常类名 变量名){
                //处理异常1
               }catch(异常类名 变量名){
                //处理异常2
               }
 
         //多线程:jdk5以后:Lock:接口 (锁:可重复入的互斥锁)
               try{
                //可能出现问题的代码
                }finally{
                //释放资源(系统资源)
               }
             
           throws:抛出
public class ExceptionDemo1 {

    public static void main(String[] args) {

        //定义两个变量
      /*  int a = 10 ;
        int b = 0 ;
        System.out.println(a/b); //jvm在内存中会创建当前异常类的对象  new ArithmeticException()*/


      //使用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");
        }

    }
}

11.3.用try…catch去处理多个异常

jdk7以后提供捕获多个异常时的操作

try{
//可能出现问题的代码
}catch(异常类名1 | 异常类名2 | 异常类名3 …变量名){ //异常类名必须为同级别
//处理异常

}

public class ExceptionDemo2 {

    public static void main(String[] args) {
        //method1() ;
//        method2() ;
        //method3() ;
        method4() ;
    }
   
    private static void method4() {
        try {
            int a = 10;
            int b = 0;
            int[] arr = {4, 5, 6};
            System.out.println(a / b);
            System.out.println(arr[0]);
            System.out.println(arr[3]);
        }  catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {//(ArithmeticException算数异常 / ArrayIndexOutOfBoundsException数组索引越界异常)
                  System.out.println("程序出问题了...");
        }
    }

    //使用trt...catch...catch...catch...不能将大的异常放在最前面
    private static void method3() {
        try {
            int a = 10;
            int b = 1; //可能为0
            int[] arr = {4, 5, 6};
            arr = null; //空值
            System.out.println(a / b);
            System.out.println(arr[0]);
            System.out.println(arr[3]);
        }  catch (Exception e) {//包含了所有 编译时期/运行时期异常
            System.out.println("程序出问题了...");
        }
       /* }catch (ArrayIndexOutOfBoundsException e){
            System.out.println("访问了数组中不存在的索引");
        }catch(ArithmeticException e){// 通过jvm创建当前异常类对象,如果当前e类型匹配就会catch语句
            System.out.println("除数不能为0");
        }*/
    }

    //针对多个异常进统一处理,try...catch...catch
    private static void method2() {
        try{
            int a = 10;
            int b = 1 ; //可能为0
            int[] arr = {4,5,6} ;
            arr = null ; //空值
            System.out.println(a/b);
            System.out.println(arr[0]);
            System.out.println(arr[3]);

        }catch (ArrayIndexOutOfBoundsException e){
            System.out.println("访问了数组中不存在的索引");
        }catch(ArithmeticException e){// 通过jvm创建当前异常类对象,如果当前e类型匹配就会catch语句
            System.out.println("除数不能为0");
        }catch (Exception e){
            System.out.println("程序出问题了...");
        }

    }


    //分别针对可能出现问题的代码,进行try...catch(不推荐)
    private static void method1() {
        try{
            int a = 10;
            int b = 0 ;
            System.out.println(a/b);
        }catch (ArithmeticException e){
            System.out.println("除数不能为0");
        }

        //创建一个数组
        try{
            int[] arr = {4,5,6} ;
            System.out.println(arr[0]);
            System.out.println(arr[3]);
        }catch(ArrayIndexOutOfBoundsException e){
            System.out.println("访问了数组中不存在索引");
        }


    }
}

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

RuntimeException:运行时期异常

子类包括:NullPointException、ClassCastException、ArrayIndexOfOutofBoundsException…

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

编译时期异常:

调用者必须显示处理,不处理,编译通过不了,程序运行不了。 如果在当前方法中已经去捕获了try…catch…,调用者无序进行处理, 但是如果在方法中抛出异常的,调用者必须处理(捕获/抛出throws)。

建议:首先使用try…catch…其次在使用throws

11.5关于try…catch…和throws

1)有的时候没有办法去抛出,继承关系中,如果子类继承父类, 父类的该方法没有异常,子类重写该方法的时候,某段代码有异常情况,则只能try…catch

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

public class ExceptionDemo4 {
    public static void main(String[] args) {

    }
}

class Father{
    public void show(){}

    public void method() throws  ArrayIndexOutOfBoundsException{}
}
class Son extends  Father{
   /* @Override
    public void method() throws Exception { //要么跟他父类的方法异常类名一致,要么是异常类名的子类

    }*/
    @Override
    public void method() throws ArrayIndexOutOfBoundsException { //要么跟他父类的方法异常类名一致,要么是异常类名的子类

    }

    public void show()  {
        //将String--->Date
        try{
            String source = "2021-8-3" ;

            //创建SimpleDateFormat对象
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd") ;
            //解析
            Date date = sdf.parse(source) ;
            System.out.println(date);
        }catch (ParseException e){
            System.out.println("解析出问题了...");
        }

    }
}

11.6throws和throw的区别(面试)

共同点:都是抛出。
区别
用法不同:
1)使用位置不同
		throws: a)将异常抛出在方法声明上。
			    b)在方法名的后面可以跟多个异常类名,中间逗号隔开!
		throw:a)在方法的语句体中某个逻辑语句中
        	   b)它后面只能跟异常对象,而不是类名
2)调用者是否处理不同
		throws:调用者必须进行显示处理(try...catch/throws(继续抛出)),否则报错
		throw:调用者无须显示处理,一般情况都是在逻辑语句进行处理
3)出现异常是否肯定性
		throws:在方法上的,执行某个方法的代码中,可能有问题(表示出现异常的一种可能性)
		throw:执行某段代码一定会执行这个异常(表示出现异常的一种肯定性)
4)处理方式不同
		throws:将具体的处理交给jvm---通过jvm吧异常信息打印控制台上。显示的底层源码				而且会显示当前错误消息字符串
		throw:程序 中某段代码有问题:只是打印异常类名(jvm处理)
public class ExceptionDemo5 {
    public static void main(String[] args) throws ParseException {
//
//        try {
//            method();
//        } catch (ParseException e) {
//            System.out.println("解析问题了...");
//        }
        method();
      //  method2();
    }

    //throw
    private static void method2() throws ArithmeticException {
        int a = 10 ;
        int b = 0 ;

        //逻辑语句
        if(b!=0){
            System.out.println(a/b);
        }else{
            //抛出一个对象
            //throw 匿名对象 new XXXException() ;
            throw new ArithmeticException() ;

        }
    }

    //throws
    private static void method() throws ParseException {
        //将String--->Date
        String source = "2021-8-3" ;

        //创建SimpleDateFormat对象
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd ") ;
        //解析
        Date date = sdf.parse(source) ;
        System.out.println(date);
    }

}

11.7异常实现底层原理

不管使用throws/try…catch…finally:都要通过jvm调用Throwable类当中的功能完成错误信息的打印

 try{
   //可能出现问题的代码   //首先加载这些代码 (语法校验)
  }catch(异常类名 变量名){   
  //如果存在问题,那么jvm在内存中创建异常的实例,实例是否为catch语句中类型的实例
 //类似于 a instanceOf 引用类型:如果是就会执行catch语句
               异常处理                                
               //要么手动处理/要么通过jvm进行处理(打印错误日志信息)
 }finally{
        释放资源...
}
         Exception继承自Throwable
         public String getMessage()获取详细消息字符串   
         同义的方法:public String getLocalizedMessage()
         
         public String toString():打印出当前异常信息的简短描述:
                  格式: 内存中异常对象的类名:(全限定名称:包名.类名) 换行
 					    “:”(一个冒号和一个空格)  换行
          
         public String getMessage():消息字符串
         //public void printStackTrace():跟踪堆栈:包含toString以及底层原码(某行代码出现问题)以及本类中的代码问题,都是体现方法上(将当前跟方法相关的底层方法全部跟踪一遍)
 
 		 finally:不能单独使用,它是结合try...catch....finally:异常的标准格式
         finally用法特点:
                         释放相关的资源,代码一定执行的。除非在执行finally,jvm退						   出了!

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

finally会执行,目前这个代码,在catch语句已经形成返回路径,它会记录并在执行完finally当中的语句后按照记录的最终返回路径返回结果;finally是去释放资源用的。除非jvm退出(System.exit(0))。

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
    }
}

11.二分法查找

数组高级查找算法之二分搜索算法

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

手动书写或者通过Arrays的sort(任何数组进行二分法查找)

Arrays工具类:

​ 里面提供排序sort(任何数组进行排序) :元素升序排序 Integer,String

​ 里面提供二分搜素法binarySerach(任何类型进行查询 ,int key)

手动实现

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 ;

    }
}

12.线程和进程

线程是依赖于进程的

进程:
        能够调用的系统资源的独立单位!
        理解:计算机---->打开任务管理器---->客户端软件---->应用进程
             计算机开启启动 ---->服务进程(后台进程)

        现在的计算机----"多进程计算机"意义?
        主要为了提高CPU的使用率,

 在玩游戏的同时,还可以听音乐----->开启游戏的进程,音乐软件的进程 是同时的吗?
 不是同时,在一点点(时间片),在两个进程进行高效切换!


 线程 :
        属于程序中执行的最小单元(进程中的一条任务线)
        一个进程有多个线程组成,多个线程----->线程组(ThreadGroup)
        开启一个360软件---->360进程
                    杀毒的同时,还可以清理垃圾/安装系统/卸载软件...

        多线程的意义?
               多线程的特点:具有随机性
               多个线程在抢占CPU的执行权
                举例:
                     1v3 打篮球,只能3个人抢占篮球的几率大,并不一定这个3个人就一直						能够抢占篮球
                     有可能1个人抢占的篮球几率大(线程的执行具有随机性)


面试题:
jvm是多线程吗?
          是多线程:
               至少有两条线程
               用户线程main,以及创建对象的时候,当对象使用完毕,需要被垃圾回收器回收;
               jvm针对没有更多引用对象,开启一条垃圾回收线程!

java语言能够开启多线程?
      开启线程---->开启进程----Java语言不能够开启进程---借助于底层语言C语言开启进程
      封装成本地方法 ----->jdk提供类:Thread 里面封装好的方法
      开启线程:start()

线程有几种状态

六种:创建,就绪,运行,阻塞(等待/超时等待),死亡。

Thread类内部枚举类
public enum State{
NEW, 新建 (未执行)
RUNNABLE, 运行状态 (可能出现阻塞的情况)
BLOCKED, 线程阻塞状态(wait,sleep…)
WAITTING, 死死等待(底层调用wait()方法)
TIMED_WAITTING, 超时等待(直接调用wait(long timeout))
TERMINATED; 线程终止死亡状态(线程执行完毕)
}

12.1线程的方法和优先级

Thread类的构造方法:

Thread(String name…(其他参数)):创建线程类对象,设置名称

Thread类的成员方法

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

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

线程的优先级

Thread类中静态常量字段(成员变量field)

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的执行权越大

优先级小的:抢占CPU的执行权越小

默认优先级:随机性大一些

public class ThreadDemo2 {
    public static void main(String[] args) {//用户线程

        //创建MyThread2类的对象
        MyThread2 t1 = new MyThread2()  ;
        MyThread2 t2 = new MyThread2()  ;
        MyThread2 t3 = new MyThread2()  ;


        // public final void setName(String name):设置线程名称
        t1.setName("洪学佳") ;
        t2.setName("张俊杰") ;
        t3.setName("高圆圆");

        t1.setPriority(10); //最大优先级
        t2.setPriority(1);//最小优先级

        int num1 = t1.getPriority();
        int num2 = t2.getPriority();
        int num3 = t3.getPriority();
        System.out.println(num1+"---"+num2+"---"+num3);

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

12.2.守护线程、join线程、yield礼让线程

public final void setDaemon(boolean on)

参数为true,表示标记当前线程为守护线程,当正在运行的线程如果都是守护线程,则jvm自动退出这个方法必须在启动线程之前调用(start()之前)

守护线程会在其他线程运行完毕之后,随着jvm的退出而自动退出。

public final void join() throws InterruptedException:

等待该线程终止!简而言之,如果该线程出现,就像是vip一样,插队运行,只有当join线程运行完毕之后,其他线程才可以运行。但是需要在线程启动之后设置。

public static void yield():暂停当前正在执行的线程,礼让线程。

当有线程来的时候,当前线程如果是礼让线程,则就暂定当前的线程,二者继续发生抢占,有时候也会出现礼让不成功的情况(给你机会你也不中用啊!!!)

12.3线程的实现方式

方式1:

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

  1. 这个子类应该重写Thread类的run方法

3)然后可以分配并启动子类的实例。启动线程用的start()而不是run()

//线程1
public class MyThread extends Thread {
    //重写Thread类的方法

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

//线程2
public class MyThread2 extends Thread {
    //t1,t2
    @Override
    public void run() {
        for(int x = 0 ; x < 100 ; x ++){
            //获取线程名称
            System.out.println(this.getName()+":"+x);
        }
    }

//测试类
public class ThreadDemo {
    public static void main(String[] args) {

        //3)创建Thread类的子类对象
        MyThread my1 = new MyThread() ;//第一个线程对象
        MyThread my2 = new MyThread() ; //第二个线程对象

        //4)启动
        //my1.run();
        //my2.run();

     /*   my1.start();
        my1.start();
            my1只是代表一个线程对象
            my1将start方法调用两次---->IllegalThreadStateException:非法线程状态异常
            start()原码: 校验当前线程状态:如果线程已经启动了,就不能再启动
           */

     my1.start();//start():有jvm调用底层run方法,出现并发执行
     my2.start();

    }
}

方式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();
    }
}

12.4静态代理

特点:真实角色和代理角色必须实现同一个接口,真实角色:专注于自己的功能, 代理角色:完成对真实角色功能的"增强"。

例如: 结婚这个这个情况,真实角色:You 你自己,代理角色:WeddingCompany 婚庆公司,你只管在婚礼现场进行仪式即可,然后付款。结婚现场布置,灯光,酒席等婚庆公司完成即可。

12.5安全问题

线程运行过程中一般需要进行加锁,尤其是要进行修改共享数据的时候,不加锁则会造成严重安全问题,例如,售卖火车票时,火车票就是各个售卖窗口的共享资源,不加锁就会出现同票的情况。但是在读取数据的时候,共享数据是不变的,就不必进行加锁。

检验线程安全的标准

1)是否是多线程环境

2)是否存在共享数据

3)是否存在多条语句对共享数据的操作

解决方式:

Java提供同步机制:同步代码块 将多条对共享数据包裹起来

synchronized(锁对象){

​ 将多条对共享数据的操作包裹起来

}

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

//资源类
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--)+"张票");
                }
            }


            /**
             * 出现同票:  线程的原子性操作(++,--:最简单的操作)
             * 原子性:记录原始数据值,然后再对数据自增或者自减
             *  t1执行的操作,  窗口1正在出售第 23张票 ,当准备--的时候,t3将t1里面tickets--还没执行的时候,就已经打印了
             *                窗口3正在出售第23张票
             *
             *         t2进来之后,tickets-- 动作完成了  22张
             *          窗口2正在出售第22张票
             *
             *
             *   出现负票:  线程的执行具有随机性, ---加入延迟!
             *
             *          窗口1正在出售第1张票,延迟睡眠150秒 t3醒来之后
             *          窗口3正在出售第0张票,   --
             *          t2也在某一刻同时醒来并执行语句---- 窗口2正在出售第-1张票
             *
             */
        }

    }
}

//测试类
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();
    }
}

12.6面试题,wait()方法/notify()方法这些方法为什么不定义在Thread类中呢,而是定义Object类中呢?

它和锁对象有关系,而锁对象:可以任何的java类对象,因为是对象,所以定义在Object(顶层父类)

syncronized(锁对象){

​ 锁对象.wait()

​ 锁对象.notify()

}

s12.6.1leep和wait的区别

)调用方法是是否会释放锁
sleep(long mills)线程睡眠,调用等待睡眠时间到了,继续线程执行,不会去释放锁
wait的方法调用会立即释放锁对象,目的就是为了让锁对象调用notify()来唤醒其他线程…
2)来源不同
sleep方法来源于Thread类
属于线程的功能---->线程执行过程中可能产生阻塞 (睡眠一段时间内是阻塞的)
wait方法来源于Object类
它和锁对象有关系,线程处于等待过程中 使用锁对象.wait()

3)共同点:都会抛出一个异常:InterruptedException:中断异常

12.7同步方法

什么是同步方法? 如果一个方法的方法体的第一句话就是同步代码块。可以将synchronized关键字提取到方法声明上,跟在权限修饰符的后面。

权限修饰符 synchronized 返回值类型 方法名(形式列表){ //非静态的同步方法

业务逻辑…

}

12.8死锁问题

线程安全问题:可以通过同步方法或者是同步代码块去解决,但是执行过程中出现互相等待的情况就是死锁。

解决方案:

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

例如:卖包子和买包子,只有当商家生产出来包子,消费者才可以买

//生产包子
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);
            }

        }

    }
}
//共享资源		卖家和买家操作同一个东西
public class StuffBun {
    //成员变量不私有化
    String name ;//包子的类型(肉包子,菜包子)
    String bunType ;//大包子/小包子
}
//测试
public class StuffBun {
    //成员变量不私有化
    String name ;//包子的类型(肉包子,菜包子)
    String bunType ;//大包子/小包子
}
12.8.1信号灯法解决买卖包子
//买包子
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){
            stu.get();//获取包子数据
        }
    }
}
//生产包子
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){
			stu.set();//生产包子
      }
}
//共享资源
public class StuffBun {
    //成员变量不私有化
   private  String name ;//包子的类型(肉包子,菜包子)
   private String bunType ;//大包子/小包子

    //定义标记:表示是否存在包子数据
    private boolean flag ; //默认false,没有数据

    //提供给包子数据进行赋值的方法
    public synchronized void set(String name,String bunType){ //锁对象是this:非静态的同步方法
            //如果当前生产者没有语句,需要等待生成产生数据
            if(this.flag){
                //锁对象调用wait发那个发
                try {
                    this.wait();//释放锁对象...
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //赋值
            this.name = name ;
            this.bunType = bunType ;



            //如果现在有数据了
            //改变信号
            this.flag  = true ;//有数据类
            //通知(唤醒)消费者线程,赶紧使用数据
            this.notify(); //唤醒对方线程

    }

    //提供方法:获取包子的数据
    public synchronized void get(){ //非静态的同步方法:锁对象 this
            //如果当前消费资源类中存在包子数据,先等待消费使用完毕数据
            if(!this.flag){
                //等待使用完毕数据
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println(this.name+"---"+this.bunType);


            //改变信号值
            //如果包子消费完毕
            this.flag = false ;
            //唤醒对方线程(生产者资源类,别等了,产生数据)
            this.notify();
    }
}
//测试
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();

    }
}

13.locks锁类

JDK5以后提供java.util.current.locks.Lock :提供比syncrhonized方法(/同步代码块)更具体的锁定操作

Lock是一个接口

​ void lock()获取锁

​ void unlock() 试图释放锁

提供具体的子实现类: ReentrantLock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.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();
            }

        }
    }
}
//测试类
public class LockDemo {
    public static void main(String[] args) {
            //创建共享资源类对象
        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();
    }
}

14.线程组

线程组 ThreadGroup。线程组代表一组线程。 此外,线程组还可以包括其他线程组

Thread:----> public final ThreadGroup getThreadGroup() {//获取线程组

return group;

}

//给线程设置默认的线程组名称

//public Thread(ThreadGroup group, Runnable target) {

​ ThreadGroup:public final String getName() {:获取默认的线程组名称

​ return name;

}

构造方法;

public ThreadGroup(String name) {}

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

线程组就是一个任务队列(Queue),一个任务就是一个线程。

public class MyThread implements Runnable {
    @Override
    public void run() {
        for(int x = 0 ;x < 100 ;  x ++){
            System.out.println(Thread.currentThread().getName()+":"+x);
        }
    }
}

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

    }
}

15.线程池

import java.util.TreeMap;
import java.util.concurrent.Callable;
public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        for(int x = 0 ; x < 100 ; x++){
            System.out.println(Thread.currentThread().getName()+":"+x);
        }

        return null;
    }
}

public class MyRunnable implements  Runnable {
    @Override
    public void run() {
        for(int x = 0 ; x < 100 ; x ++){
            System.out.println(Thread.currentThread().getName()+":"+x);
        }
    }
}


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author Kuke
 * @date 2021/8/6
 * 线程池  属于 "池"化技术   ------>相似 数据库连接池(dbcp,c3po,druid(为监控而生))
 *
 *             特点:
 *                  在内存中创建一个固定可重用的线程数,当前线程执行完毕终止了,不会被回收掉,再次
 *  回到线程池中,等待下一次利用!
 *
 *              弊端: 维护成本大
 *
 *             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();

    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值