zoukankan      html  css  js  c++  java
  • Java 多线程二、线程的生命周期、线程安全、死锁

    一、 线程的生命周期

    线程是存在生命周期的,线程从创建之后,运行后执行完相关操作,其终点一定是死亡。

    如下图:演示线程的生命周期:

    线程的生命中期分为五个阶段

    • 1.新建
    • 2.就绪
    • 3.运行
    • 4.阻塞(不一定有
    • 5.死亡

    这5个阶段里,其中阻塞是不一定有的,其他几个状态都有,线程的最终结果都是死亡。

    1.正常状态的变化:

    • 新建->就绪:
      调用thread.start() 方法即可让线程到就绪状态。
    • 就绪->运行:
      当线程获取CPU的执行权时,即会到运行状态。
    • 运行->就绪:
      当运线程行时,失去CPU的执行权,即会到就绪状态,如使用yeild()方法强制切换CPU的执行权。
    • 运行->死亡:
      线程的最终结果都应该是死亡,从运行到死亡是不可逆的。
      通过执行stop() 方法,或者是抛异常(Error/Exception)没有处理的情况下,则该线程会死亡。

    2.有阻塞状态的变化

    • 运行->阻塞
      通过sleep(time)方法调用,让线程等待一段时间,此时线程会到阻塞状态,直到sleep时间到。
      通过wait() 让线程挂起,直到有notify() 通知该线程重新到就绪状态。

    • 阻塞->就绪
      首先明确,阻塞不能直接到运行状态,线程阻塞后,当线程被唤醒时,他是先到就绪状态,当该线程拿到CPU 的执行权时,放可到运行状态。

      • 1).当sleep 时间到时,会从阻塞到就绪
      • 2).当被wait的线程,使用 notify/notifyAll 方法唤醒线程时,被wait的线程会到就绪状态。

    二、 线程的安全问题

    当多线程之间有共享数据的时候,就会存在线程安全问题。

    多线程导致错票问题

    • 1.卖票过程出现了重票,错票问题,即出现了线程安全问题。
    • 2.出现重票和错票的原因,是因为在一个线程还没操作完,另一个线程过来再操作共享数据,即会出现线程安全问题。

    例如:

    三个线程同时抢100张票,当加了sleep 之后,出现重票,错票的概率大大增加了,因为在上一个线程还没进行ticket--时候,下一个线程也进来了,导致因为共享数据问题导致错票。

    代码如下:

    public class WindowTest {
        public static void main(String[] args) {
            Window win = new Window();
            Thread t1 = new Thread(win);
            Thread t2 = new Thread(win);
            Thread t3 = new Thread(win);
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    class Window implements Runnable {
        private int ticket = 100;
        @Override
        public void run() {
            while (true) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
    
                }
            }
        }
    }
    

    运行结果中有票号为-1,即错票。

    如何解决?
    思路:当一个线程在操作共享数据时,当前线程锁定资源,其他线程不能操作该共享数据,这种情况即使该线程阻塞,其他线程也不允许操作共享数据。

    三种方式来解决线程安全问题

    1.同步代码块

    	synchronized(同步监视器){
    	//需要被同步的代码
    	}
    
    • 什么是需要被同步的代码?即操作共享数据的代码
    • 什么是共享数据?多个线程共同操作的变量。比如该例子中的ticket
    • 什么是同步监视器?形象的说就是“锁”。任何一个类的对象,都可以充当锁,但切记锁是惟一的。

    切记:多个线程公用一把锁,锁是唯一的。

    说明:加了同步监视器之后,在同步代码块中,智能有一个线程操作,其他线程等待,所以这块相当于单线程的,所以效率比较低一些。
    在使用继承方式来创建多线程时,要慎用this来充当同步监视器,考虑使用当前类充当同步监视器,如 : Window2.class

    例如:

    如下方式,使用同步代码块方式解决线程安全问题:

    public class WindowTest1 {
        public static void main(String[] args) {
            Window1 win = new Window1();
            Thread t1 = new Thread(win);
            Thread t2 = new Thread(win);
            Thread t3 = new Thread(win);
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    
    class Window1 implements Runnable {
        private int ticket = 100;
        //创建一个唯一对象,做为锁,锁可以使任何对象,但切记锁智能有一把
        Object obj = new Object();
    
        @Override
        public void run() {
            while (true) {
                //这里也可以用this 替换obj,因为我们只new了一个Window
                synchronized (obj) {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
                        ticket--;
                    } else {
                        break;
                    }
                }
            }
        }
    }
    

    此时,运行结果中就不会有重票出现了

    2.同步方法

    如果操作共享数据的代码完整的声明在 一个方法中,我们不妨将此方法声明为同步的,在方法声明处加 synchronized 标识符。

    如下代码演示使用同步方法来解决线程安全问题:

    public class WindowTest3 {
        public static void main(String[] args) {
            Window3 win = new Window3();
            Thread t1 = new Thread(win);
            Thread t2 = new Thread(win);
            Thread t3 = new Thread(win);
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    
    class Window3 implements Runnable {
        private int ticket = 100;
    
        //创建一个唯一对象,做为锁,锁可以使任何对象,但切记锁智能有一把
        @Override
        public void run() {
            while (true) {
                this.show();
                if (ticket <= 0) {
                    break;
                }
            }
        }
    
        private synchronized void show() {//同步监视器中,这块默认的锁就是this
            if (ticket > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
                ticket--;
            }
        }
    }
    

    此时,错票数据就不会出现了

    注意: 因为此时只有一个Window3对象,所以同步监视器默认就是this

    3.Lock 锁 ReentrantLock

    JDK 5.0 中新增了可以使用Lock 锁方式解决线程安全问题。

    如下代码演示如何通过Lock锁方式,手动加锁和解锁

    程序在run方法中,先手动加锁,保证只有当前线程拿到锁后其他线程不会进来,当执行到finally时候,释放锁,这时其他线程才能进来。

    package com.jerry.thread6;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class WindowTest5 {
        public static void main(String[] args) {
            Window5 win = new Window5();
            Thread t1 = new Thread(win);
            Thread t2 = new Thread(win);
            Thread t3 = new Thread(win);
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    
    class Window5 implements Runnable {
        private int ticket = 100;
        private ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                try {
                    //手动加锁
                    lock.lock();
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
                        ticket--;
                    } else {
                        break;
                    }
                } finally {
                    //手动解锁
                    lock.unlock();
                }
            }
        }
    }
    

    总结:

    面试题一:synchronized 和 lock方式,有什么不同?

    相同点: 二者都可以解决线程安全问题。
    不同点: lock 方式,是手动加锁和解锁。synchronized 是自动解锁的,他是在执行完同步方法或同步代码块才会自动释放同步监视器(自动解锁)。手动加锁,解锁,更加灵活

    面试题二:如何解决线程安全问题?

    1)加同步代码块synchronized 关键字

    2)使用同步方法 ,即加synchronized 关键字的方法

    3)使用 ReentrantLock 手动加锁,手动解锁

    单例中的线程安全

    如下:

    懒汉式代码,存在线程安全问题,因为当多线程创建单例时,可能会重复创建,解决懒汉式线程安全,一般有两种方法

    2. 性能较低的懒汉式

    这段代码,当第一次创建实例后,其他线程还要去判断锁,性能是比较低的。

    class Bank1 {
        private Bank1() {
        }
    
        private static Bank1 instance = null;
    
        //这种写法,效率较低,因为第一次已经将instance创建出来了,其他线程再去用的时候,还得去再判断一下锁,效率比较低
        public static Bank1 getInstance() {
            synchronized (Bank1.class) {
                if (instance == null) {
                    return new Bank1();
                }
                return instance;
            }
        }
    }
    

    1. 性能较高的懒汉式

    双重判断,当第一个线程创建了实例对象之后,第二个线程看到他不是NULL 了,就不用去再次判断锁了

    class Bank2 {
        private Bank2() {
        }
    
        private static Bank2 instance = null;
    
        //这种写法,做了双重判断,当第一个线程创建了实例对象之后,第二个线程看到他不是NULL 了,就不用去再次判断锁了
        public static Bank2 getInstance() {
            if (instance == null) {
                synchronized (Bank2.class) {
                    if (instance == null) {
                        return new Bank2();
                    }
                }
            }
            return instance;
        }
    }
    

    三、 线程的死锁问题

    什么是死锁?

    • 1.不同线程分别占用对方的需要的同步资源不放弃,都在等待对方放弃自己所需要的同步资源,就形成了线程的死锁
    • 2.出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

    打个比方:

    两个人分别有两双筷子,但是拿的都是对方的筷子,都在等对方把筷子给自己才能去吃饭,这时两个人就僵住了,谁也吃不到饭,这个问题就类似死锁问题。

    如下代码演示死锁

    如下代码,使用了双重锁,第一个线程,先抓s1,再抓s2,第二个线程,先抓s2,再抓s1。
    当线程1执行到sleep时候,挂起了一会,此时线程2刚好运行后抓住s2 的锁不放,因为s2这块也有sleep,这时线程1要去拿s2就拿不到,此时即出现了死锁。

    public class DeadLockTest {
        //死锁问题
        //产生死锁的原因,sleep之后,死锁概率加大,两个锁互相等待对方释放锁,而又拿着对方的锁不放,就导致死锁
        public static void main(String[] args) {
    
            StringBuilder s1 = new StringBuilder();
            StringBuilder s2 = new StringBuilder();
            //使用继承方式创建线程
            new Thread() {
                @Override
                public void run() {
                    synchronized (s1) {
                        s1.append("a");
                        s2.append("1");
                        sleepTime(100);
    
                        synchronized (s2) {
                            s1.append("b");
                            s2.append("2");
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }.start();
    
            //使用实现Runnable 接口的方式创建线程
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (s2) {
                        s1.append("c");
                        s2.append("3");
                        sleepTime(100);
                        synchronized (s1) {
                            s1.append("d");
                            s2.append("4");
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }).start();
        }
    
        private static void sleepTime(int time) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    怎样避免死锁?

    • 1.专门的算法,原则避免。
    • 2.尽量减少同步资源的定义
    • 3.尽量避免同步嵌套
  • 相关阅读:
    Read-Copy Update Implementation For Non-Cache-Coherent Systems
    10 华电内部文档搜索系统 search04
    10 华电内部文档搜索系统 search05
    lucene4
    10 华电内部文档搜索系统 search01
    01 lucene基础 北风网项目培训 Lucene实践课程 索引
    01 lucene基础 北风网项目培训 Lucene实践课程 系统架构
    01 lucene基础 北风网项目培训 Lucene实践课程 Lucene概述
    第五章 大数据平台与技术 第13讲 NoSQL数据库
    第五章 大数据平台与技术 第12讲 大数据处理平台Spark
  • 原文地址:https://www.cnblogs.com/vpersie2008/p/12802376.html
Copyright © 2011-2022 走看看