3/15day11_多线程_volatile关键字_原子性

2020-03-15  本文已影响0人  蹦蹦跶跶的起床啊

复习


1.throw和throws
    格式:
        throw new XxxException("异常描述信息");
        public static void 方法名()throws XxxException{
            
        }
    含义:
        throw new XxxException("异常描述信息"); 真的真的真的抛出了一个异常
        public static void 方法名()throws XxxException{}; 方法内部可能抛出异常(也可能不抛出)
    案例:
         public static void readFile(String name) throws FileNotFoundException { 
            //假设硬盘上有一个叫1.txt的文件
            if ("1.txt".equals(name)) {
                //可以读
                System.out.println("读取文件成功...");
            }else{
                //抛出异常
                throw new FileNotFoundException("没有找到你要的文件:"+name);
            }
        }
 2.运行时和编译时异常区别
     继承:
        编译时继承Exception
        运行时继承RuntimeException
     使用:
        当出现运行时异常时,我们不需要throws,也不需要trycatch
        当出现编译时异常时,我们必须throws或者trycatch
     案例:
         public static int getElement(int[] arr)throws ArrayIndexOutOfBoundsException{
            //自己判断,数组是否有3索引
            if (arr.length < 4) {
                //抛出异常
                throw new ArrayIndexOutOfBoundsException("哥们越界了!!!");
            }
            //获取数组中索引为3的元素
            int num = arr[3];
            //返回num
            return num;
        }

        调用方法:
        public static void main(String[] args) {
            int[] arr = {1,2,3};
            getElement(arr);
        }
3.在开发异常的使用
    a.如果没有遇到异常,该怎么写就怎么写代码
    b.如果遇到异常
        如果编译时异常,要么throws要么trycatch
        如果运行时异常,编译时不需要处理,运行后根据打印的异常信息,修改代码再次运行直到运行成功为止 

今日内容


多线程


并行和并发

进程和线程

Thread类[重点]

创建线程方式一_继承方式

public class MyThread  extends  Thread{
    public MyThread(String name) {
        super(name);
    }

    public MyThread() {
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(getName()+i);
        }
    }

}

public class DemoThread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("我叫子线程");
        myThread.start();
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

代码分析: 打印结果是 主线程和子线程交替出现, 是因为main方法为主线程, 和进行子线程时, 是并发进行的

在子线程中, 可以先获取当前对象,在调用该对象的getName()方法, 或者, 直接使用getName()方法也可以
Thread.currentThread().getname()此方法通用在主线程或者子线程

创建线程的方式二_实现方式

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
public class ThreadDemo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread tr = new Thread(mr,"我叫子线程");
        tr.start();
        for (int i = 0; i < 50; i++) {
//            System.out.println("主线程"+i);
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

两种创建线程方式优劣比

实现Runnable接口比继承Thread类所具有的优势:
1.适合多个相同的程序代码的线程去共享同一个资源。(实现类方式, 线程和任务是分开的, 而直接继承的话,就直接把任务给定义到继承类中了, 给写死了)
2.可以避免java中的单继承的局限性。
3.增加程序的健壮性,实现解耦操作(继承方式线程和任务是耦合的),代码可以被多个线程共享,代码和线程独立。
4.线程池只能放入Runable实现类或Callable类线程,不能直接放入继承Thread的类。

匿名内部类简化线程创建方式[重点]

public class TestDemo {
    public static void main(String[] args) {
        new Thread(){
           @Override
           public void run() {
               for (int i = 0; i < 50; i++) {
                   System.out.println(Thread.currentThread().getName()+"------"+i);
               }
           }
       }.start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println(Thread.currentThread().getName()+"------"+i);
                }
            }
        }).start();

        for (int i = 0; i < 50; i++) {
           System.out.println(Thread.currentThread().getName()+"------"+i);
        }
    }
}

高并发和线程安全


高并发及线程安全的介绍

多线程的运行机制[内存方面]

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("i = " + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        //1.创建两个线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
 
        //2.启动两个线程
        t1.start();
        t2.start();
    }
}

