zoukankan      html  css  js  c++  java
  • Java基础—线程

      推荐阅读:http://www.iteye.com/topic/806990

    一、起手式——基本概念

    1.什么叫线程

      进程:进行中的程序;作为资源分配的单位。

      线程:轻量级的进程;程序里的顺序控制流,可以理解为程序里不同的执行路径;作为调度和执行的单位

          多个线程可以共享内存,共享地址。相互间的通信十分迅速

            线程体为run()方法(直接调用run()视为普通方法),启动线程为start()方法

        这里方法run()称为线程体, 它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止, 而CPU再运行其它线程, 而如果直接用Run方法, 这只是调用一个方法而已, 程序中依然只有主线程--这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。

      真正的多线程是指有多个CPU,而我们自己用的电脑,实际上是模拟多线程,来多个线程之间来回切换,然而由于切换的时间非常之短,所以我们所看到的就好像同时进行多个线程一样。

    2.如何创建线程

      1.继承Thread类,重写run()方法

      线程类:Rabbit(兔子)类与Tortoise(乌龟)类:

    package com.jiangbei.thread;
    
    /**
     * 兔子类
     * @author Administrator
     * @date 2017/8/30
     **/
    public class Rabbit extends Thread {
    
        @Override
        public void run() {
            //线程体
            for (int i =0; i < 100; i++){
                System.out.println("兔子跑了"+i+"步");
            }
        }
    }
    
    /**
     * 乌龟类
     */
    class Tortoise extends Thread {
    
        @Override
        public void run() {
            //线程体
            for (int i =0; i < 100; i++){
                System.out.println("乌龟跑了"+i+"步");
            }
        }
    }
    View Code

      应用线程类方法线程体是run()方法,启动线程是start()方法,直接调用run()是普通方法,并且启动线程并不等于运行了,具体还需要等带CPU调度

    package com.jiangbei.thread;
    
    /**
     * 兔子类应用类
     * @author Administrator
     * @date 2017/8/30
     **/
    public class RabbitApp {
        public static void main(String[] args) {
            //创建子类对象
            Rabbit rabbit = new Rabbit();
            Tortoise tortoise = new Tortoise();
    
            //调用start()方法,启动线程
            rabbit.start();
            tortoise.start();
    
            for(int i =0; i < 10; i++){
                System.out.println("main>>>>>>");
            }
        }
    }
    View Code

      运行结果:

      

      2.实现Runnable接口,实现run方法(推荐)

        优点避免单继承;方便共享资源

        原理是静态代理模式,将在设计模式篇中补充

      实现接口的线程类:

    package com.jiangbei.thread;
    
    /**
     * 实现Runnable接口,创建线程的类
     * 真实角色类
     * @author Administrator
     * @date 2017/8/30
     **/
    public class Programer implements Runnable{
        @Override
        public void run() {
            for(int i = 0; i < 100; i++){
                System.out.println("一边敲HelloWorld");
            }
    
        }
    }
    View Code

      应用类:(步骤见代码注释)

    package com.jiangbei.thread;
    
    /**
     * Programer应用类
     * @author Administrator
     * @date 2017/8/30
     **/
    public class ProgramerApp {
        public static void main(String[] args) {
            //创建真实角色
            Programer p = new Programer();
            //创建代理角色+持有真实角色引用
            Thread proxy = new Thread(p);
            //是用代理角色启动线程
            proxy.start();
    
            for (int i = 0; i < 100; i++) {
                System.out.println("一边聊QQ");
            }
    
        }
    }
    View Code

      结果:

      

       匿名内部类的写法:

      new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Counter.inc();
                    }
                }).start();

      应用:实现接口方式模仿12306购票(方便共享资源:)

    package com.jiangbei.thread.web12306;
    
    /**
     * 模拟Web12306抢票系统
     * @author Administrator
     * @date 2017/8/30
     **/
    public class Web12306 implements Runnable{
        private int num = 50;
    
        @Override
        public void run() {
            while (true) {
                if(num < 0){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
            }
        }
    
        public static void main(String[] args) {
            //创建真实对象
            Web12306 web12306 = new Web12306();
            //创建代理对象
            Thread t1 = new Thread(web12306,"黄牛甲");
            Thread t2 = new Thread(web12306,"黄牛乙");
            Thread t3 = new Thread(web12306,"黄牛丙");
            //启动线程
            t1.start();
            t2.start();
            t3.start();
    
        }
    }
    View Code

      结果:

      

      是用可以有返回值的 Callable 接口,暂不此展开...

    3.线程的五种状态

      

      详细状态:

      

      创建:两种方式 继承Thread类,重写run()方法;实现Runnable接口,实现run()方法。——new出一个线程

      就绪:调用Thread的start()方法(第二种实现接口方式使用静态代理)

      运行:得到时间片,开始运行

      阻塞:遇到阻塞事件——解除阻塞回到就绪状态

      终止线程死亡,严禁使用stop()方法,而应该定义一个boolean flag,并在run()方法中判断标志进行合理结束run()方法进而结束线程。

        通过外部干预实例:

    package com.jiangbei.thread;
    
    /**
     * 测试线程状态类
     *
     * @author Administrator
     * @date 2017/8/30
     **/
    public class ThreadStatusTest {
        public static void main(String[] args) {
            Study study = new Study();
            //创建匿名代理
            new Thread(study).start();
            for (int i =0; i < 500; i++) {
                if (450 == i) {
                    study.stop();
                }
                System.out.println("main>>>>>>>>>");
            }
        }
    }
    class Study implements Runnable {
        private boolean flag = true;
        @Override
        public void run() {
            while (flag){
                System.out.println("Study...");
            }
        }
        //对外提供停止方法
        public void stop () {
            this.flag = false;
        }
    }
    View Code

    4.线程常用方法

      Thread.sleep():线程睡眠,参数为当前线程的睡眠毫秒数。(静态方法)。抱着锁睡觉

      join()线程合并,将当前线程与线程合并,等待线程终止;

        示例:

    package com.jiangbei.thread;
    
    /**
     * 测试Thread的常用方法
     *
     * @author Administrator
     * @date 2017/8/30
     **/
    public class ThreadMethod extends Thread{
        public static void main(String[] args) throws InterruptedException {
            ThreadMethod tm = new ThreadMethod();
            Thread t = new Thread(tm);
            t.start();
            for(int i = 0; i < 100; i++){
                t.join();//等待线程执行完毕
                System.out.println("main...."+i);
            }
        }
    
        @Override
        public void run() {
            for(int i = 0; i < 100; i++){
                System.out.println("join...."+i);
            }
        }
    }
    View Code

        结果:join执行完了再走

        

      yield() static线程让步,当前线程让出CPU——高风亮节,牺牲自己

        示例:

    package com.jiangbei.thread;
    
    /**
     * 测试Thread的常用方法
     *
     * @author Administrator
     * @date 2017/8/30
     **/
    public class ThreadMethod extends Thread{
        public static void main(String[] args) throws InterruptedException {
            ThreadMethod tm = new ThreadMethod();
            Thread t = new Thread(tm);
            t.start();
            for(int i = 0; i < 100; i++){
                if (i%20 == 0) {    //被20整除
                    Thread.yield();//暂停本线程,也就是main
                }
                System.out.println("main...."+i);
            }
        }
    
        @Override
        public void run() {
            for(int i = 0; i < 100; i++){
                System.out.println("yield...."+i);
            }
        }
    }
    View Code

        结果:没有真正的暂停,如果CPU又调度掉了它,那么它将继续执行,取决于CPU的算法

        

      sleep() static线程休眠,暂停当前线程,休眠多少秒。——抱着锁睡觉(排它锁)

               用于倒计时与模拟延时等

        示例:模拟网络延时的时候可能会存在数据不准确(资源冲突)

     public static void main(String[] args) throws InterruptedException {
            int num = 10;
            while (true) {
                System.out.println(num--);
                Thread.sleep(1000); //睡眠1秒,主线程睡眠
                if(num <= 0){
                    break;
                }
            }
        }
    View Code

      wait()线程等待,当前线程进入wait pool 线程等待池。放弃锁等待

      notify()/notifyAll():线程唤醒,唤醒等待池中的一个/所有线程

      currentThread()  getName()  isAlive()   NORM_PRIORITY   setPriority(int newPriority) 

      详细介绍,请参见 JDK-API

    5.线程同步

      多线程的访问的情况下,由于出现多条路径,就可能会出现访问同一份资源的线程安全问题——对共享资源的访问控制

     以之前的12306抢票为例:

        线程不安全

    package com.jiangbei.thread.web12306;
    
    /**
     * 模拟Web12306抢票系统
     * @author Administrator
     * @date 2017/8/30
     **/
    public class Web12306 implements Runnable{
        private int num = 50;
        private boolean flag = true;
        @Override
        public void run() {
            while (flag) {
               test1();
               // test2();
            }
        }
    
        //测试方法1:线程不安全
        public void test1() {
            if(num <= 0){
                flag = false;
                return;
            }
            try {
                Thread.sleep(200);//睡眠1秒,存在线程不安全
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
        }
        //测试方法2:线程安全
        public synchronized void test2 (){
            if(num <= 0){
                flag = false;
                return;
            }
            try {
                Thread.sleep(200);//睡眠1秒,存在线程不安全
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
        }
        public static void main(String[] args) {
            //创建真实对象
            Web12306 web12306 = new Web12306();
            //创建代理对象
            Thread t1 = new Thread(web12306,"黄牛甲");
            Thread t2 = new Thread(web12306,"黄牛乙");
            Thread t3 = new Thread(web12306,"黄牛丙");
            //启动线程
            t1.start();
            t2.start();
            t3.start();
    
        }
    }
    View Code

        结果:3个代理黄牛,导致结果不准确!

        

        线程安全同步方法 :运行上述代码test2()

        结果:加入同步方法,牺牲了效率,换来了安全

        

        同步块:只能锁包装类(例如this),同步块必须谨慎选择同步范围

     //测试方法3:利用同步块
        public void test3 (){
            synchronized(this){
                if(num <= 0){
                    flag = false;
                    return;
                }
                try {
                    Thread.sleep(200);//睡眠1秒,存在线程不安全
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"抢到了"+num--);
            }
        }
    View Code

        典型应用:单例模式-doublechecking——更多的介绍,将会在设计模式篇进一步讲解

      懒汉式:

    package com.jiangbei.thread;
    
    /**
     * 线程同步Demo,单例模式运用
     * 作者: Administrator
     * 日期: 2017/8/30
     **/
    public class SynDemo {
        public static void main(String[] args) {
            Jvm jvm1 = Jvm.getIntance();
            Jvm jvm2 = Jvm.getIntance();
            System.out.println(jvm1);
            System.out.println(jvm2);
        }
    }
    /*
    单例模式-懒汉式
        1.构造器私有化,避免外部创建对象
        2.声明私有静态变量
        3.创建公有静态方法,对外提供对静态变量的访问
     */
    class Jvm {
    
        //声明私有静态变量
        private static Jvm instance = null;
        //构造器私有化
        private Jvm(){
    
        }
        //对外提供公有静态方法用于获取
        public static Jvm getIntance() {
            if (null == instance) {
                instance = new Jvm();
            }
            return instance;
        }
    }
    View Code

      单线程下,完全没有问题:

      

      稍微修改单例的方法,模拟延时;此时多线程下便会发生线程安全问题,导致单例失效!

      //对外提供公有静态方法用于获取
        public static Jvm getIntance() {
            if (null == instance) {
                try {
                    Thread.sleep(1000);//模拟延时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                instance = new Jvm();
            }
            return instance;
        }
    View Code

      //可以创建一个线程类,定义方法获取实例,创建两个线程对象进行访问,便可以发现问题,当某个线程延时时,可能另外一个线程也通过了空判断,导致两次创建对象!

       最简单的处理方式是改造为同步方法:

      //对外提供公有静态方法用于获取
        public static synchronized Jvm getIntance() {
            if (null == instance) {
                try {
                    Thread.sleep(1000);//模拟延时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                instance = new Jvm();
            }
            return instance;
        }
    View Code

      我们这里来使用同步块,由于静态方法里面没有 this,我们只能锁字节码,类.class:

       //对外提供公有静态方法用于获取
        public static Jvm getIntance() {
            synchronized(Jvm.class) {
                if (null == instance) {
                    try {
                        Thread.sleep(1000);//模拟延时
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    instance = new Jvm();
                }
                return instance;
            }
        }
    View Code

      由于上面锁字节码信息任何时候都需要等(即使存在对象),效率非常低,我们加一重判断:

      public static Jvm getIntance() {
            if(null == instance) {
                synchronized (Jvm.class) {
                    if (null == instance) {
                        try {
                            Thread.sleep(1000);//模拟延时
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        instance = new Jvm();
                    }
    
                }
            }
            return instance;
        }
    View Code

      //我们对段代码稍作分析:假设a,b两个线程同时进来,假设都为空,都经过了第一重检查,此时假设a先进入锁区域,创建了对象,释放锁离开

      然后b进来,再次检查时发现对象不为空,对象已经创建,直接离开。

      这就是double check:

    public Resource getResource() {  
      if (resource == null) {   
        synchronized(this){   
          if (resource==null) {  
            resource = new Resource();    
          }     
        }    
      }  
      return resource;  
    }  

      对象互斥锁:synchronized(obj) 保证同一时刻只能有一个线程访问该对象,从而保证了共享数据的操作完整性。

      同步一般分为两类(注意死锁问题):

          同步方法

            public synchronized void fun1(){}

          同步方法拿的对象的锁,即一个线程进入同步方法A后,其它线程不能进入同步方法B

          同步块

            synchronized(this){}

      解决线程死锁的问题最好只锁定一个对象,不要同时锁定两个对象

    经典的生产者消费者模型,请参见:http://www.cnblogs.com/myhomepages/archive/2016/11/03/6028663.html

    二、白鹤亮翅——高级线程类与关键字

     1.volatile

      用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。

      基本原理:

        多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。完整的过程应该是:

    read and load 从主存复制变量到当前工作内存
    use and assign  执行代码,改变共享变量值 
    store and write 用工作内存数据刷新主存相关内容

        本质上,volatile就是不去缓存,直接取值。

      详细的底层原理解析,请参见http://www.importnew.com/18126.html

      注意:

        volatile并不是原子性的,很容易被误用,关于这方面的讲解,请参见http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html

      2.ThreadLocal

       ThreadLocal类,为每个线程维护一个独立的变量副本(应当修正) ——线程本地变量

        用处:保存线程的独立变量。对一个线程类(继承自Thread)
      当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。
        深入浅出的讲解,请参见:http://www.iteye.com/topic/103804                
                作者:海子
                出处:http://www.cnblogs.com/dolphin0520/
        几个面试题,请参加:http://www.cnblogs.com/dolphin0520/p/3932934.html
  • 相关阅读:
    go语言goroutine
    go语言接口
    go语言的map
    go语言切片
    go语言数组
    django的信号机制
    python redis 实现简单的消息订阅
    scrapy中使用selenium来爬取页面
    尝试用tornado部署django
    控制台输出太多导致项目启动过慢
  • 原文地址:https://www.cnblogs.com/jiangbei/p/6664555.html
Copyright © 2011-2022 走看看