A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 小石姐姐 于 2018-11-22 09:17 编辑



day05  异常 多线程

异常  异常的概念和体系
异常: 指的是程序在执行过程中, 出现的非正常的情况, 最终会导致JVM的非正常停止.
// 异常的体系结构

java.lang.Throwable  // 体系最顶层

        |_ Error         // 不应该试图捕获的严重问题, 不能处理的错误

        |_ Exception     // 可以处理的异常
   
            |_ RuntimeException   // 运行时异常

1. 错误(Error): 不能捕获处理的严重问题. 绝症
        必须将程序停下来, 修改代码才能解决
        错误的类名都是 "XxxError" 方式
2. 异常(Exception): 可以捕获处理的问题. 发烧感冒吃个药就好了
        程序执行起来后, 如果有合适的处理方式, 即使发生异常, 程序也能处理该异常并继续运行
        异常的类名都是 "XxxException" 方式
        1. 编译时异常:
                编译时期就会发生的异常, 必须在编译时期处理  Unhandled exception XxxException
        2. 运行时异常:
                编译时正常, 运行时才会发生的异常
[Java] 纯文本查看 复制代码
//throws ParseException   将异常抛出
    public static void main(String[] args) /*throws ParseException*/ {
        SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd");
        Date parse = null;
        try {
            parse = sdf.parse("1999-09-09");  //ParseException  解析异常
        } catch (ParseException e) {
            e.printStackTrace();
        }
        System.out.println(parse);
        System.out.println("后续代码");
        int arr[]={1,2,3};
        try {
            System.out.println(arr[3]);
        }catch (Exception e){
            System.out.println(e);
        }
          //ArrayIndexOutOfBoundsException索引越界异常
        int arr1[]=new int[1024*1024*1024];  //内存溢出OutOfMemoryError
        System.out.println(arr1);
    }

异常的产生过程解析
异常产生的过程:
  • 当执行的代码发生异常操作时, JVM会创建一个对应的异常类对象, 包含异常的内容, 原因, 位置ew ArrayIndexOutOfBoundsException(), new NullPointerException()
  • 如果执行代码的方法没有对异常进行 try...catch 处理, 则会向该方法调用处的方法抛(向上层抛). 如果所有方法(包括main()方法)都没有 try...catch 处理异常, 则该异常会被JVM按照默认的处理方式处理
  • JVM对于异常的默认处理方式是: 将异常信息(内容, 原因, 位置)打印到控制台
  • 终止当前的程序

[Java] 纯文本查看 复制代码
private static int divide(int a,int b) {
        if (b==0){
            throw new RuntimeException("不能除零");
            //java.lang.RuntimeException: 不能除零
        }
        return a/b;
​
    }


异常关键字: throw制造异常
throw关键字作用:        在方法中抛出指定的异常对象        格式:        throw new 异常类名("异常原因字符串");

// 示例:
   
[Java] 纯文本查看 复制代码
 public static void method(int index) {
        if (index < 0) {
            throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");
        }
    }

注意:
    1. throw 必须写在方法的内部

    2. throw 后面new的异常对象, 必须是 "Exception" 或 "Excetion的子类" 的对象

    3. 一个方法内部 throw 了一个异常对象, 则该方法可以分为2种情况来处理该异常:
          throw 的是"运行时异常"(RuntimeException及其子类)对象, 那么可以不处理
               该异常最终会交给JVM处理, 结果就是: 打印异常信息到控制台, 并立刻结束程序
                          如果 throw 的是"编译时异常"(Exception及其子类), 则必须处理:
               处理方式1: throws 抛出
               处理方式2: try...catch 捕获

          ```java
        
[Java] 纯文本查看 复制代码
  private static int demo03(int arr[], int index) {
                  if (arr == null) {      //int[] arr=null;
                      throw new NullPointerException("传递数组值为空");
                  }
              //ArrayIndexOutOfBoundsException: 传输的索引超出范围
                  if (index>arr.length-1||index<0){     //index<0 ,index>length-1
                  throw  new ArrayIndexOutOfBoundsException("传输的索引超出范围");
              }
              int d = arr[index];
                  return d;
          }

      ##### **Objects工具类方法: requireNonNull()非空判断**

      java.util.Objects: 操作各种对象相关的工具类
              // 静态方法
              static <T> T requireNonNull(T obj): 检查传入参数是不是null. 是null则抛异常; 非null则返回该对象
              static <T> T requireNonNull(T obj, String message): 检查传入参数是不是null. 是null则抛异常, 同时带有原因; 非null则返回该对象

      ```java
   
