JavaSE# 笔记【高并发和线程安全 volatile关键字 原子类 多行代码的线程安全问题 并发包】@Gray

一.高并发和线程安全

1.高并发和线程安全

高并发:就是在一段时间内有大量的线程要执行. 双11,春运12306,大学选选修课

​线程安全:在高并发的情况下,多个线程之间有互相影响的效果。

2.多线程内存运行机制

当一个线程启动后,JVM会为其分配一个独立的"线程栈区",这个线程会在这个独立的栈区中运行。

  • 看一下简单的线程的代码:

1.一个线程类:

public class MyThread extends Thread {
 @Override 
 public void run() { 
 	for (int i = 0; i < 100; i++) { 
	 	System.out.println("i = " + i);
  		} 
	 } 
 }
  1. 测试类:
public class Demo {
	 public static void main(String[] args) { 
		 	//1.创建两个线程对象
		 	 MyThread t1 = new MyThread(); 
			 MyThread t2 = new MyThread(); 
			 
			 //2.启动两个线程 
			 t1.start(); 
			 t2.start(); 
		} 
 }
  • 启动后,内存的运行机制
    在这里插入图片描述

3.安全性问题-可见性

  • 介绍
    多个线程在执行的过程中,一个线程可能看不到另一个线程对变量的改变,这个叫线程的可见性问题.
  • 代码演示
public class AAA extends Thread {
    //定义变量
    static int num = 0;
    @Override
    public void run() {
        System.out.println("AAA线程开始执行了");

        //睡眠2秒钟
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        }
        num = 10;
        System.out.println("AAA执行结束了");
    }
}

public class BBB extends Thread{
    @Override
    public void run() {
        //循环
        while(true){
            //判断
            if(AAA.num == 10){
                System.out.println("BBB线程知道数字是10了");
                break;
            }
        }
    }
}

public class Test01 {
    public static void main(String[] args) {
        AAA a = new AAA();
        a.start();

        BBB b = new BBB();
        b.start();
    }
}

图解:
在这里插入图片描述

4.安全性问题-有序性

  • 介绍

    • 在程序的编译期间,程序可能会把没有上下逻辑关系的代码上下打乱顺序这个叫代码重排,一个线程代码的重排可能会对别的线程造成影响。

    • 这是一个小概率事件所以无法通过代码演示。

  • 图解
    在这里插入图片描述

5.安全性问题-原子性

  • 介绍

    ​ 一个不可分割的语句,在多线程的情况下被分割成了多个步骤,导致线程之间互相有影响。

  • 代码演示

public class CCC extends Thread {
    static int num = 0;

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            num++;
        }
    }
}

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        //开启线程
        CCC c1 = new CCC();
        c1.start();
        CCC c2 = new CCC();
        c2.start();

        //让主线程睡眠,为了让循环先执行
        Thread.sleep(2000);

        //打印num
        System.out.println(CCC.num);

    }
}

  • 图解
    在这里插入图片描述

二.volatile关键字

​ 可以解决可见性有序性问题。用关键字修饰变量即可。

​ 关键字可以让线程每次都获取最新值,并且不允许代码重排。

//定义变量
static volatile int num = 0;

三.原子类

1.AtomicInteger演示

​ 原子类可以解决可见性 有序性原子性

创建线程:

import java.util.concurrent.atomic.AtomicInteger;
public class CCC extends Thread {
    //static int num = 0;
    static AtomicInteger num = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            //num++;
            num.getAndIncrement();
        }
    }
}

测试类:

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        //开启线程
        CCC c1 = new CCC();
        c1.start();

        CCC c2 = new CCC();
        c2.start();

        //让主线程睡眠,为了让循环先执行
        Thread.sleep(2000);
        //打印num
        System.out.println(CCC.num);

    }
}

2.AtomicInteger工作机制-CAS机制

在这里插入图片描述

3.AtomicIntegerArray类示例[了解]

数组存储的元素在多线程的情况下也会有线程安全问题。

线程类:

import java.util.concurrent.atomic.AtomicIntegerArray;
public class DDD extends Thread {
    //static int[] arr = new int[1000];

    //原子类数组代替普通数字,小括号里面写的也是数组长度
    static AtomicIntegerArray arr = new AtomicIntegerArray(1000);

    @Override
    public void run() {
        //1000循环
        //0 0 0 0 0 0 0 0 0 0 0 0 0
        //1 1 1 1 1 1 1 1 1 1 1 1 1

        //1000 1000 1000 1000 1000 1000
        for (int i = 0; i < 1000; i++) {
            //arr[i]++;
            //给i索引的元素加一
            arr.addAndGet(i,1);
        }
    }
}

