线程

    进程:当前正在运行的程序,一个应用程序在内存中的执行区域
    线程:进程中的一个执行控制单元,执行路径
    
    一个进程可以有一个线程,也可以有多个线程
    
    单线程:安全性高,但是效率低
    多线程:安全性低,效率高
    
    多线程案例: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、等待唤醒机制

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

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

posted @ 2019-06-25 23:12  勤奋的园  阅读(143)  评论(0编辑  收藏  举报