[Java] 纯文本查看 复制代码
   public static void main(String[] args) {
              method(null);
          }
      //java.lang.NullPointerException: 传递的对象是null
          public static void method(Object obj) {
      //        if (obj == null) {
      //            throw new NullPointerException("传递的对象是null");
      //
      //        }
              Objects.requireNonNull(obj);//检查传入参数是不是null.
              Objects.requireNonNull(obj,"传递的对象是空");
​
          }

```java

##### **异常的处理方式1: throws声明抛出异常**

异常处理的第一种方式:
        throws, 声明抛出 (方法自己不处理, 交给方法调用者处理, "甩锅给别人")
        作用: 告诉方法的调用者, 调用此方法有可能发生异常, 需要在编写代码时处理

格式:
        修饰符 返回值类型 方法名() throws 异常类名1, 异常类名2, ... {



##### 注意:

        1. throws 必须写在方法声明上
        2. throws 后面的异常类名, 一般是 Exception 或 Exception的子类
                (RuntimeException及其子类也行, 但是没有什么意义)
        3. 方法内部如果抛出了多个异常对象, throws 后也必须声明多个异常
                如果抛出的异常对象有子父类关系, 那么直接声明父类异常即可
        4. 调用了一个带有 throws 声明抛出异常的方法, 就必须处理该异常:
                要么继续声明 throws 抛出
                                要么 try...catch 捕获处理异常

```java
[Java] 纯文本查看 复制代码
public static void main(String[] args) throws IOException,FileNotFoundException{
    //public static void main(String[] args) throws IOException,FileNotFoundException{
        //Exception>IOexception>FileNotFoundException
        readFile("c.txt");
        readFiles(".tx");
    }
        //FileNotFoundException 编译异常 FileNotFoundException: 路径不对
    private static void readFile(String filename) throws FileNotFoundException {
            if (!"a.txt".equals(filename)){
                throw  new FileNotFoundException("路径不对");
            }
        System.out.println("读取文件");
​
    }
    //IOException: 文件的后缀名不对
    private static void readFiles(String filename) throws IOException {
        if (!".txt".endsWith(filename)) {
            throw new IOException("文件的后缀名不对");
        }
    }
}
        ///为啥两个只抛出了一个错    当抛出异常以后 它会交给JVm处理 所以后面的代码不在运行 异常处理方式2: 捕获异常
try...catch:         捕获并处理异常 (方法内部自己处理异常, 不交给别人, "自己背锅");

try {
        // 可能产生异常的代码
    } catch (异常类名 变量名) {
        // 处理异常的代码
        // 一般会将异常信息存储到日志中
    }
        ...
        } catch (异常类名 变量名) {
        // 处理异常的代码
        // 一般会将异常信息存储到日志中
    }

注意:
    1. try 中可能会抛出多种异常, 就可以写多个 catch 分别处理每种异常

    2. 如果 try 中产生了异常, 就会从产生异常的那一行代码直接跳转到对应的 catch 中执行处理代码, 然后继续执行 try...catch 之后的其他代码; 如果 try 中没有产生异常, 那就不会执行 catch , 执行完 try 中的代码后, 继续执行 try...catch 之后的其他代码.

       ## 问题
