zoukankan      html  css  js  c++  java
  • 线程

        进程:当前正在运行的程序,一个应用程序在内存中的执行区域
        线程:进程中的一个执行控制单元,执行路径
        
        一个进程可以有一个线程,也可以有多个线程
        
        单线程:安全性高,但是效率低
        多线程:安全性低,效率高
        
        多线程案例:360,迅雷等

    1、程序运行原理

    分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间

    抢占式调度:优先让优先级高的线程使用cpu,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

    抢占式调度

      大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,“感觉这些软件好像在同一时刻运行着”。

    实际上,cpu(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于cpu的一个核而言,某个时刻,只能执行一个线程,而cpu的在多个线程间切换速度相对我们感觉要快,看上去就是在同一时刻运行。

      其实,多线程并不能提高程序的运行速度,但能够提高程序运行速率,让cpu的使用率更高。

    2、线程的使用

    在Java中创建线程的方法有两种
    方法一:
        继承java.lang.Thread类

    继承Thread类
        自定义一个类继承Thread类
        重写Thread的run方法
        创建一个该类的对象
        使用该类对象调用start方法开启线程

    package LESSON13;
    public class MyThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(  getName()+":"+i);//调用父类方法getName

           
    //System.out.println("i"+i+"当前线程:"+Thread.currentThread().getName()); } } }
    package LESSON13;
    
    public class ThreadDemo1 {
        public static void main(String[] args) {
            MyThread mt1 = new MyThread();
            mt1.start();//使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
            mt1.setName("张三");
            
            MyThread mt2 = new MyThread();
            mt2.start();
            mt2.setName("老王");

         //MyThread myThread01 = new MyThread("红色线程");//也可创建对象时直接赋予名称,输出时使用Thread.currentThread().getName()得到名称
            //MyThread myThread02 = new MyThread("蓝色线程"); } }



    方法二:
        实现java.lang.Runnable接口

    好处:面向接口编程,低耦合
    实现Runnable接口
        自定义一个类实现Runnable接口
        重写Thread的run方法
        创建一个该类的对象
        使用Thread(Runnable target)构造方法创建Thread类对象
        使用Thread类对象调用start方法开启线程

    package LESSON13;
    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(i); 

           //System.out.println("i"+i+"线程名称:"+Thread.currentThread().getName()); } } }
    package LESSON13;
    public class RunnableDemo {
        public static void main(String[] args) {
            MyRunnable mr = new MyRunnable();//MyRunnable实现Runnable接口,也属于Runnable类型
            Thread t = new Thread(mr);//使用Thread(Runnable target)构造方法创建Thread类对象
            t.start();

        //MyRunnable mr = new MyRunnable();
        //Thread t = new Thread(mr,"黄色线程");//创建对象时直接赋予名称
        //t.start();
    } }

    3、使用匿名内部类

      使用线程的内匿名内部类,可以方便的实现每个线程执行不同的线程任务操作。

    方式一:创建线程对象时,直接重写Thread类中的run方法

    package com.zy.dmeo03;
    
    public class Demo03 {
    
        public static void main(String[] args) {
            // 使用匿名内部类的方式()一般都有重写的发生,简化线程开发步骤
            //创建线程,和指定任务一气呵成
        
            new Thread(){//发生了重写   说明一定有一个类重写了Thread中的run方法   只是这个类没有名字(匿名)
                @Override//重写
                public void run() {
                    for (int a = 0; a < 200; a++) {
                        System.out.println("a"+a);
                    }
                }
            }.start();//调用start方法说明产生了一个对象(只有对象才能调用方法)
            //实际上有一个类重写了Thread中的run方法,并且产生了一个匿名子类对象,然后调用start方法
        
            
            //1为什么匿名内部类------class Demo03中还有一个内部类,只是没有名字
            //重写:1子类重写父类方法,2实现类重写接口方法
            
            
            //-----------------------------------------------
            
            new Thread(){
                @Override
                public void run() {
                    for (int b = 0; b < 200; b++) {
                        System.out.println("b"+b);
                    }
                }
            }.start();
            
            //以上代码创建了两个线程,并开启了两个线程
            
            
    
        }
    
    }

    方式2:使用匿名内部类的方式实现Runnable接口,重写Runnable接口中的run方法

    package com.zy.dmeo03;
    
    public class Demo04 {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            new Thread(
                    
                    //new 一个接口
                    new Runnable() {//Runnable实现类(匿名)
                        
                        @Override//重写
                        public void run() {
                            for (int a = 0; a < 200; a++) {
                                System.out.println("a"+a);
                            }
                            
                        }
                    }
                    //整体就是一个匿名的runnbale实现类对象
            
                    
                    
                    ).start();
            //new Thread(runnbale实现类对象).start
        
            
            new Thread(
                    new Runnable() {
                        
                        @Override
                        public void run() {
                            for (int b = 0; b < 200; b++) {
                                System.out.println("b"+b);
                            }
                            
                        }
                    }
                    
                    
                    ).start();
    
        }
    
    }

    4、线程池

      线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去l频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

       在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。

      线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

    package com.zy.demo04;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class MyPool {
    
        public static void main(String[] args) throws Exception {
            // 使用线程池,创建和管理线程------提高效率
            //使用Executors工厂,创建线程池对象,保留3预备线程
            ExecutorService pool = Executors.newFixedThreadPool(3);
            //池对象,提交任务
            ExecutorService pool2 = pool;
            pool2.submit(//执行该任务会从该线程池中取出一个空闲的线程
                    new Runnable() {
                        
                        @Override
                        public void run() {
                            System.out.println("我的任务1");
                            
                        }
                    }
                    );
            pool2.submit(//执行该任务会从该线程池中取出一个空闲的线程
                    new Runnable() {
                        
                        @Override
                        public void run() {
                            System.out.println("我的任务2");
                            
                        }
                    }
                    );
            pool2.submit(//执行该任务会从该线程池中取出一个空闲的线程
                    new Runnable() {
                        
                        @Override
                        public void run() {
                            System.out.println("我的任务3");
                            
                        }
                    }
                    );        
            //pool.shutdown();//结束  线程池一般处于等待执行状态,在程序中一般不需要关闭
        
            
        Future<String> ft = pool.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            System.out.println("任务5");
            return "奖励一瓶矿泉水";
        }
            
            
        });
        System.out.println(ft.get());
        
        
        }
    
    } 

    5、线程原理

    在某一个时间段内,多个线程在CPU上交替运行,给人的感觉是“同时”执行。
    实际上,在某一个时间点上,只能在CPU上运行一个线程。
    原理:
        1.线程启动之后,会先到CPU调度队列中排队,CPU会计算各线程优先级确定队里线程的先后顺序,挑选优先级最高的线程运行。
        2.CPU运行某个线程,会给该线程分配运行的时间片,如果时间片到期,该线程自动退出CPU,然后重新排队。
        3.该线程再一次运行在CPU上,接着上一次运行的位置继续向后运行,直到运行结束,线程死亡。

    sleep方法

    void sleep(long time)方法用于使当前线程休眠指定的毫秒数
    特点:
        让当前线程退出CPU的运行,处于阻塞(休眠)状态;一旦进入阻塞状态不会排队,但是该线程所持有的对象的锁不会释放;休眠一段时间后,结束阻塞,重新排队。

    join方法

    利用sleep方法对线程的控制是非常不精确的,然而 join方法可以精确控制线程。
     void join() 等待该线程终止后再运行
     void join(long millis) 等待线程运行多少毫秒后再运行
    特点:
        让当前线程退出CPU的运行,处于阻塞状态;直到调用的线程执行完毕后结束阻塞,重新排队。如果当前线程中调用了另外一个线程的 join方法,当前线程会立即阻塞,直到另外一个线程运行完成。
    问题:
        如果2个线程彼此调用对方的join方法,会导致程序无法进行。
        解决办法:throws  InterruptedException

    6、线程同步

      如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

      

     

      其实,线程安全问题都是由全局变量即静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时操作写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

    产生数据不一致的原因
        多个线程并发访问了同一个对象,如果破坏了不可分割的操作, 从而就会造成数据不一致,被多线程并发访问时如果一个对象有可能出现数据不一致的问题,那么这个对象称为线程不安全的对象。
    如何解决多线程并发访问的问题

    方式1:同步代码块
          synchronized(锁对象){
                  可能会产生线程安全问题的代码
            }

    同步代码块中的锁对象可以使任意的对象;但多个线程是,要是用同一个锁对象才能够保证线程安全。

    package com.zy.demo05;
    
    public class Demo05 {
    
        public static void main(String[] args) throws Exception {
            for (int a = 0; a < 200; a++) {
                Thread.sleep(1000);//毫秒//休眠  该线程在改时间内不抢夺cpu的使用权
                System.out.println("*");
            }
    
        }
    
    }

    方式2:同步方法

     public synchronized void method(){

    可能会产生线程安全问题的代码

    }

    同步方法中的锁对象是this

    package com.zy.demo06;
    
    public class Demo06 {
        public static synchronized void show(){//保护方法(线程同步)
            
            for (int a = 0; a < 200; a++) {
                
            }
        }//在该方法运行期间不会被抢走,方法结束后才允许别的线程抢夺
    
    }

     静态同步方法:在方法声明上加上static synchronized

    public static synchronized void method(){

    可能会产生线程安全问题的代码

    }

    静态同步方法中的锁对象是类名.class

    7、死锁

      同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一众现象:程序出现无线等待,这种现象我们称为死锁。这种情况能避免就避免掉。

    synchronized(A锁){

      synchronized(B锁){

    }

    }

    8、等待唤醒机制

        在开始讲解等待唤醒机制之前,有必要搞清一个概念——线程之间的通信:多个线程在处理同一资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即——等待唤醒机制。

      其实,所谓唤醒的意思就是让线程池的线程具备执行资格。必须注意的是,这些方法都是在同步中才能生效。同时这些方法在使用时必须标明所属锁,这样才可以明确这些方法操作的到底是那个锁上的线程。

  • 相关阅读:
    naotu.baidu.com 非常棒的脑图在线工具
    编程常用英语词汇大全
    我的总结,编程人生
    排序箭头,升序,降序简单实现
    jquery网页倒计时效果,秒杀,限时抢购!
    echarts入门,5分钟上手写ECharts的第一个图表
    严谨的程序案例Api
    github Travis CI 持续集成
    Cmake使用
    Linxu安装Lamp环境
  • 原文地址:https://www.cnblogs.com/qfdy123/p/11087299.html
Copyright © 2011-2022 走看看