线程安全、volatile关键字、原子性、并发包、死锁、线程池

day08【线程安全、volatile关键字、原子性、并发包、死锁、线程池】

今日介绍:
	(重点)"a.线程的安全 
    (理解)b.volatile 可见性关键字       
    (理解)c.原子性      
    (重点)"d.并发包              

第一章 线程安全

1.1 线程安全问题出现的原因
a.单线程永远没有安全问题(单线程是安全的)
b.多线程同时执行,执行同样的任务,操作同一个共享数据,才有可能出现安全问题    
1.2 线程安全问题的演示:卖票案例
  • 代码演示

    /**
     * 卖票任务
     */
    public class MyTask implements Runnable{
    
        private int count = 100;
    
        @Override
        public void run() {
            while (true) {
                if (count > 0) {
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"卖出第"+count+"张票..");
                    count--;
                }else{
                    break;
                }
            }
        }
    }
    public class ThreadDemo {
        public static void main(String[] args) {
            //0.创建任务
            MyTask mt = new MyTask();
            //1.创建线程
            Thread t1 = new Thread(mt);
            Thread t2 = new Thread(mt);
            Thread t3 = new Thread(mt);
    
            //2.启动
            t1.start();
            t2.start();
            t3.start();
            //出现的安全问题:
            //a.可能出现重复数据
            //b.可能出现0,-1非法数据
        }
    }
    
  • 执行结果

    a.可能出现重复数据
    b.可能出现0,-1等非法数据    
    
  • 产生重复数据的原因:

    • 第一个线程卖完某张票,还没有来得及让票数减1,CPU就被其他线程抢走了,导致其他线程也卖出相同的票
  • 产生非法数据0和-1的原因:

    • 当剩下最后一张票时,三个线程都通过大于0的判断,导致卖出第0和-1张票
1.3 线程同步
  • 什么是线程同步:

    让某些代码只能由一个线程进入执行,当该线程没有执行完毕之前,其他线程无法进入执行
    
1.4 三种线程同步的方式
  • 同步代码块

    格式:
    	synchronized(锁对象){
         	需要同步的代码   
        }
    	锁对象可以是任意对象,但是必须是同一个对象,也就是说锁对象,不能直接new Object();
            
    /**
     * 卖票任务
     */
    public class MyTask implements Runnable{
    
        private int count = 100;
        private Object obj = new Object();
    
        @Override
        public void run() {
            while (true) {
                //同步代码块
                synchronized (obj) {
                    if (count > 0) {
                        System.out.println(Thread.currentThread().getName() + "卖出第" + count + "张票..");
                        count--;
                    }
                }
            }
        }
    }        
    
  • 同步方法

    格式:
    	public synchronized void 方法名(){
         	需要同步的代码块   
        }
    
    /**
     * 卖票任务
     */
    public class MyTask implements Runnable{
    
        private int count = 100;
    
        @Override
        public void run() {
            while (true) {
               sellTicket();
            }
        }
        //同步方法
        public synchronized void sellTicket() {
            if (count > 0) {
                System.out.println(Thread.currentThread().getName()+"卖出第"+count+"张票..");
                count--;
            }
        }
    }
    扩展: 同步方法能否是静态的呢???
        	可以!!!如果同步方法是静态的,那么默认使用当前类的字节码文件作为锁对象
    
  • Lock锁机制

    格式:
    	Lock lock = new ReentrantLock(); 
    	lock.lock(); -- 加锁,获取锁
            需要同步的代码
        lock.unlock(); -- 解锁,释放锁    
            
    /**
     * 卖票任务
     */
    public class MyTask implements Runnable{
    
        private int count = 100;
    
        private Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                //加锁
                lock.lock();
                    if (count > 0) {
                        System.out.println(Thread.currentThread().getName()+"卖出第"+count+"张票..");
                        count--;
                    }
                //解锁
                lock.unlock();
            }
        }
    }        
    

第二章 volatile关键字(理解即可)

2.1. 看程序说结果
  • 示例代码
public class VolatileThread extends Thread {
	// 定义成员变量
	private boolean flag = false ;
	public boolean isFlag() { return flag;}
	@Override
	public void run() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 将flag的值更改为true
		this.flag = true ;
		System.out.println("flag=" + flag);
	}
}
public class TestVolatileDemo {
    public static void main(String[] args) {
        // 创建VolatileThread线程对象
        VolatileThread volatileThread = new VolatileThread() ;
        volatileThread.start();


        //主线程中的代码
        while(true) {
            if(volatileThread.isFlag()) {
                System.out.println("执行了======");
            }
        }
    }
}
  • 结果只有一句代码输出:flag=true