[Java] 纯文本查看 复制代码
import java.io.FileNotFoundException;
import java.io.IOException;
​
public class d6 {
    public static void main(String[] args) throws Exception, FileNotFoundException {
        try{ readFile("c.txt","a.txt");
​
        }catch (FileNotFoundException    e){
            System.out.println("传递的文件不对");
        }catch (IOException o){
            System.out.println("后缀不对");
        }
        System.out.println("后续代码");
​
    }
​
    private static void readFile(String filename,String finame) throws FileNotFoundException,IOException {
        if (!"a.txt".equals(filename)) {
            throw new FileNotFoundException("路径不对");
​
        }
        if (!".txt".endsWith(finame)) {
            throw new IOException("文件的后缀名不对");
        }
​
​
​
    }
}
​import java.io.FileNotFoundException;
import java.io.IOException;
​
​




[Java] 纯文本查看 复制代码
public class d6 {
    public static void main(String[] args) throws Exception, FileNotFoundException {
        try{readFile("c.txt");
            readFiles("a.tet");
​
        }catch (FileNotFoundException    e){
            System.out.println("传递的文件不对");
        }catch (IOException o){
            System.out.println("后缀不对");
        }
        System.out.println("后续代码");
​
    }
​
    private static void readFile(String filename) throws FileNotFoundException {
        if (!"a.txt".equals(filename)) {
            throw new FileNotFoundException("路径不对");
​
        }
​
    }
    private static void readFiles(String finame) throws IOException{
        if (!".txt".endsWith(finame)) {
            throw new IOException("文件的后缀名不对");
        }
    }
}
​

Throwable中的3个异常处理方法
java.lang.Throwable        // 成员方法        String getMessage(): 异常的信息. 没有原因返回null        String toString(): 异常的类型和原因信息        void printStackTrace(): 使用标准错误输出流打印异常信息

[Java] 纯文本查看 复制代码
public static void main(String[] args) throws  FileNotFoundException {
        try{readFile("c.txt");
        }catch (FileNotFoundException    e){
            System.out.println("传递的文件不对");      //1
            System.out.println(e.getMessage()); //路径不对123
            System.out.println(e.toString());  //重写OBject类的toString方法   //3   //e:same
            e.printStackTrace();//java.io.FileNotFoundException: 路径不对123
        }
​
    }
    private static void readFile(String filename) throws FileNotFoundException {
        if (!"a.txt".equals(filename)) {
            throw new FileNotFoundException("路径不对123");   //2
        }
    }finally代码块
try {
       // 可能发生异常的代码
   } catch(异常类型 异常变量名) {
       // 处理异常
   }
       ...
       catch(异常类型 异常变量名) {
       // 处理异常
   } finally {
       // 无论是否发生异常, 是否捕获, 最后都会执行的代码.
       // 通常在这里执行释放资源的操作
   }

注意:
    1. finally 必须和 try...catch 一起使用
    2. finally 一般用于释放资源 (IO流时用到)

   
[Java] 纯文本查看 复制代码
public static void main(String[] args) {
        try {
            readFile("c.txt");
        } catch (FileNotFoundException e) {
            e.printStackTrace();   //java.io.FileNotFoundException: 路径不对
        } finally {
            //无论是否出现异常都会执行
            System.out.println("资源施放");
        }
    }
​
    private static void readFile(String filename) throws FileNotFoundException {
        if (!"a.txt".equals(filename)) {
            throw new FileNotFoundException("路径不对");
        }
        System.out.println("读取文件");
 }
异常注意事项1: 捕获多个异常的3种方式
捕获多个异常:

    1. 多个异常分别 try...catch 处理
    2. 一个 try 多个 catch
            如果异常存在继承关系, 子类异常在上, 父类异常在下
    3. 多个异常, 一次捕获一次处理
            用Exception多态捕获
