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

    一、起手式——基本概念

    1.什么叫线程

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

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

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

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

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

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

    2.如何创建线程

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

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

    package com.test.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+"步");
            }
        }
    }

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

    package com.test.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>>>>>>");
            }
        }
    }

    运行结果:

      

    为什么运行结果是这样的呢?
        对于这个疑问,你需要知道main方法,也就是主方法体,它是一个优先的主线程,而兔子和乌龟是你定义的两个线程,所以根据CPU的调度规则,就会出现这样的运行结果。
    

      

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

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

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

      实现接口的线程类:

    package com.test.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");
            }
    
        }
    }

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

    package com.test.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");
            }
    
        }
    }

    结果:

      

       匿名内部类的写法:

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

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

    package com.test.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();
    
        }
    }

    结果:

      

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

    3.线程的五种状态

      

      详细状态:

      

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

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

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

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

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

        通过外部干预实例:

    package com.test.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类的线程终止【死亡】 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; } }

    4.线程常用方法

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

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

        示例:

    package com.test.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);
            }
        }
    }

    结果:join执行完了再走

        

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

        示例:

    package com.test.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);
            }
        }
    }

    结果:没有真正的暂停,如果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;
                }
            }
        }

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

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

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

      详细介绍,请参见 JDK-API

    5.线程同步

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

     以之前的12306抢票为例:

        线程不安全

    package com.test.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();
    
        }
    }

    结果: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--);
            }
        }

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

      懒汉式:

    package com.test.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;
        }
    }

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

      

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

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

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

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

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

    我们这里来使用同步块,由于静态方法里面没有 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;
            }
        }

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

    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;
        }

    //我们对段代码稍作分析:假设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
  • 相关阅读:
    1014 Waiting in Line (30)(30 point(s))
    1013 Battle Over Cities (25)(25 point(s))
    1012 The Best Rank (25)(25 point(s))
    1011 World Cup Betting (20)(20 point(s))
    1010 Radix (25)(25 point(s))
    1009 Product of Polynomials (25)(25 point(s))
    1008 Elevator (20)(20 point(s))
    1007 Maximum Subsequence Sum (25)(25 point(s))
    1006 Sign In and Sign Out (25)(25 point(s))
    1005 Spell It Right (20)(20 point(s))
  • 原文地址:https://www.cnblogs.com/zhuangwei1015/p/10009840.html
Copyright © 2011-2022 走看看