Java=多线程-高并发和线程安全,volatile,原子类

一,多线程

并行与并发

并行: 两个事件,在同一个时刻,都在发生
并发: 两个事件,在同一个时间段内,都在发生(交替执行)

进程与线程

进程: 正在内存中运行的程序,我们称为进程
线程: 进程中完成某个小功能的模块(进程中用执行某个功能的执行单元)

线程是属于某个进程的
      每个进程都有独立的内存空间(独立的栈独立的堆等),并且至少有一个线程
      每个线程都会跟进程申请一块独立栈,共享进程的堆 

线程调用是指CPU在不同的进程不同的线程之间进行快速切换
    
线程调度的分类:
    分时调度: 每个线程平均拥有CPU的执行权
    抢占式调用: 每个线程随机分配CPU的执行权(具体的分配多少和线程优先级有关)
    我们Java程序(Java进程)中所有线程采用抢占式调度 

线程的状态

Thread对象共有6种状态:NEW(新建),RUNNABLE(运行),BLOCKED(阻塞),WAITING(等待),TIMED_WAITING(有时间的等待),TERMINATED(终止);状态转换如下:

扫描二维码关注公众号,回复: 9839698 查看本文章

1.Thread类

a.Thread类是什么?
    Thread是Java定义好的,代表线程的类,只要创建该类的一个对象,其实就是创建了一个线程
   
b.Thread类的构造方法
    public Thread(); // 无参构造,线程会有默认的名字,Thread-0,Thread-1等...
	public Thread(String name); //带有线程名字的构造

	public Thread(Runnable r);//带有线程任务的构造
	public Thread(Runnable r,String name); //即带有线程名字,又带有线程任务的构造
    
c.Thread类的成员方法
    public String getName(); //获取线程的名字
	public void setName(String name);//修改线程的名字

	public void run();//代表线程要执行的任务,任务有关的代码需要写在次方法中
	public void start();//线程只创建并不会执行,必须调用start开启后才会执行任务

	public static void sleep(long millis); //让当前线程"休眠/暂停"多少毫秒
			这里的当前线程只指 Thread.sleep(1000)这句代码写哪个线程中,哪个线程就是当前线程
	public static Thread currentThread();//获取当前线程对象
			这里的当前线程是指 Thread.currentThread() 这句代码写哪个线程中,哪个线程就是当前线程


线程执行有优先级,优先级越高先执行机会越大(并不是一定先执行!!)。优先级用int的priority参数表示。
线程优先级最高为10,最低为1。默认为5 

2.创建新的线程的方式-继承

a.描述:
	将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
b.分析创建的步骤:
	i.创建子类 继承 Thread
    ii.子类中重写run方法(在run中编写线程要执行的任务代码)
    iii.创建子类对象(实际上就是创建一个线程对象)
    iv. 调用线程对象的start方法(启动该线程)    
c.案例:
	//i.创建子类 继承 Thread
    public class MyThread extends Thread {
        //ii.子类中重写run方法(在run中编写线程要执行的任务代码)
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println("子线程..."+i);
            }
        }
    }
	public class ThreadDemo02 {
    public static void main(String[] args) {
        // iii.创建子类对象(实际上就是创建一个线程对象)
        MyThread mt = new MyThread();
        //iv. 调用线程对象的start方法(启动该线程)
        mt.start();
        //主线程 不会等待子线程任务结束
        for (int i = 0; i < 50; i++) {
            System.out.println("主线程..."+i);
        }
    }
}

注意:
	a.我们可以给线程起名字,也可以使用默认的名字
    b.我们获取线程的名字时:
			建议使用通用方式: Thread.currentThread().getName();
			如果是子线程内部也可以直接调用getName()获取子线程的名字

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

a.描述
    声明实现 Runnable 接口的类。该类然后实现 run 方法。
    然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递	并启动.