// 1. 多个异常分别try...catch处理
    try {
            // 可能发生异常的代码
    } catch (异常类型 变量名) {
            // 处理异常
    }
     
    try {
            // 可能发生异常的代码
    } catch (异常类型 变量名) {
            // 处理异常
    }
     
        // 2. 一个try多个catch
        try {
        // 可能发生异常的代码
        // 可能发生异常的代码
        // 可能发生异常的代码
    }catch (异常类型1 变量名){  // 子类异常在上
        // 处理异常1
    }catch (异常类型2 变量名){  // 父类异常在下
        // 处理异常2
    }

        // 3.
        try {
                // 可能发生异常的代码
        // 可能发生异常的代码
        // 可能发生异常的代码
        // 可能发生异常的代码
        // 可能发生异常的代码
    }catch (Exception e){  // 父类异常多态接收
        // 处理异常
    }
[Java] 纯文本查看 复制代码
public static void main(String[] args) {
     try {
           int[] arr = {1, 2, 3};
           System.out.println(arr[1]);
          List<Integer> list = List.of(1, 2, 3);
           System.out.println(list.get(1));
       }catch ( ArrayIndexOutOfBoundsException e){
            System.out.println(e);  //java.lang.ArrayIndexOutOfBoundsException: 3
        }catch (IndexOutOfBoundsException e){
                System.out.println(e);
     //java.lang.IndexOutOfBoundsException: Index 3 out-of-bounds for length 3
        }
        System.out.println("后续代码");    }
​
        try {
            int[] arr = {1, 2, 3};
            System.out.println(arr[1]);
            List<Integer> list = List.of(1, 2, 3);
            System.out.println(list.get(1));
        }catch ( /*ArrayIndexOutOfBounds*/Exception e){    //父类异常对象
            System.out.println(e);         //发生异常以后的代码不会运行
        }
​
        System.out.println("后续代码");
    }

运行时异常, 可以不抛出 throws 也不捕获 try catch, 交给JVM处理
// 因为可以通过某些代码来避免发生运行时异常
        // 通过if判断, 避免可能发生的ArrayIndexOutOfBoundsException
    if (i >= 0 || i <= arr.length-1) {
        int a = arr;
    }
        // 通过if判断, 避免可能发生的NullPointerException
    if (phone != null) {
        phone.call();
    }异常注意事项2: finally中有return语句
如果 finally 代码块中有 return 语句, 则永远返回 finally 中的 return 语句的值应该避免在 finally 中写 return 语句

[Java] 纯文本查看 复制代码
get a(100);
int a=10;
try {
    return a;
} catch(Exception e) {
    System.out.println(e)
} finally {
        a =100;
    return a;

}异常注意事项3: 子父类继承重写方法时的异常要求
子父类继承关系下, 子类重写父类带有throws的方法:

    1. 如果父类抛出多个异常, 子类重写父类方法时可以有3种方式:
            a: 抛出和父类相同的异常
                            b: 抛出父类异常的子类
                            c: 不抛出异常
            2. 父类方法没有抛出异常, 子类重写父类该方法时也不可抛出异常
               类产生该异常, 只能捕获处理, 不能声明抛出
            3. 一般情况下:
                   父类方法声明的异常是什么样的, 子类重写的方法声明异常就什么样, 保持一致即可
[Java] 纯文本查看 复制代码
public class Fu {
    public void method() {
        // ...
    }
}

public class Zi extends Fu {
    @Override
    public void method() {
        // ...
    }
}

自定义异常类
如果Java提供的异常类不足以满足我们的需求, 我们也可以自己定义异常类        定义编译时异常: 继承 Exception        定义运行时异常: 继承 RuntimeException

[Java] 纯文本查看 复制代码
public class d10 extends Exception {
    public  d10(){
​
    }
    public d10(String s) {
        super(s);
    }
}

定义测试类:
​        1.定义静态成员变量:                private static String[] usernames = {"张三", "李四", "王五"};                 数组保存已经注册过的用户名(模拟数据库)        2.使用Scanner获取用户输入的注册的用户名(模拟前端,页面)        3.定义一个方法                public static void checkUsername(String username) {}                        对用户输入的中注册的用户名进行判断            遍历存储已经注册过用户名的数组,获取每一个用户名            使用获取到的用户名和用户输入的用户名比较是否相同                true: 用户名已经存在,抛出RegisterException异常,告知用户"亲,该用户名已经被注册";                false: 继续遍历比较            如果循环结束了,还没有找到重复的用户名,提示用户"恭喜您,注册成功!";

            4. 调用checkUsername()方法将输入的用户名传入, 查看效果