测试类

import java.util.Arrays;
public class Test03 {
    public static void main(String[] args) throws InterruptedException {

        //开启了100个线程
        for (int i = 0; i < 1000; i++) {
            DDD d = new DDD();
            d.start();
        }

        //睡眠2秒钟
        Thread.sleep(2000);

        //打印数组
        //System.out.println(Arrays.toString(DDD.arr));
        System.out.println(DDD.arr);
    }
}

四.多行代码的线程安全问题【重点】

1.执行顺序的问题

  • 火车卖票问题

线程类:

public class AAA implements Runnable {

    //定义火车站一共有100张票
    int ticket = 100;
    @Override
    public void run() {
        //循环
        while(true){
            //判断有没有票
            if(ticket <= 0){
                break;
            }

            //如果有票就卖票
            System.out.println(Thread.currentThread().getName() +  "卖出了" + ticket + "号票");
            //给票减一
            ticket--;
        }
    }
}

测试类:

public class Test01 {
    public static void main(String[] args) {
        AAA a = new AAA();
        //开启线程
        Thread t1 = new Thread(a);
        t1.setName("窗口一");
        t1.start();

        //开启线程
        Thread t2 = new Thread(a);
        t2.setName("窗口二");
        t2.start();


    }
}
  • 图解
    在这里插入图片描述

2.synchronized关键字

表示同步,同步的意思是一个线程在执行的时候,别的线程只能等待。一个线程执行结束之后别的线程才能执行。

3.同步代码块

  • 格式

    synchronized(锁对象){
        同步代码
    }
    
  • 同步锁

    • 锁对象可以是任意类型的对象
    • 多个线程如果要同步必须使用同一个对象作为锁
  • 代码演示

线程实现类:

public class AAA implements Runnable {

    //定义火车站一共有100张票
    int ticket = 100;

    @Override
    public void run() {
        //循环
        while(true){
            //同步代码块
            synchronized ("abc") {
                //判断有没有票
                if (ticket <= 0) {
                    break;
                }

                //先获取当前线程对象,再获取线程名字
                String name = Thread.currentThread().getName();
                //如果有票就卖票
                System.out.println(name + "卖出了" + ticket + "号票");

                //给票减一
                ticket--;
            }
        }
    }
}

测试类:

public class Test01 {
    public static void main(String[] args) {
        AAA a = new AAA();
        //开启线程
        Thread t1 = new Thread(a);
        t1.setName("窗口一");
        t1.start();

        //开启线程
        Thread t2 = new Thread(a);
        t2.setName("窗口二");
        t2.start();
    }
}

4.同步方法

  • 格式

    public synchronized void method(){
        同步代码
    }
    
  • 同步方法的锁对象

    • 同步方法里面也是有锁对象的,但是锁对象不需要我们指定,同步方法的锁对象是固定的。
    • 非静态同步方法:this (代表当前类的对象)
    • 静态同步方法: 类的字节码对象(每一个类只有一个.class对象,所以这个对象一定是唯一的)
  • 代码演示

线程实现类:

public class AAA implements Runnable {

    //定义火车站一共有100张票
    int ticket = 100;

    @Override
    public void run() {
        //循环
        while(true){
            //判断有没有票
            if(ticket <= 0){
                break;
            }
            //调用方法
            method();
        }
    }
    //定义同步方法
    public  synchronized void method(){
        //再次判断
        if(ticket > 0) {
            //先获取当前线程对象,再获取线程名字
            String name = Thread.currentThread().getName();
            //如果有票就卖票
            System.out.println(name + "卖出了" + ticket + "号票");

            //给票减一
            ticket--;
        }
    } 
}

测试类:

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

        AAA a = new AAA();
        //开启线程
        Thread t1 = new Thread(a);
        t1.setName("窗口一");
        t1.start();

        //开启线程
        Thread t2 = new Thread(a);
        t2.setName("窗口二");
        t2.start();

    }
}

5.Lock锁

​ Lock锁的方式更符合面向对象的调用方式。更符合程序员的写代码习惯。

​ Lock是一个接口,有一个子类ReentrantLock

  • 两个方法
 public void lock()   :加同步锁 
 public void unlock() :释放同步锁
  • 代码演示

线程实现类:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class AAA implements Runnable {
    //定义火车站一共有100张票
    int ticket = 100;
    //创建锁对象
    Lock lock = new ReentrantLock();
    
    public void run() {
        //循环
        while(true){
            //加锁
            lock.lock();
            //判断有没有票
            if(ticket <= 0){
                lock.unlock();
                break;
            }
            //先获取当前线程对象,再获取线程名字
            String name = Thread.currentThread().getName();
            //如果有票就卖票
            System.out.println(name +  "卖出了" + ticket + "号票");
            //给票减一
            ticket--;
            //解锁
            lock.unlock();
        }
    }
}