b.分析步骤:
	i.创建实现类 实现 Runnable接口(实际上接口中一个任务方法,run方法)
    ii.实现类重写run方法(run中编写具体的任务代码)
    iii.创建实现类对象(该实现类对象并不是线程对象,我们称为任务对象) 
    iv. 创建Thread对象,同时传入实现类对象
        public Thread(Runnable r);//带有线程任务的构造
	v. 启动该线程(调用线程对象的start方法)
c.代码实现
    //i.创建实现类 实现 Runnable接口(实际上接口中一个任务方法,run方法)
    public class MyRunnable implements Runnable {
        //ii.实现类重写run方法(run中编写具体的任务代码)
        @Override
        public void run() {
            //run中写任务代码
            for (int i = 0; i < 50; i++) {
                System.out.println("子线程..."+i);
            }
        }
    }
    
	public class TestThread {
        public static void main(String[] args) {
            //iii.创建实现类对象(该实现类对象并不是线程对象,我们称为任务对象)
            MyRunnable mr = new MyRunnable();
            //iv. 创建Thread对象,同时传入实现类对象
            Thread tt = new Thread(mr);
            //v. 启动该线程(调用线程对象的start方法)
            tt.start();

            //主线程不会等待子线程执行完毕
            for (int i = 0; i < 50; i++) {
                System.out.println("主线程..."+i);
            }
        }
    }

两种方式的优劣比较

两种创建线程的方式,实现方式比较好
    a.实现方式比较好,因为实现方式线程和任务是分开,是由程序员自己组合
    b.实现方式避免了Java单继承不足
    c.实现方式线程和任务是解耦的,继承方式线程和任务是耦合的
    d.对于线程池来说,我们需要的是Runnable的实现类,而不需要Thread的子类
综上所述: 在开发中我们建议使用实现方式(并不是说继承方式不对)   

匿名内部类简化创建线程方式

匿名内部类作用:
	可以快速创建一个类的子类对象或者一个接口的实现类对象
public class TestDemo {
    public static void main(String[] args) {
        //1.继承方式创建线程
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println(Thread.currentThread().getName()+"..."+i);
                }
            }
        }.start();
        //2.实现方式创建线程
        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(); 
	} 
}

多线程的安全性问题--可见性

什么有可见性:
	当一个共性变量,被多个线程使用时,其中某个线程对共性变量进行了修改,对于其他线程来说并不是立刻可见的
        其他线程获取的值还是以前的副本(旧的值)
案例:
	public class MyThread extends Thread {
        //无论创建多个MyThread对象,他们共性一个静态变量a
        public static int a = 0;
        @Override
        public void run() {
            System.out.println("线程启动,休息2秒...");
            try { Thread.sleep(1000 * 2); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("将a的值改为1");
            a = 1;
            System.out.println("线程结束...");
        }
    }

	public class TestSafaDemo01 {
        public static void main(String[] args) {
            //1.启动线程
            MyThread t = new MyThread();
            t.start();
            //2.主线程继续
            while (true) {
                if (MyThread.a == 1) {
                    System.out.println("主线程读到了a = 1");
                }
            }
        }
    }

多线程的安全性问题-有序性

什么是有序性:
    在不影响代码的结果的程度上对代码进行"重排"
    如果在多线程的情况下,"重排"可能对一样的代码,执行后得出不一样的结果
    我们要保证在多线程的情况下,不对代码进行"重排",保证代码是有序(不要使用重排!!) 

多线程的安全性问题-原子性

什么原子性:
    线程对一个共性变量,进行++时,这个++分成两步操作,先取出值加1 然后给共性变量赋值
    如果取出值加1后,还没有来得及赋值,被其他线程抢走CPU,此时我们称为++操作不具有原子性 

三。volatile关键字

volatile是一个关键字,用来修饰成员变量(静态变量),被他修饰的变量,具有可见性和有序性

volatile解决可见性

public class MyThread extends Thread {
    //无论创建多个MyThread对象,他们共性一个静态变量a
    public volatile static int a = 0;
    @Override
    public void run() {
        System.out.println("线程启动...");
        try { Thread.sleep(1000 * 2); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("将a的值改为1");
        a = 1;
        System.out.println("线程结束...");
    }
}

public class TestSafaDemo01 {
    public static void main(String[] args) {
        //1.启动线程
        MyThread t = new MyThread();
        t.start();
        //2.主线程继续
        while (true) {
            if (MyThread.a == 1) {
                System.out.println("主线程读到了a = 1");
            }
        }
    }
}

volatile解决有序性

volatile不能解决原子性

volatile不能解决原子性问题
public class MyThread extends Thread {
    public volatile static int a = 0;
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            a++;
        }
        System.out.println("修改完毕!");
    }
}