import java.util.Scanner;   


[Java] 纯文本查看 复制代码
public  class name {
        static String names[]={"张三","李四","王五"};
    public static void main(String[] args) throws Exception {
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入姓名:");
        String user = sc.next();
        checkUser(user);
    }
    public  static  void checkUser(String user) throws Exception {
        for (String name : names) {
            if (name.equals(user)){
                throw  new Exception("亲,该用户名已经被注册!");
​
            }return;
        }
        System.out.println("恭喜你注册成功");
    }
[/i][i]}

多线程计算机基本概念: 并发与并行
并发: (交替执行) 指两个或多个事件在"同一时间段内"发生并行: (同时执行) 指两个或多个事件在"同一时刻"发生 (同时发生)
我们研究的是"并发"
并发的实现, 是依靠电脑CPU快速地在多个任务之间切换执行实现的
计算机基本概念: 进程
进程: 一个应用程序在内存中的一次执行过程
​        每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程        进程也是程序的一次执行过程,是系统运行程序的基本单位        系统运行一个程序即是一个进程从创建、运行到消亡的过程
计算机基本概念: 线程
线程: 是进程内的一个独立执行单元 (一条代码执行路径)        一个程序运行后至少有一个进程, 一个进程中可以包含多个线程        ​        公司 > 部门 > 员工        程序 > 进程 > 线程
多线程的好处:        效率高        多个线程之间互不影响
线程的调度
线程的调度方式:

    1. 分时调度: 所有线程轮流使用CPU, 平分占用CPU的时间
    2. 抢占式调度: 优先让优先级高的线程使用CPU; 如果优先级相同, 则随机选择一个线程执行
​        1-10        线程1 10 可能性更大         线程2 9        线程3        Java使用的是"抢占式"调度
主线程
Exception in thread "main"主线程:        我们以前编写的代码, 也在一条线程中执行, 该线程叫作"main线程", 也称为"主线程"如果我们没有额外创建线程, 那么我们的程序就只有一个线程, 即主线程, 此时程序是"单线程"的单线程的执行特点:        同一个线程内的代码, 从上往下依次执行
创建多线程程序的第一种方式: 继承Thread类
实现多线程的第一种方式:

    1. 定义类, 继承 Thread 类
    2. 重写 run() 方法, run方法内部是线程要执行的任务
    3. 创建Thread子类的对象, 调用 start() 方法启动线程
java.lang.Thread类: 表示线程. 实现了Runnable接口    void start(): 启动线程, 即让线程开始执行run()方法中的代码
注意:        必须调用 start() 方法来开启线程, 不能直接调用 run() 方法, 调用 run() 会变成单线程        同一个线程对象, 不能多次调用 start() 方法        Java是抢占式调度, 不同线程的代码, 执行顺序是随机的
今日API

java.lang.Throwable: 异常的顶级

        // 成员方法

        String getMessage(): 异常的信息. 没有原因返回null

        String toString(): 异常的类型和原因信息

        void printStackTrace(): 使用标准错误输出流打印异常信息

        

java.lang.Thread类: 表示线程. 实现了Runnable接口

    void start(): 启动线程, 即让线程开始执行run()方法中的代码