线程t1先读取a 的值为:0
t1被暂停
线程t2读取a的值为:0
t2将a = 1
t2将a写回主内存
t1将a = 1
t1将a写回主内存(将t2更改的1,又更改为1)
所以两次加1,但结果仍为1,少加了一次。
原因:两个线程访问同一个变量a的代码不具有"原子性". 原子性是指已经不能分隔的最小单位. (指一个操作可否完成)
原子性:在java语言原子性是指的一个不可以分割的操作,比如说 a = 0,这个就具有原子性,如果是a++, 这个操作其实是 a = a+1; 是可以分割的,所以他就不是一个原子性操作。
非原子操作具有线程安全问题,我们需要使用相关的手段,保证线程同步,

java的原子性和可见性区别

(1)原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。
(2)可见性volatile修饰词,可以应对多线程同时访问修改同一变量,由于相互的不可见性所带来的不可预期的结果。

volatile关键字


概念

volatile解决可见性

volatile解决有序性

当变量被修饰为volatile时,会禁止代码重排


volatile不能解决原子性

volatile小结

1.解决变量的可见性, 一旦变量发生改变, 所有使用该变量的线程都会取到最新值
2.解决变量的有序性, 编译时, 不会对有volatile修饰的成员变量进行重排
3.无法解决变量操作过程中的原子性

原子类

原子类概念

原子类可以保证对“变量”操作的:原子性、有序性、可见性。

AtomicInteger类

AtomicInteger类的工作原理-CAS机制

AtomicIntegerArray类示例

public class MyThread extends Thread {
    private static int[] intArray = new int[1000];//不直接使用数组
 
    @Override
    public void run() {
        for (int i = 0; i < arr.length(); i++) {
            intArray[i]++;
        }
    }
}

public class MyThread extends Thread {
    private static int[] intArray = new int[1000];//定义一个数组
    //改用原子类,使用数组构造
    public static AtomicIntegerArray arr = new AtomicIntegerArray(intArray);
    @Override
    public void run() {
        for (int i = 0; i < arr.length(); i++) {
            arr.addAndGet(i, 1);//将i位置上的元素 + 1
        }
    }
}
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new MyThread().start();
        }
        Thread.sleep(1000 * 5);//让所有线程执行完毕
 
        System.out.println("主线程休息5秒醒来");
        for (int i = 0; i < MyThread.arr.length(); i++) {
            System.out.println(MyThread.arr.get(i));
        }
    }
}

今日小结

"说出进程和线程的概念
    进程: 正在内存中运行的程序
    线程: 进程中完成某个功能的执行单元
"能够理解并发与并行的区别
   并行: 两个线程真的真的真的一起运行  
   并发: 两个线程看起来一起运行,实际上交替执行    
能够描述Java中多线程运行原理[CPU]
   线程调度  
"能够使用继承类的方式创建多线程【重点】
     a.子类 继承 Thread
     b.子类 重写 run
     c.创建 子类 对象
     d.调用 子类 对象 start
       
"能够使用实现接口的方式创建多线程【重点】
     a.实现类 实现 Runnable
     b.实现类 重写 run
     c.创建 实现类 对象
     d.创建 Thread对象 同时 传入 实现类 对象
     e.调用 Thread对象 start 方法  
"使用匿名内部类快速创建 继承方式和实现方式的线程 【重点】     
       
能够说出实现接口方式的好处
能够解释安全问题的出现的原因
      可见性 有序性 原子性 
能够说出volatile关键字的作用
       解决 可见性 有序性 不能解决 原子性
"能够掌握原子类AtomicInteger的使用【重点】  
       创建:
      AtomicInteger i = new AtomicInteger(10);
        使用:
        i.getAndIncrement(); //i++ 
        i.incrementAndGet(); //++i
能够理解原子类的工作机制
    因为原子类底层使用CAS机制(乐观锁机制,自旋机制)
上一篇 下一篇

猜你喜欢

热点阅读