public class TestSafeDemo {
    public static void main(String[] args) throws InterruptedException {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.start(); //线程1 对a加了10000次
        t2.start(); // 线程2 对a加了 10000次
        Thread.sleep(1000);
        System.out.println("获取a最终值:" + MyThread.a);
        //总是不准确的。原因:两个线程访问a 的步骤不具有:原子性
    }
}

volatile的作用

a.解决变量的可见性,一旦变量发生改变,所有使用到该变量的线程都会取到最新值
b.解决变量的有序性,一旦变量加上volatile,那么编译器不会该变量的代码进行重排
c.无法解决变量操作过程中原子性,对变量的操作还是有可能被其他线程打断  

四。原子类

a.什么是原子类?
    是对普通类型(比如:int,Integer,double,Double)的原子类封装,使其的操作成员原子操作
b.原子类的作用?
    对原子类的增加或者减少操作,保证是原子性,保证中间不会被其他线程"打断"
c.原子类有哪些?
    比如:
        AtomicInteger是对int变量进行操作的原子类
        AtomicLong是对long变量进行操作的原子类
        AtomicBoolean对boolean变量操作的“原子类”;
注意: 原子类,既可以解决原子性,也可以解决有序性和可见性

java.util.concurrent.atomic 包下定义了一些对 变量 操作的 原子类 ”:
1).java.util.concurrent.atomic.AtomicInteger :对 int 变量操作的 原子类 ”;
2).java.util.concurrent.atomic.AtomicLong :对 long 变量操作的 原子类 ”;
3).java.util.concurrent.atomic.AtomicBoolean :对 boolean 变量操作的 原子类 ”;

AtomicInteger类

a.AtomicInteger是什么?
    是对int类型变量进行操作的原子类
b.AtomicInteger的构造方法
    public AtomicInteger(int num);
c.AtomicInteger的成员方法
    public int getAndIncrement();//就相当于 变量++  
	public int incrementAndGet();//就相当于 ++变量 
    
d.使用AtomicInteger改写案例    
    public class MyThread extends Thread {
        public static AtomicInteger a = new AtomicInteger(0);
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                a.getAndIncrement();//相当于 a++  先获取在自增1
            }
            System.out.println("修改完毕!");
        }
    }

	public class TestSafeDemo {
        public static void main(String[] args) throws InterruptedException {
            MyThread t1 = new MyThread();
            MyThread t2 = new MyThread();

            t1.start(); //线程1 对a加了10000次
            t2.start(); // 线程2 对a加了 10000次
            Thread.sleep(1000);
            System.out.println("获取a最终值:" + MyThread.a);
            //总是不准确的。原因:两个线程访问a 的步骤不具有:原子性
        }
    }

Unsafe 类中,调用了一个: compareAndSwapInt() 方法,此方法的几个参数:
var1 :传入的 AtomicInteger 对象
var2 AtommicInteger 内部变量的偏移地址
var5 :之前取出的 AtomicInteger 中的值;
var5 + var4 :预期结果
此方法使用了一种 " 比较并交换 (Compare And Swap)" 的机制,它会用 var1 var2 先获取内存中
AtomicInteger 中的值,然后和传入的,之前获取的值 var5 做一下比较,也就是比较当前内存的值和预期的值
是否一致,如果一致就修改为 var5 + var4 ,否则就继续循环,再次获取 AtomicInteger 中的值,再进行比较并
交换,直至成功交换为止。
 