【石家庄校区】就业班day06-线程,同步
线程多线程原理1: 线程执行的随机性
CPU执行哪个线程是随机的, 不能人为干预Java线程调度是抢占式的, 多个线程互相抢夺CPU的执行权补充:第一种方式: 继承Thread类        定义类继承Thread        重写run()方法, 要执行的任务        创建子类的对象, 调用start()方法启动线程
Thread常用方法: getName(), currentThread()java.lang.Thread类: 表示线程. 实现了Runnable接口
        // 构造方法
    Thread(): 创建Thead对象
        MyTread myTread = new MyTread();
    Thread(String threadName): 创建Thead对象并指定线程名
        
    Thread(Runnable target): 通过Runnable对象创建Thread对象
    Thread(Runnable target, String threadName): 通过Runnable对象创建对象并指定线程名
        // 成员方法
    void run(): 用于让子类重写, 表示该线程要执行的任务.不能直接调用
    void start(): 启动线程, 即让线程开始执行run()方法中的代码
    String getName(): 获取线程的名称
    void setName(String name): 设置线程名称
        myTread.setName("小王八");
        myTread.start();
        // 静态方法
    static Thread currentThread(): 返回对当前正在执行的线程对象的引用
        System.out.println(Thread.currentThread().getName());

    static void sleep(long millis): 让所在线程睡眠指定的毫秒

Thread常用方法: setName(), Thread(String name)
java.lang.Thread类: 表示线程. 实现了Runnable接口
        // 构造方法
    Thread(String threadName): 创建Thead对象并指定线程名
    // 成员方法
    void setName(String name): 设置线程名称
    Thread常用方法: sleep()
java.lang.Thread类: 表示线程. 实现了Runnable接口
        // 静态方法
    static void sleep(long millis): 让所在线程睡眠指定的"毫秒"
创建多线程程序的方式2: 实现Runnable接口
创建线程的第2种方式:

    1. 定义类, 实现Runnable接口
    2. 重写 run() 方法
    3. 创建Runnable实现类对象
    4. 创建Thread类对象, 在构造方法中传入Runnable实现类对象
    5. 通过Thread对象调用start()方法启动线程
java.lang.Thread类: 表示线程. 实现了Runnable接口
        // 构造方法
    Thread Thread(Runnable target): 通过Runnable对象创建Thread对象
    Thread Thread(Runnable target, String threadName): 通过Runnable对象创建对象并指定线程名
   
    Thread和Runnable的区别
实现Runnable的好处:                    //实现了runnable接口,还可以继承其他的类,实现其他的接口

  1. 避免单继承的局限性        //类继承了thread类就不能继承其他的类

  2. 增强了程序的扩展性, 降低了程序的耦合性(解耦)         

  3. 线程是Thread, 任务是Runnable实现类对象. 相当于将线程和任务分离

     ##### **匿名内部类方式创建线程**
5分钟练习: 匿名内部类创建线程需求:
[Java] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) {
        // 匿名内部类方式
        new Thread(new Runnable(){
            @Override
            public void run() {
                // 要执行的任务
            }
        }).start();
    }
}

分别使用Thread子类匿名内部类方式, 和Runnable实现类匿名内部类 2种方式, 创建线程对象        线程对象内部循环100次, 打印线程名字和次数
[Java] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) {
        // 继承Thread类, 使用匿名内部类简化
        new Thread("继承Thread类") {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }.start();

        // 实现Runnable接口, 使用匿名内部类简化
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        };
        new Thread(r, "可以设置名字").start();

        // 更简单的方式
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }, "实现Runnable接口").start();
    }
}

线程安全问题的原因
问题发生场景:         多个线程操作共享资源问题发生原因:         JVM是抢占式调度, CPU在每个线程之间切换是随机的, 代码执行到什么位置是不确定的        在操作共享资源时, 由于一个线程还没有执行完, 另一个线程就来操作, 就会出现问题如何解决:         在操作共享资源时, 让线程一个一个来执行, 不要并发操作共享变量, 就可以解决问题
解决线程安全问题方式1: 同步代码块
解决多线程操作共享数据的安全问题的3种方式:

    1. 同步代码块
    2. 同步方法
    3. 锁机制
同步代码块: 使用 synchronized 关键字修饰的代码块, 并传入一个当作锁的对象
格式:
    synchronized (锁对象) {
                // 操作共享数据的代码

    }注意:
