zoukankan      html  css  js  c++  java
  • 线程与进程

    什么是进程:

        所谓进程(process)就是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。进程中所包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。线程只能归属于一个进程并且它只能访问该进程所拥有的资源。当操作系统创建一个进程后,该进程会自动申请一个名为主线程或首要线程的线程。

    什么是线程:

        一个线程是进程的一个顺序执行流。同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。一个进程中可以包含多个线程。线程是程序执行的最小程序单元。

    进程与线程的区别 :

        一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存。线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个执行控制。从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。

    什么时候使用到线程:

        线程通常用于在一个程序中需要同时完成多个任务的情况。例如在游戏中我们会听到某些背景音乐,某个角色在移动,出现某些绚丽的动画效果等。多个线程在一起执行,进而就是我们说的多线程。

    线程的优先级:

        每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。 

        线程的API 中展示的调用setproperty方法来设置线程的优先级:

        线程优先级默认值 5,从1到10逐渐提高 .

    线程的生命周期:

      上面的图可以看出,线程的大概生命周期同时对于几种状态,哪几种呢?

      1. 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t =new MyThread();
      2. 就绪状态(Runnable):当调用线程对象的start()方法线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了start()此线程立即就会执行。就绪状态是线程进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中。
      3. 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。
      4. 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。

      5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

      根据线程阻塞产生的原因不同,我们又可以将阻塞的状态分为三种:

      1. 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态
      2. 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态
      3. 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。


      

    说了那么多 ,那 我们如何产生一个线程呢?

            线程的产生有两种方法:

       1.  继承线程 Thread 类 

    public class ThreadB extends Thread{
    @Override
        public void run() {
        while(true){
        System.out.println("this is B");
        try {
            Thread.sleep(100);
    } catch (InterruptedException e) {
        e.printStackTrace();
                }
            }
        }
    }                

         2 . 实现Runnable 接口 

     1 public class ThreadB implements Runnable{
     2     @Override
     3     public void run() {
     4     while(true){
     5         System.out.println("this is B");
     6 try {
     7         Thread.sleep(100);
     8 } catch (InterruptedException e) {
     9         e.printStackTrace();
    10             }
    11            }
    12     }
    13 }                            

    线程的开启方式:

    public static void main(String[] args){   
    //继承Thread类的方法,直接开启线程 new ThreadA().start(); //实现Runnable的方法。new 一个线程类对象,传入new 的线程对象 再开启线程
    通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
      new Thread(new ThreadA()).start(); }

        这里要注意了:  因为 Java 是单继承的,在我们日常开发中,一般会选择实现Runnable 接口的方式去开启线程, 避免无法继承其他类的情况。

      线程安全问题:

      多个线程访问出现的延迟和线程的随机性,会导致我们线程出现一系列问题,在理想情况下,线程安全问题不会存在,不容易出现,但是一旦出现,对我们软件的影响是非常大的。当多个线程并发读写同一个临界资源比如 多线程共享的实例变量,以及多线程共享的静态公共变量等 的时候会发生“线程并发安全问题”,由此我们产生了同步(synchronized
    操作。

        同步操作有几种,介绍几种常用的方法:

         1 . 同步方法

         2.  同步代码块

         3.  重入锁 

     同步的前提是 :  同步需要两个或两个以上的线程,多个线程使用的是同一个锁。 不满足这两个,就不能称为同步。

     当然同步也不是万能的 ,它也有弊端: 比如当线程非常多的时候,因为每个线程都会去判断同步上的锁,这是很耗费资源的, 无形中会降低程序的执行效率。

          Synchronized 用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。每个Java对象都可以用做一个实现同步的锁,线程进入同步代码块之前会自动获得锁,并且在退出同步代码块时释放锁。

           同步方法:synchronized 加在方法上面。

    1 public synchronized 返回值类型 方法名(参数列表){
    2              // 逻辑代码
    3 }    

        同步代码块:synchronized 加在方法体之中,需要执行同步的代码块前面。

    Synchronized(同步监视器){
            //需要进行同步的逻辑代码块
    }

      重入锁 :

      Java中的锁框架指的是java.util.concurrent.locks这个包里的,不同于对象的内置加锁同步以及java.lang.Object的等待/通知机制,包含锁框架的并发工具通过轮询锁、限时等待及其他方式改善了这种机制。 这里的锁指的是接口Lock。

      类ReentranLock实现了接口Lock,这是一个可重入的互斥锁,这个锁是和一个持有量相关联的。当一条线程持有这个锁并且调用lock()、lockUnitinterruptibly()或者任意一个tryLock()方法重新获取锁,这个持有量就递增1。当线程调用unlock()方法,持有量就递减1。当持有量为0时,锁就会被释放。

    1 Lock  lock = new ReentrantLock();// 实例化接口
    2         lock.lock();
    3         try {
    4             //使用锁获取到的资源
    5         } catch (Exception e) {
    6             //异常处理
    7         } finally {
    8         lock.unlock();
    9         }

       既然说到了锁 。我们就来谈谈 基本锁概念;

        内置锁:

    •   java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
    •   java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。

       对象锁与类锁:  

    java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,

    •   对象锁是用于对象实例方法,或者一个对象实例上的
    •   类锁是用于类的静态方法或者一个类的class对象上的。

      我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。

       守护线程: 

      用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当VM检测仅剩一个守护线程,而用户线程都已经退出运行时,VM就会退出,因为没有如果没有了被守护这,也就没有继续运行程序的必要了。如果有非守护线程仍然存活,VM就不会退出。

      守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。

      虽然守护线程可能非常有用,但必须小心确保其他所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。 因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。

      与用户线程的不同点:

       当进程结束时,所有正在运行的守护线程都会被强制结束。而一个进程中所有前台线程都结束,进程就会结束

     另外有几点需要注意:

    1、setDaemon(true)必须在调用线程的start()方法之前设置,否则会抛出IllegalThreadStateException异常。
    2、在守护线程中产生的新线程也是守护线程。
    3、不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。

       试想既然我们一个进程有多个线程组成,当我们组成执行这个程序时, 多个进程协调执行,如果进程需要其他进程的执行结果或者参数怎么办呢?  这就涉及到我们进程间的通讯了。

       进程间的通讯有以下几种: 

    •         同步     即 多个线程通过synchronized关键字这种方式来实现线程间的通信。

        依旧 举个栗子:

     1 public class Demo {
     2 
     3     synchronized public void methodA() {
     4         //逻辑代码块
     5     }
     6 
     7     synchronized public void methodB() {
     8         //逻辑代码块
     9     }
    10 }
    11 
    12 public class ThreadA extends Thread {
    13 
    14     private Demo demo;
    15     //构造方法
    16     @Override
    17     public void run() {
    18         super.run();
    19         demo.methodA();
    20     }
    21 }
    22 
    23 public class ThreadB extends Thread {
    24 
    25     private Demo demo;
    26     //构造方法
    27     @Override
    28     public void run() {
    29         super.run();
    30         demo.methodB();
    31     }
    32 }
    33 
    34 public class Run {
    35     public static void main(String[] args) {
    36         Demo demo = new Demo();
    37 
    38         //线程A与线程B 持有的是同一个对象:demo
    39         ThreadA one = new ThreadA(object);
    40         ThreadB two = new ThreadB(object);
    41         one.start();
    42         two.start();
    43     }
    44 }           

        线程A和线程B持有同一个Demo类的对象demo,这两个线程需要调用不同的方法,但是它们是同步执行的,所以:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了通信。

      这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(即对象,便获得了访问权限),谁就可以执行。

    •    while轮询的方式  不停地通过while语句询问条件是否成立 

       还是举个栗子:

    public class Test {
    
        private List<String> list = new ArrayList<String>();
    
        public void add() {
            list.add("elements");
        }
    
        public int size() {
            return list.size();
        }
    }
    
    public class ThreadA extends Thread {
        
            private Test test;
    
            public ThreadA(Test test) {
                super();
                this.test = test;
            }
            @Override
            public void run() {
                try {
                    for (int i = 0; i < 10; i++) {
                        test.add();
                        System.out.println(ThreadA.currentThread()+ "添加了" + (i + 1) + "个元素");
                        Thread.sleep(1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        
    }
    
    public class ThreadB extends Thread {
         private Test test;
    
            public ThreadB(Test test) {
                super();
                this.test = test;
            }
    
            @Override
            public void run() {
                try {
                    while (true) {
                        if (test.size() == 5) {
                            System.out.println("==5, 线程b准备退出了");
                            throw new InterruptedException();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        
     }
    
    
    public class Run{
        
          public static void main(String[] args) {
                Test service = new Test();
    
                ThreadA a = new ThreadA(service);
                a.setName("A");
                a.start();
    
                ThreadB b = new ThreadB(service);
                b.setName("B");
                b.start();
            }
    
    }

    线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件(list.size()==5)是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源。之所以说它浪费资源,是因为JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。就类似于现实生活中,某个人一直看着手机屏幕是否有电话来了,而不是: 在干别的事情,当有电话来时,响铃通知TA电话来了。

      这种方式还存在另外一个问题:

    轮询的条件的可见性问题,关于内存可见性问题。线程都是先把变量读取到本地线程栈空间,然后再去再去修改的本地变量。因此,如果线程B每次都在取本地的 条件变量,那么尽管另外一个线程已经改变了轮询的条件,它也察觉不到,这样也会造成死循环。

    •   wait/notify机制 

    import java.util.ArrayList;
    import java.util.List;
    
    public class MyList {
    
        private static List<String> list = new ArrayList<String>();
    
        public static void add() {
            list.add("anyString");
        }
    
        public static int size() {
            return list.size();
        }
    }
    
    
    public class ThreadA extends Thread {
    
        private Object lock;
    
        public ThreadA(Object lock) {
            super();
            this.lock = lock;
        }
    
        @Override
        public void run() {
            try {
                synchronized (lock) {
                    if (MyList.size() != 5) {
                        System.out.println("wait begin "
                                + System.currentTimeMillis());
                        lock.wait();
                        System.out.println("wait end  "
                                + System.currentTimeMillis());
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    public class ThreadB extends Thread {
        private Object lock;
    
        public ThreadB(Object lock) {
            super();
            this.lock = lock;
        }
    
        @Override
        public void run() {
            try {
                synchronized (lock) {
                    for (int i = 0; i < 10; i++) {
                        MyList.add();
                        if (MyList.size() == 5) {
                            lock.notify();
                            System.out.println("已经发出了通知");
                        }
                        System.out.println("添加了" + (i + 1) + "个元素!");
                        Thread.sleep(1000);
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public class Run {
    
        public static void main(String[] args) {
    
            try {
                Object lock = new Object();
    
                ThreadA a = new ThreadA(lock);
                a.start();
    
                Thread.sleep(50);
    
                ThreadB b = new ThreadB(lock);
                b.start();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

        线程A要等待某个条件满足时(list.size()==5),才执行操作。线程B则向list中添加元素,改变list 的size。

         A,B之间如何通信的呢?

      也就是说,线程A如何知道 list.size() 已经为5了呢? 这里用到了Object类的 wait() 和 notify() 方法。 当条件未满足时(list.size() !=5),线程A调用wait() 放弃CPU,并进入阻塞状态。---不像②while轮询那样占用CPU 当条件满足时,线程B调用 notify()通知 线程A,所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。 这种方式的一个好处就是CPU的利用率提高了。 但是也有一些缺点:比如,线程B先执行,一下子添加了5个元素并调用了notify()发送了通知,而此时线程A还执行;当线程A执行并调用wait()时,那它永远就不可能被唤醒了。因为,线程B已经发了通知了,以后不再发通知了。这说明:通知过早,会打乱程序的执行逻辑。

    •   管道通信就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信

        具体就不介绍了。分布式系统中说的两种通信机制:共享内存机制和消息通信机制。感觉前面的①中的synchronized关键字和②中的while轮询 “属于” 共享内存机制,由于是轮询的条件使用了volatile关键字修饰时,这就表示它们通过判断这个“共享的条件变量“是否改变了,来实现进程间的交流。 而管道通信,更像消息传递机制,也就是说:通过管道,将一个线程中的消息发送给另一个。

  • 相关阅读:
    Iphone开发-NSdata 与 NSString,Byte数组,UIImage 的相互转换
    Iphone访问WCF服务 之 ASIHTTPRequest
    TP6 实现上传文件
    页面自适应纯CSS,使用rem单位
    jQuery解决高度统一问题
    dedecms Ajax异步获取文章列表
    手机端页面自适应解决方案—rem布局
    mysql中bigint、int、mediumint、smallint 和 tinyint的取值范围
    修改PHP 上传文件大小限制
    WampServer下修改和重置MySQL密码
  • 原文地址:https://www.cnblogs.com/thelovelybugfly/p/9647910.html
Copyright © 2011-2022 走看看