测试类:

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

        AAA a = new AAA();
        //开启线程
        Thread t1 = new Thread(a);
        t1.setName("窗口一");
        t1.start();

        //开启线程
        Thread t2 = new Thread(a);
        t2.setName("窗口二");
        t2.start();

    }
}

五.并发包

之前学习的集合类型都是会有线程安全问题的,如果遇到了多线程情况的,需要用并发包解决问题。

1.CopyOnWriteArrayList[了解]

  • ArrayList和CopyOnWriteArrayList效果演示

线程实现类:

import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;

public class AAA extends Thread {

    //定义集合
    //static ArrayList<Integer> list = new ArrayList<>();
    //使用并发包集合
    static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();

    @Override
    public void run() {
        //给集合添加10000个元素
        for (int i = 0; i < 10000; i++) {
            list.add(i);
        }
    }
}

测试类:

public class Test01 {
    public static void main(String[] args) throws InterruptedException {

        //开启线程
        new AAA().start();
        new AAA().start();

        //让主线程睡眠让循环先执行
        Thread.sleep(2000);

        //打印集合长度
        System.out.println(AAA.list.size());

        //出现问题一:可能长度小于20000
        //出现问题二:可能出现索引越界异常

    }
}

2.CopyOnWriteArraySet[了解]

  • HashSet和CopyOnWriteArraySet效果演示

线程实现类:

import java.util.HashSet;
import java.util.concurrent.CopyOnWriteArraySet;
public class BBB extends Thread {
    //定义集合
    //static HashSet<Integer> set = new HashSet<>();
    //定义并发包集合
    static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
    @Override
    public void run() {
        //给集合添加10000个元素
        for (int i = 0; i < 10000; i++) {
            set.add(i);
        }
    }
}

测试类:

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        //开启线程
        new BBB().start();
        new BBB().start();

        //让主线程睡眠让循环先执行
        Thread.sleep(2000);
        //打印集合长度
        System.out.println(BBB.set.size());

        //出现问题:集合的长度大于10000
    }
}

3.ConcurrentHashMap

1. HashMap和Hashtable和ConcurrentHashMap效果演示

线程实现类:

import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;

public class CCC extends Thread {

    //创建集合
    //static HashMap<Integer,Integer> map = new HashMap<>();

    //使用Hashtable集合
    //static Hashtable<Integer,Integer> map = new Hashtable<>();

    //使用ConcurrentHashMap集合
    static ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();

    @Override
    public void run() {
        //给集合添加10000个元素
        for (int i = 0; i < 10000; i++) {
            map.put(i,i);
        }
    }
}

测试类:

public class Test03 {
    public static void main(String[] args) throws InterruptedException {
        //开启线程
        new CCC().start();
        new CCC().start();

        //让主线程睡眠
        Thread.sleep(2000);

        //打印集合长度
        System.out.println(CCC.map.size());

        //出现问题:集合的长度大于10000
    }
}

2.Hashtable和ConcurrentHashMap的速度区别【重点】

  • Hashtable执行速度慢
  • ConcurrentHashMap执行速度快

线程实现类:

import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;
public class CCC extends Thread {
    //创建集合
    //static HashMap<Integer,Integer> map = new HashMap<>();
    //使用Hashtable集合
//    static Hashtable<Integer,Integer> map = new Hashtable<>();
    //使用ConcurrentHashMap集合
    static ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<>();

    @Override
    public void run() {
        //获取系统当前时间
        long time1 = System.currentTimeMillis();

        //给集合添加10000个元素
        for (int i = 0; i < 10000; i++) {
            map.put(i,i);
        }
        //获取系统当前时间
        long time2 = System.currentTimeMillis();

        System.out.println( (time2-time1)  + "毫秒");
    }
}

测试类:

public class Test04 {
    public static void main(String[] args) {
        //创建1000个线程
        for (int i = 0; i < 1000; i++) {
            new CCC().start();
        }
    }
}

3.速度区别的原因

  • Hashtable方法都是同步的,一个线程在执行的时候,别的线程只能等待。
public synchronized V put(K key, V value) {
  • ConcurrentHashMap用到了CAS机制部分同步代码块.

4.悲观锁和乐观锁

  • CAS机制称为乐观锁,执行效率
  • 同步机制称为悲观锁,执行效率
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页