​        锁对象可以是任意类型的一个对象        锁对象必须是被多个线程共享的唯一对象        锁对象的作用: 只让一个线程在同步代码块中执行
补充:// 同步代码块就类似于房间和房门
房间+------+|       |       线程1 |       \ 门锁  线程2|       |       线程3 +------+
线程进入房间后, 锁上房间, 其他线程就进不来+------+|          || 线程1|   线程2 |          |   线程3+------+
线程1做完事情, 打开锁出了门, 其他线程就可以抢着进来+------+|       ||       \线程2 |       |       线程3+------+线程1
[AppleScript] 纯文本查看 复制代码
public class RunnableImpl implements Runnable {

    // 定义成员变量, 作为共享的票数
    private int ticket = 100;

    // 定义多个线程共享的同一个锁对象
//    Object lock = new Object();
    String lock = new String();

    @Override
    public void run() {
        // 循环卖票
        while (true) {

            // 将操作共享数据的代码用同步代码块套住
            synchronized (lock) {
                // 当票数大于0时卖票
                if (ticket > 0) {
                    // 打印当前卖出的票
                    System.out.println(Thread.currentThread().getName()+"卖出第"+ticket+"张票");
                    // 票数--
                    ticket--;
                }
            }
        }                       //代码块
    }

同步技术解决线程安全问题的原理
锁对象, 也称为"同步锁", "对象监视器"
同步的原理:    线程进入同步代码块前, 会争夺锁对象, 只有一个线程会抢到锁对象    进入同步代码块的线程, 会持有锁对象, 并执行同步代码块中的代码    此时同步代码块外的线程, 处于阻塞状态, 只能等待    当同步代码块内的线程执行完代码块, 会离开同步代码块, 并归还锁对象给同步代码块    等在同步代码块外的其他线程就可以继续争夺锁对象
解决线程安全问题方式2: 同步方法
同步方法: 使用 synchronized 关键字修饰的方法, 具有默认的锁对象
非静态同步方法的锁对象: this
​     
[Java] 纯文本查看 复制代码
   // 非静态同步方法        public synchronized void method(){                // 可能会产生线程安全问题的代码         }

public class RunnableImpl implements Runnable {

    // 定义成员变量, 作为共享的票数
    private int ticket = 100;

    @Override
    public void run() {
        // 循环卖票
        while (true) {
            // 调用同步方法
            sellTicket();
        }
    }