2.2. JMM
  • JMM是Java虚拟机提出一种Java内存模型.
    • JMM中规定 所有共享变量(成员变量和静态变量) 都保存在主内存
    • 当某个线程要使用该共享变量时,会在当前线程的工作内存中保存一个变量的副本
2.3. 问题分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oQcHKh2H-1577451048240)(img/image-20191226102433734.png)]

2.4. 问题解决方案
  • 加锁

    public class TestVolatileDemo {
        public static void main(String[] args) {
            // 创建VolatileThread线程对象
            VolatileThread volatileThread = new VolatileThread() ;
            volatileThread.start();
    
            Object obj = new Object();
            //主线程中的代码
            while(true) {
                synchronized (obj) { // 第一种方式,加锁
                    if (volatileThread.isFlag()) {
                        System.out.println("执行了======");
                    }
                }
            }
        }
    }
    因为加锁:可以保证工作内存被清空,且从主内存获取最新的值
    
  • volatile关键字

    public class VolatileThread extends Thread {
        // 定义成员变量
        private volatile boolean flag = false ; //地中方式:添加volatile关键字
        public boolean isFlag() { return flag;}
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 将flag的值更改为true
            this.flag = true ;
            System.out.println("flag=" + flag);
        }
    }
    
    volatile: 可见性关键字,保证当工作内存修改之后,主内存可以预见,会主动去更新最新的值
        	并且把所有工作内存中副本设置为无效
    
  • synchronized和volatile的区别

    synchronized 清空工作内存,导致必须重新从主内存中获取最新的值
     volatile 工作内存中的值设置为无效,不得不从内存中获取最新的值
    
    synchronized 既可以保存可见性,也可以保证原子性
       volatile 只能保证可见性,但并不具有原子性 
     
    synchronized 用于修饰方法或者代码块
        volatile 用于修饰成员变量或者静态变量
    

第三章 原子性(理解即可)

3.1. 看程序说结果
public class VolatileAtomicThread implements Runnable {
    // 定义一个int类型的遍历
    private int count = 0 ;
    @Override
    public void run() {
	// 对该变量进行++操作,1000次
        for(int x = 0 ; x < 1000 ; x++) {
            count++ ;//1.获取值 2.增加值 3.更新值
          	//count = count + 1
            System.out.println("count =========>>>> " + count);
        }
    }
}

public class TestVolatileAtomicThreadDemo {
    public static void main(String[] args) {
        //1.创建任务
        VolatileAtomicThread t = new VolatileAtomicThread();
        //2.创建100个线程
        // 开启100个线程对count进行++操作
        for(int x = 0 ; x < 100 ; x++) {
            new Thread(t).start();
        }
    }
}

结果:count =========>>>> 99998 少于 100000
3.2. 问题原理说明

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lOscG5VP-1577451048241)(img/image-20191226110605547.png)]

3.3. volatile原子性测试
  • synchronized具有原子性,保证某个线程count++三步操作,不会分割!!

  • volatile只具有可见性,也就说当主内存的共享数据改变了,会通知所有的子线程工作内存的副本进行更新

  • 但是,它不具有原子性,也就说如果我们在工作内存中对变量的操作不是原子操作,那么volatile不能保持其原子性

3.4. 问题解决方案
  • 加锁

    public class VolatileAtomicThread implements Runnable {
        // 定义一个int类型的遍历
        private int count = 0;
    
        @Override
        public void run() {
    // 对该变量进行++操作,100次
            for (int x = 0; x < 1000; x++) {
                synchronized (this) {
                    count++;
                    System.out.println("count =========>>>> " + count);
                }
            }
        }
    }
    只要给count++加锁,那么只有获取到锁的线程执行完毕count++的所有操作之后,其他才能执行
    也就是说我们count++变成原子操作
    
  • 原子类

    • AtomicInteger原子类

构造方法:
public AtomicInteger();-- 默认值为0
public AtomicInteger(int initialValue);-- 指定初始值
成员方法:
public int getAndIncrement(): – 相当于 i++
public int incrementAndGet(): – 相当于 ++i
但是 getAndIncrement 和 incrementAndGet 具体原子性的