compareAndSwapInt() 方法是 " 线程安全 " 的。
 
我们假设两个线程交替运行的情况,看看它是怎样工作的:
初始 AtomicInteger 的值为 0
线程 A 执行: var5 = this.getIntVolatile(var1,var2); 获取的结果为: 0
线程 A 被暂停
线程 B 执行: var5 = this.getIntVolatile(var1,var2); 获取的结果为: 0
线程 B 执行: this.compareAndSwapInt(var1,var2,var5,var5 + var4)
线程 B 成功将 AtomicInteger 中的值改为 1
线程 A 恢复运行,执行: this.compareAndSwapInt(var1,var2,var5,var5 + var4)
此时线程 A 使用 var1 var2 AtomicInteger 中获取的值为: 1 ,而传入的 var5 0 ,比较失败,返回
false ,继续循环。
线程 A 执行: var5 = this.getIntVolatile(var1,var2); 获取的结果为: 1
线程 A 执行: this.compareAndSwapInt(var1,var2,var5,var5 + var4)
此时线程 A 使用 var1 var2 AtomicInteger 中获取的值为: 1 ,而传入的 var5 1 ,比较成功,将其修改
var5 + var4 ,也就是 2 ,将 AtomicInteger 中的值改为 2 ,结束。
CAS 机制也被称为:乐观锁。因为大部分比较的结果为 true ,就直接修改了。只有少部分多线程并发的情况会
导致 CAS 失败,而再次循环。

AtomicIntegerArray类

常用的数组操作的原子类: 1).java.util.concurrent.atomic.AtomicIntegetArray: int 数组操作的原子类。
2).java.util.concurrent.atomic.AtomicLongArray :对 long 数组操作的原子类。
3).java.utio.concurrent.atomic.AtomicReferenceArray :对引用类型数组操作的原子类。

非原子类数组在多线程并发时会有问题

public class MyThread extends Thread {
    public static int[] intArray = new int[1000];//不直接使用数组

    @Override
    public void run() {
        for (int i = 0; i < intArray.length; i++) {
            intArray[i]++;
        }
    }
}

public class TestDemo01 {
    public static void main(String[] args) throws InterruptedException {
        //创建1000个线程,每个线程为数组的每个元素+1
        for (int i = 0; i < 1000; i++) {
            new MyThread().start();
        }

        Thread.sleep(1000 * 5);//让所有线程执行完毕
        System.out.println("主线程休息5秒醒来");
        for (int i = 0; i < MyThread.intArray.length; i++) {
            System.out.println(MyThread.intArray[i]);
        }
    }
}
打印结果:
	1000,1000,1000,999,1000,1000,1000,1000,....
有个别元素是小于1000的,因为int[]是非原子类数组,不能保存原子性!!!

使用原子类数组,保证原子性,解决问题

public class MyThread extends Thread {
    public static int[] intArray = new int[1000];//不直接使用数组
    public static AtomicIntegerArray arr = new AtomicIntegerArray(intArray);
    @Override
    public void run() {
//        for (int i = 0; i < intArray.length; i++) {
//            intArray[i]++;
//        }
        for (int i = 0; i < arr.length(); i++) {
            arr.addAndGet(i, 1);//将i位置上的元素 + 1,相当于 ++数组[i]
        }

    }
}

public class TestDemo01 {
    public static void main(String[] args) throws InterruptedException {
        //创建1000个线程,每个线程为数组的每个元素+1
        for (int i = 0; i < 1000; i++) {
            new MyThread().start();
        }

        Thread.sleep(1000 * 5);//让所有线程执行完毕
        System.out.println("主线程休息5秒醒来");
//        for (int i = 0; i < MyThread.intArray.length; i++) {
//            System.out.println(MyThread.intArray[i]);
//        }
        for (int i = 0; i < MyThread.arr.length(); i++) {
            System.out.println(MyThread.arr.get(i));
        }
    }
}
发布了103 篇原创文章 · 获赞 15 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/u010581811/article/details/104882590
今日推荐