    // 同步方法: 卖票                                 //调用方法
    // 所有操作共享变量的代码, 都要放在同步方法中
    public synchronized void sellTicket() {
        // 当票数大于0时卖票
        if (ticket > 0) {
            // 打印当前卖出的票
            System.out.println(Thread.currentThread().getName()+"卖出第"+ticket+"张票");
            // 票数--
            ticket--;
        }
    }
}


[Java] 纯文本查看 复制代码
public class Test {                                 //
    public static void main(String[] args) {
        // 创建卖票任务
        RunnableImpl run = new RunnableImpl();

        // 创建3个线程作为窗口, 传入同一个任务
        Thread t0 = new Thread(run, "窗口1");
        Thread t1 = new Thread(run, "窗口2");
        Thread t2 = new Thread(run, "窗口3");

        // 开始卖票
        t0.start();
        t1.start();
        t2.start();
    }
}
静态同步方法静态同步方法:        public static synchronized void method(){                // 可能会产生线程安全问题的代码         }
静态同步方法的锁对象: 当前类的字节码对象
获取一个类的字节码对象的3种方式:        1. 对象.getClass()        2. 类名.class        3. Class.forName("类的全路径");

解决线程安全问题方式3: Lock锁java.util.concurrent.locks.Lock接口: JDK 5 新增的Lock接口        // 成员方法        void lock(): 获取锁        void unlock(): 释放锁        java.util.concurrent.locks.ReentrantLock类: Lock的实现类
[Java] 纯文本查看 复制代码
public class RunnableImpl implements Runnable {
    // 成员变量创建锁对象, 该锁对象也要所有线程共享唯一一个
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        // 加锁
        lock.lock();
        try {
            // 操作共享变量的代码...
        } finally {
            // 在finally中保证释放锁
            lock.unlock();  
        }
    }
}

线程安全和效率的特点:
​        线程安全, 效率低        线程不安全, 效率高
锁对象, 也称为"同步锁", "对象锁", "对象监视器"
同步的原理:    线程进入同步代码块前, 会"争夺锁对象", "只有一个线程"会抢到锁对象    进入同步代码块的线程, 会"持有锁对象", 并执行同步代码块中的代码    此时同步代码块外的线程, 处于"阻塞"状态, 只能等待    当同步代码块内的线程执行完代码块, 会离开同步代码块, 并"归还锁对象"给同步代码块    等在同步代码块外的其他线程就可以继续争夺锁对象
java.util.concurrent.locks.Lock接口: JDK 5 新增的Lock接口        // 成员方法        void lock(): 获取锁        void unlock(): 释放锁        java.util.concurrent.locks.ReentrantLock类: Lock的实现类
使用方式:
public class RunnableImpl implements Runnable {    // 成员变量创建锁对象, 该锁对象也要所有线程共享唯一一个    Lock lock = new ReentrantLock();  // 成员变量
[Java] 纯文本查看 复制代码
@Override
public void run() {
    // 加锁
    lock.lock();
    try {
        // 操作共享变量的代码...
    } finally {
        // 在finally中保证释放锁
        lock.unlock();  
    }
}

线程的生命周期中, 可以出现有6种状态:线程的生命周期中, 可以出现有6种状态:
​    1. "NEW 新建"       线程被创建, 但没有调用 start() 启动    2. "RUNNABLE 可运行"       调用 start()方法后已启动, 但xi可能正在执行 run() 方法的代码, 也可能正在等待CPU的调度    3. "BLOCKED (锁)阻塞"       线程试图获取锁, 但此时锁被其他线程持有    4. "WAITING 无限等待"       通过锁对象调用无参的 wait() 进入此状态.        等待其他线程通过锁对象执行 notify() 或 notifyAll() 才能结束这个状态    5. "TIMED_WAITING 计时等待"       如通过锁对象调用有参的 wait(long millis) 或 sleep(long millis), 则进入此状态.        直到时间结束之前被其他线程通过锁对象执行 notify()或 notifyAll()唤醒, 或时间结束自动唤醒    6. "TERMINATED 终止"       run()方法结束(执行结束, 或内部出现异常), 则进入此状态
今日API



java.lang.Thread类: 表示线程. 实现了Runnable接口
    // 构造方法
    Thread Thread(): 创建Thead对象
    Thread Thread(String threadName): 创建Thead对象并指定线程名
    Thread Thread(Runnable target): 通过Runnable对象创建Thread对象
    Thread Thread(Runnable target, String threadName): 通过Runnable对象创建对象并指定线程名
    // 成员方法
    void run(): 用于让子类重写, 表示该线程要执行的任务.不能直接调用
    void start(): 启动线程, 即让线程开始执行run()方法中的代码
    String getName(): 获取线程的名称
    void setName(String name): 设置线程名称
    // 静态方法
    static Thread currentThread(): 返回对当前正在执行的线程对象的引用
    static void sleep(long millis): 让所在线程睡眠指定的毫秒
   
java.lang.Object类:
    // 成员方法 (只能通过"锁对象"调用)
    void notify(): 随机唤醒在同一个锁对象上的某一个处于等待状态的线程
    void notifyAll(): 唤醒所有在同一个锁对象上处于等待状态的线程
    void wait(): 让当前线程处于无限等待状态
    void wait(long timeout): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态
    void wait(long timeout, int nanos): 让当前线程处于计时等待状态, 时间到或被唤醒后结束此状态



0 个回复

您需要登录后才可以回帖 登录 | 加入黑马