```
使用原子类改造案例
public class VolatileAtomicThread implements Runnable {
    // 定义一个int类型的遍历
//    private int count = 0;
    private AtomicInteger count = new AtomicInteger();
    @Override
    public void run() {
// 对该变量进行++操作,100次
        for (int x = 0; x < 1000; x++) {
                count.getAndIncrement();//相当于 后++
                System.out.println("count =========>>>> " + count);
        }
    }
}

第四章 并发包

4.1 ConcurrentHashMap
a.HashMap是线程不安全的,多个线程向同一个Map中添加键值对时,可能会出现Map中键值对个数少于实际添加的个数
b.在多线程的情况下,Java给我一个HashTable,它是线程安全的,HashTable使用全表锁
c.JDK的并发包中提出另外一个新的键值对集合:ConcurrentHashMap  
    他也是线程安全的,但是使用局部锁(锁当前键值对添加到的那个链表)
    
public class Const {
    //线程不安全,最快!!!
    public static HashMap<String,String> map = new HashMap<>();
    //线程安全,但是由于采用全局锁(全表都加锁),所以性能较低
    public static HashTable<String,String> map = new HashTable<>();
    //线程安全,而且采用局部锁(只对哈希表中某个链表加锁),所以性能更高
    public static ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
}  

public class Thread1A extends Thread {
    public void run() {
        long start = System.currentTimeMillis();
        for (int i = 0; i < 500000; i++) {
            Const.map.put(this.getName() + (i + 1), this.getName() + i + 1);
        }
        long end = System.currentTimeMillis();
        System.out.println(this.getName() + " 结束!"+(end-start));
    }
}

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread1A a1 = new Thread1A();
        Thread1A a2 = new Thread1A();
        a1.setName("线程1-");
        a2.setName("线程2-");
        a1.start();
        a2.start();
        //休息10秒,确保两个线程执行完毕
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println(i);
        }
        //打印集合大小
        System.out.println("Map大小:" + Const.map.size());
    }
}

4.2 CountDownLatch
作用: 让一个线程,等待另外一个线程执行完毕之后,再往下执行
构造方法:
	public CountDownLatch(int size); -- 其中size称为线程计数器
        
成员方法:
	public void await(); -- 等待其他线程
    public void countDown()    -- 线程计数器-1 
        
public class ThreadA extends Thread {
    private CountDownLatch latch;

    public ThreadA(CountDownLatch latch){
        this.latch = latch;
    }

    @Override
    public void run() {
        System.out.println("A");
        //让线程等待
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("C");
    }
}
public class ThreadB extends Thread {
    private CountDownLatch latch;

    public ThreadB(CountDownLatch latch){
        this.latch = latch;
    }

    @Override
    public void run() {
        System.out.println("B");
        //让线程计数器-1
        latch.countDown();
    }
}

public class TestDemo {
    public static void main(String[] args) {
        //1.创建一个CountDownLatch
        CountDownLatch latch = new CountDownLatch(1);

        ThreadA a = new ThreadA(latch);
        ThreadB b = new ThreadB(latch);
        a.start();
        b.start();
    }
}
4.3 CyclicBarrier
CyclicBarrier 
    作用: 让一组线程都执行到某个点时,才能继续某个任务
    构造方法:
		public CyclicBarrier(int parties, Runnable barrierAction);
						-- parties总共需要的线程数
            			-- barrierAction 当所有线程都到了,需要执行的任务
                            
    成员方法:
		public int await(); -- 当某个线程到了,需要调用该方法等待
需求:公司召集5名员工开会,等5名员工都到了,会议开始。            
public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        //0.创建一个屏障点
        CyclicBarrier cb = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                System.out.println("人都到了,开始开会...");
            }
        });
        //1.创建5个线程,模拟5个员工
        MyThread m1 = new MyThread(cb);
        MyThread m2 = new MyThread(cb);
        MyThread m3 = new MyThread(cb);
        MyThread m4 = new MyThread(cb);
        MyThread m5 = new MyThread(cb);
        
		m1.setName("张三");m2.setName("李四");m3.setName("王五");
        m4.setName("赵六");m5.setName("前妻");
        
        m1.start();m2.start();m3.start();m4.start();m5.start();
    }
}
public class MyThread extends Thread {
    private CyclicBarrier cb;
    public MyThread(CyclicBarrier cb) {
        this.cb = cb;
    }
    @Override
    public void run() {
        try {
            //模拟随机等待1-5秒
            Thread.sleep(new Random().nextInt(5000)+1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName()+"到了...");
        //调用await方法
        try {
            cb.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

4.4 Semaphore
Semaphore
    作用:是控制线程的并发数量。
	构造方法:
	public Semaphore(int permits); -- permits 最多允许的并发线程数量
    成员方法:
	public void acquire(); -- 获取线程运行的许可
    public void release(); -- 归还线程运行的许可    
   
        
 public class MyThread extends Thread {
    private Semaphore sm;
    public MyThread(Semaphore sm) {
        this.sm = sm;
    }

    @Override
    public void run() {
        //获取线程执行的许可
        try {
            sm.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + "即将要执行...");

        try {
            Thread.sleep(new Random().nextInt(3000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(getName() + "即将要结束...");
        //归还线程的许可
        sm.release();
    }
}

public class TestDemo {
    public static void main(String[] args) {
        //0.创建一个Semaphore
        Semaphore sm = new Semaphore(1);

        //1.创建五个线程
        for (int i = 0; i < 50; i++) {
            MyThread t = new MyThread(sm);
            t.start();
        }
    }
}
       
4.5 Exchanger
Exchanger 
    作用: 线程间数据交换器
  	构造:
	public Exchanger()
    成员方法:
	public V exchange(V x); -- 将参数传给其他线程,同时接受其他线程传回的数据
  
        
public class ThreadA  extends Thread{
    private Exchanger exchanger;

    public ThreadA(Exchanger exchanger) {
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        System.out.println("线程A,给线程B送礼..");
        System.out.println("同时等待线程B的回礼..");
        Object result = null;
        try {
            result = exchanger.exchange("AAAAA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程B回礼了,"+result);
    }
}
        

public class ThreadB extends Thread{
    private Exchanger exchanger;

    public ThreadB(Exchanger exchanger) {
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        System.out.println("线程B,给线程A送礼...");
        System.out.println("同时等待线程A的回礼..");
        Object result = null;
        try {
            result = exchanger.exchange("BBBBB");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("收到了线程A的回礼,"+result);
    }
}

public class TestDemo {
    public static void main(String[] args) {
        //1.创建一个线程间 数据交换器
        Exchanger exchanger = new Exchanger();
        //2.创建两个线程
        ThreadA a = new ThreadA(exchanger);
        ThreadB b = new ThreadB(exchanger);
        a.start();
        b.start();
    }
}
总结:
"能够解释安全问题的出现的原因
    多线程同时执行同一个任务,操作同一个共享数据,才可能出现安全问题
"能够使用同步代码块解决线程安全问题
    synchronized(锁对象){
    	需要同步的代码
	}
"能够使用同步方法解决线程安全问题
    public synchronized void 方法名(){
    	需要同步的代码
	}
    注意:同步方法,需要锁,但是不需要我们手动编写,默认使用当前对象this作为锁对象
        如果同步方法是静态的,默认使用当前类的字节码文件作为锁对象
"能够使用Lock锁解决线程安全问题   
    Lock lock = new ReentrantLock();
	lock.lock();
		需要同步的代码
    lock.unlock();        

能够说出volatile关键字的作用
    可见性: 当线程的工作内存中修改了副本的值,主内存中的原本就会去更新最新的值
能够说明volatile关键字和synchronized关键字的区别
      volatile 不具有原子性
      synchronized 具有原子性
        
能够理解原子类的工作机制,CAS机制
"能够掌握原子类AtomicInteger的使用
        AtomicInteger ai = new AtomicInteger(初始值);
		ai.getAndIncrement(); --++
		ai.increamentAndGet(); --++
"能够描述ConcurrentHashMap类的作用
        	解决HashMap线程不安全问题,解决HashTable线程安全但是性能较差问题    
"能够描述CountDownLatch类的作用
            用于让一个线程 等待另外一个线程执行完毕,当前线程才能继续执行 
"能够描述CyclicBarrier类的作用
            用于让一组线程完成某些操作之后,其他任务才能执行 
"能够表述Semaphore类的作用
         	用于限制线程并发的最大数量   
"能够描述Exchanger类的作用
            用于线程间数据交换

猜你喜欢

转载自blog.csdn.net/qq_41371264/article/details/103738275
今日推荐