• 多线程(1)


     


    1、进程、线程、多线程

    1.1.什么是进程?

    电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。比如下图中的QQ、酷狗播放器、电脑管家等等。在这里插入图片描述

    1.2.什么是线程?

    进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。

    那什么是多线程?提到多线程这里要说两个概念,就是串行和并行,搞清楚这个,我们才能更好地理解多线程。

    所谓串行,其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子:当我们下载多个文件时,在串行中它是按照一定的顺序去进行下载的,也就是说,必须等下载完A之后才能开始下载B,它们在时间上是不可能发生重叠的。
    在这里插入图片描述
    并行:下载多个文件,开启多条线程,多个文件同时进行下载,这里是严格意义上的,在同一时刻发生的,并行在时间上是重叠的。
    在这里插入图片描述
    了解了这两个概念之后,我们再来说说什么是多线程。举个例子,我们打开腾讯管家,腾讯管家本身就是一个程序,也就是说它就是一个进程,它里面有很多的功能,我们可以看下图,能查杀病毒、清理垃圾、电脑加速等众多功能。

    1.3.多线程

    按照单线程来说,无论你想要清理垃圾、还是要病毒查杀,那么你必须先做完其中的一件事,才能做下一件事,这里面是有一个执行顺序的。

    如果是多线程的话,我们其实在清理垃圾的时候,还可以进行查杀病毒、电脑加速等等其他的操作,这个是严格意义上的同一时刻发生的,没有执行上的先后顺序。

     以上就是,一个进程运行时产生了多个线程。

    2、什么是线程安全?

    当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
    既然是线程安全问题,那么毫无疑问,所有的隐患都是在多个线程访问的情况下产生的,也就是我们要确保在多条线程访问的时候,我们的程序还能按照我们预期的行为去执行,我们看一下下面的代码。

    1 Integer count = 0;
    2 public void getCount() {
    3        count ++;
    4        System.out.println(count);
    5  }

    很简单的一段代码,下面我们就来统计一下这个方法的访问次数,多个线程同时访问会不会出现什么问题,我开启的3条线程,每个线程循环10次,得到以下结果:
    在这里插入图片描述

    我们可以看到,这里出现了两个26,出现这种情况显然表明这个方法根本就不是线程安全的,出现这种问题的原因有很多。

    最常见的一种,就是我们A线程在进入方法后,拿到了count的值,刚把这个值读取出来,还没有改变count的值的时候,结果线程B也进来的,那么导致线程A和线程B拿到的count值是一样的。

    那么由此我们可以了解到,这确实不是一个线程安全的类,因为他们都需要操作这个共享的变量。其实要对线程安全问题给出一个明确的定义,还是蛮复杂的,我们根据我们这个程序来总结下什么是线程安全。

    当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。

    搞清楚了什么是线程安全,接下来我们看看Java中确保线程安全最常用的两种方式。先来看段代码。

    1 public void threadMethod(int j) {
    2 
    3     int i = 1;
    4 
    5     j = j + i;
    6 }

    大家觉得这段代码是线程安全的吗?

    毫无疑问,它绝对是线程安全的,我们来分析一下,为什么它是线程安全的?

    我们可以看到这段代码是没有任何状态的,就是说我们这段代码,不包含任何的作用域,也没有去引用其他类中的域进行引用,它所执行的作用范围与执行结果只存在它这条线程的局部变量中,并且只能由正在执行的线程进行访问。当前线程的访问,不会对另一个访问同一个方法的线程造成任何的影响。

    两个线程同时访问这个方法,因为没有共享的数据,所以他们之间的行为,并不会影响其他线程的操作和结果,所以说无状态的对象,也是线程安全的

    添加一个状态呢?

    如果我们给这段代码添加一个状态,添加一个count,来记录这个方法并命中的次数,每请求一次count+1,那么这个时候这个线程还是安全的吗?

     1 public class ThreadDemo {
     2 
     3    int count = 0; // 记录方法的命中次数
     4 
     5    public void threadMethod(int j) {
     6 
     7        count++ ;
     8 
     9        int i = 1;
    10 
    11        j = j + i;
    12    }
    13 }

    明显已经不是了,单线程运行起来确实是没有任何问题的,但是当出现多条线程并发访问这个方法的时候,问题就出现了,我们先来分析下count+1这个操作。

    进入这个方法之后首先要读取count的值,然后修改count的值,最后才把这把值赋值给count,总共包含了三步过程:“读取”一>“修改”一>“赋值”,既然这个过程是分步的,那么我们先来看下面这张图,看看你能不能看出问题:
    在这里插入图片描述
    可以发现,count的值并不是正确的结果,当线程A读取到count的值,但是还没有进行修改的时候,线程B已经进来了,然后线程B读取到的还是count为1的值,正因为如此所以我们的count值已经出现了偏差,那么这样的程序放在我们的代码中,是存在很多的隐患的。

    3.实现多线程方式 

    1.继承Thread类 

    2.实现Runnable接口

    3.实现Callable接口,重写call()方法。

    3.1.继承Thread类

    实现步骤
    1.    定义一个类MyThread继承Thread类
    2. 在MyThread类中重写run()方法
    3.  创建MyThread类的对象
    4. 启动线程
    代码演示:

     

    3.2.实现Runnable接口

    相比继承Thread类,实现Runnable接口的好处:
    • 避免了Java单继承的局限性
    • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现

    3.3.实现Callable接口,重写call()方法

    public class Demo7 {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            ExecutorService threadPool = Executors.newSingleThreadExecutor();
    
            //启动线程
            Future<String> future = threadPool.submit(new myCallable());
    
            try{
                System.out.println("waiting the thread to finish,,,");
                 //使用Future监视目标线程调用call()方法的情况,当前的线程会一直阻塞,直到call()方法结束返回结果。
                System.out.println("获取监听线程返回值: "+future.get());
            }catch(Exception e){
                e.printStackTrace();
            }
    
        }
    
    }
        /**
         * callable 比 runnable 强大的地方是:可以返回结果值
         * @return String
         * @others:接口是 Executor 框架的功能类;
         * */
        class myCallable implements Callable<String> {
    
            @Override
            public String call() throws Exception {
                System.out.println("callable body...");
                Thread.sleep(2000);
                return "Hello World!!";
            }
        }

    4、如何确保线程安全?

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

    4.1.synchronized

    synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。

    public class ThreadDemo {
    
       int count = 0; // 记录方法的命中次数
    
       public synchronized void threadMethod(int j) {
    
           count++ ;
    
           int i = 1;
    
           j = j + i;
       }
    }

    同步代码块:

     1 /**
     2  * 多线程卖票:synchronized
     3  */
     4 public class Demo10 {
     5     public static void main(String[] args) {
     6         ticket t = new ticket();
     7 
     8         Thread t1 = new Thread(t, "线程1");
     9         Thread t2 = new Thread(t, "线程2");
    10         Thread t3 = new Thread(t, "线程3");
    11 
    12         t1.start();
    13         t2.start();
    14         t3.start();
    15 
    16     }
    17 
    18     static class ticket implements Runnable {
    19         private int ticket = 10;
    20         //创建锁
    21         Object lock = new Object();
    22 
    23         @Override
    24         public void run() {
    25             String name = Thread.currentThread().getName();
    26             while (true) {
    27                 //同步代码块
    28                 synchronized (lock){
    29                     try {
    30                         sell(name);
    31                     } catch (InterruptedException e) {
    32                         e.printStackTrace();
    33                     }
    34                     if (ticket < 0) {
    35                         break;
    36                     }
    37                 }
    38             }
    39         }
    40 
    41         private void sell(String name) throws InterruptedException {
    42             Thread.sleep(1000);
    43             if (ticket > 0) {
    44                 System.out.println(name + "——" + ticket);
    45                 ticket--;
    46             }
    47         }
    48     }
    49 }
    View Code
    同步方法:
     1 public class Demo9 {
     2     public static void main(String[] args) {
     3         ticket t = new ticket();
     4 
     5         Thread t1 = new Thread(t, "线程1");
     6         Thread t2 = new Thread(t, "线程2");
     7         Thread t3 = new Thread(t, "线程3");
     8 
     9         t1.start();
    10         t2.start();
    11         t3.start();
    12     }
    13 
    14     static class ticket implements Runnable{
    15         private int ticket =10;
    16         Lock lock = new ReentrantLock();
    17 
    18         @Override
    19         public void run() {
    20             String name = Thread.currentThread().getName();
    21             while(true){
    22                 try {
    23                     if ("线程1".equals(name)){
    24                         synchronized (lock){//线程1获得lock锁
    25                             sell(name);
    26                         }
    27                     }else{
    28                         sell(name);
    29                     }
    30                 } catch (InterruptedException e) {
    31                     e.printStackTrace();
    32                 }
    33                 if (ticket < 0){
    34                     break;
    35                 }
    36 
    37             }
    38         }
    39         //synchronized:同步方法
    40         private synchronized void  sell(String name) throws InterruptedException {//线程2获取到this锁
    41             Thread.sleep(1000);
    42              synchronized (lock){
    43                  if (ticket >0){
    44                      System.out.println(name +"——"+ ticket);
    45                      ticket --;
    46                  }
    47              }
    48         }
    49     }
    50 }
    View Code

    这样就可以确保我们的线程同步了,同时这里需要注意一个大家平时忽略的问题,首先synchronized锁的是括号里的对象,而不是代码,其次,对于非静态的synchronized方法,锁的是对象本身也就是this。

    当synchronized锁住一个对象之后,别的线程如果想要获取锁对象,那么就必须等这个线程执行完释放锁对象之后才可以,否则一直处于等待状态。

    注意点:虽然加synchronized关键字,可以让我们的线程变得安全,但是我们在用的时候,也要注意缩小synchronized的使用范围,如果随意使用时很影响程序的性能,别的对象想拿到锁,结果你没用锁还一直把锁占用,这样就有点浪费资源。

    4.2.lock

    先来说说它跟synchronized有什么区别吧,Lock是在Java1.6被引入进来的,Lock的引入让锁有了可操作性,什么意思?就是我们在需要的时候去手动的获取锁和释放锁,甚至我们还可以中断获取以及超时获取的同步特性,但是从使用上说Lock明显没有synchronized使用起来方便快捷。我们先来看下一般是如何使用的:

     1 private Lock lock = new ReentrantLock(); // ReentrantLock是Lock的子类
     2 
     3    private void method(Thread thread){
     4        lock.lock(); // 获取锁对象
     5        try {
     6            System.out.println("线程名:"+thread.getName() + "获得了锁");
     7            // Thread.sleep(2000);
     8        }catch(Exception e){
     9            e.printStackTrace();
    10        } finally {
    11            System.out.println("线程名:"+thread.getName() + "释放了锁");
    12            lock.unlock(); // 释放锁对象
    13        }
    14    }

    进入方法我们首先要获取到锁,然后去执行我们业务代码,这里跟synchronized不同的是,Lock获取的所对象需要我们亲自去进行释放,为了防止我们代码出现异常,所以我们的释放锁操作放在finally中,因为finally中的代码无论如何都是会执行的。

    写个主方法,开启两个线程测试一下我们的程序是否正常:

     1 public static void main(String[] args) {
     2        LockTest lockTest = new LockTest();
     3 
     4        // 线程1
     5        Thread t1 = new Thread(new Runnable() {
     6 
     7            @Override
     8            public void run() {
     9                // Thread.currentThread()  返回当前线程的引用
    10                lockTest.method(Thread.currentThread());
    11            }
    12        }, "t1");
    13 
    14        // 线程2
    15        Thread t2 = new Thread(new Runnable() {
    16 
    17            @Override
    18            public void run() {
    19                lockTest.method(Thread.currentThread());
    20            }
    21        }, "t2");
    22 
    23        t1.start();
    24        t2.start();
    25    }

    结果
    在这里插入图片描述
    可以看出我们的执行,是没有任何问题的。

    卖票案例:

     1 /**
     2  * 多线程卖票
     3  */
     4 public class Demo8 {
     5     public static void main(String[] args) {
     6         ticket t = new ticket();
     7 
     8         Thread t1 = new Thread(t, "线程1");
     9         Thread t2 = new Thread(t, "线程2");
    10         Thread t3 = new Thread(t, "线程3");
    11 
    12         t1.start();
    13         t2.start();
    14         t3.start();
    15 
    16     }
    17 
    18     static class ticket implements Runnable{
    19         ReentrantLock lock = new ReentrantLock();
    20         private int ticket =10;
    21 
    22         @Override
    23         public void run() {
    24             String name = Thread.currentThread().getName();
    25             while(true){
    26                 //加锁
    27                 lock.lock();
    28                 try {
    29                     sell(name);
    30                 } catch (InterruptedException e) {
    31                     e.printStackTrace();
    32                 }
    33                 if (ticket < 0){
    34                     break;
    35                 }
    36                 //释放锁
    37                 lock.unlock();
    38             }
    39         }
    40 
    41         private void sell(String name) throws InterruptedException {
    42             Thread.sleep(1000);
    43             if (ticket >0){
    44                 System.out.println(name +"——"+ ticket);
    45                 ticket --;
    46             }
    47         }
    48     }
    49 }
    View Code

    4.3.死锁

    多线程死锁:同步中嵌套同步,导致锁无法释放。
    死锁解决办法:不要在同步中嵌套同步

     1 **
     2  * 死锁
     3  */
     4 public class Demo9 {
     5     public static void main(String[] args) {
     6         ticket t = new ticket();
     7 
     8         Thread t1 = new Thread(t, "线程1");
     9         Thread t2 = new Thread(t, "线程2");
    10         Thread t3 = new Thread(t, "线程3");
    11 
    12         t1.start();
    13         t2.start();
    14         t3.start();
    15     }
    16 
    17     static class ticket implements Runnable{
    18         private int ticket =10;
    19         Lock lock = new ReentrantLock();
    20 
    21         @Override
    22         public void run() {
    23             String name = Thread.currentThread().getName();
    24             while(true){
    25                 try {
    26                     if ("线程1".equals(name)){
    27                         synchronized (lock){//线程1获得lock锁
    28                             sell(name);
    29                         }
    30                     }else{
    31                         sell(name);
    32                     }
    33                 } catch (InterruptedException e) {
    34                     e.printStackTrace();
    35                 }
    36                 if (ticket < 0){
    37                     break;
    38                 }
    39 
    40             }
    41         }
    42         //synchronized:同步方法
    43         private synchronized void  sell(String name) throws InterruptedException {//线程2获取到this锁
    44             Thread.sleep(1000);
    45              synchronized (lock){
    46                  if (ticket >0){
    47                      System.out.println(name +"——"+ ticket);
    48                      ticket --;
    49                  }
    50              }
    51         }
    52     }
    53 }
    View Code

    5.线程的生命周期与状态

     

     

    5.1.多线程的几种状态

      • 新建状态(New):
        用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。
      • 就绪状态(Runnable):
        当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
      • 运行状态(Running):
        处于这个状态的线程占用CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。
      • 阻塞状态(Blocked):
        阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。
        • 阻塞状态可分为以下3种:
          • 位于对象等待池中的阻塞状态(Blocked in object’s wait pool):
            当线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。
          • 位于对象锁池中的阻塞状态(Blocked in object’s lock pool):
            当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。
          • 其他阻塞状态(Otherwise Blocked):
            当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态。
      • 死亡状态(Dead):
        当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。

    查看Thread源码,能够看到java的线程有六种状态:

     NEW:线程刚被创建,但是并未启动。

    RUNNABLE(可运行):

    线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。


    BLOCKED(锁阻塞):
    当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked态;当该线程持有锁时,该线程将变成Runnable状态。

    WAITING(无限等待):
    一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。

    TIMED_WAITING(计时等待):
    waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleepObject.wait

    TERMINATED(被终止):
    因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
     

    5.2.线程控制

     jion方法:

     1 public class Demo12 {
     2     public static void main(String[] args) {
     3         ThreadJoin tj1 = new ThreadJoin();
     4         ThreadJoin tj2 = new ThreadJoin();
     5         ThreadJoin tj3 = new ThreadJoin();
     6         tj1.setName("康熙");
     7         tj2.setName("四阿哥");
     8         tj3.setName("八阿哥");
     9         tj1.start();
    10         try {
    11             //等线程tj1执行完成之后,其余线程方可执行
    12             tj1.join();
    13         } catch (InterruptedException e) {
    14             e.printStackTrace();
    15         }
    16         tj2.start();
    17         tj3.start();
    18     }
    19     public static class ThreadJoin extends Thread {
    20         @Override
    21         public void run() {
    22             for (int i = 0; i < 5; i++) {
    23                 System.out.println(getName() + ":" + i);
    24             }
    25         }
    26     }
    27 }
    View Code

    setDemon方法:

     1 public class Demo2 {
     2     public static void main(String[] args) {
     3         Thread t1 = new Thread(new demo());
     4         //Thread t2 = new Thread(new demo());
     5         
     6         //设置t2为守护线程
     7         //t2.setDaemon(true);
     8         //设置t1为守护线程
     9         t1.setDaemon(true);
    10         t1.start();
    11         //t2.start();
    12     }
    13     static class demo extends Thread{
    14         public void run(){
    15             String name = Thread.currentThread().getName();
    16             for (int i = 0;i <4;i++){
    17                 try {
    18                     Thread.sleep(1000);
    19                 } catch (InterruptedException e) {
    20                     e.printStackTrace();
    21                 }
    22                 System.out.println(name +"的内容是"+i);
    23             }
    24         }
    25     }
    26 }
    View Code

    5.3.wait()notify()

    wait()、notify()、notifyAll()是三个定义在Object类里的方法,可以用来控制线程的状态状态。
    wait 方法会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
    notify 方法会通知某个正在等待这个对象的控制权的线程继续运行。
    notifyAll 方法会通知所有正在等待这个对象的控制权的线程继续运行。
    注意:一定要在线程同步中使用,并且是同一个锁的资源

     1 /**
     2  * wait和notify方法例子,一个人进站出站:
     3  */
     4 public class Demo13 {
     5     public static void main(String[] args) {
     6         State state = new State();
     7         InThread inThread = new InThread(state);
     8         OutThread outThread = new OutThread(state);
     9         Thread in = new Thread(inThread);
    10         Thread out = new Thread(outThread);
    11         in.start();
    12         out.start();
    13     }
    14 
    15     // 控制状态
    16     static class State {
    17         //状态标识
    18         public String flag = "车站内";
    19     }
    20 
    21     static class InThread implements Runnable {
    22         private State state;
    23         public InThread(State state) {
    24             this.state = state;
    25         }
    26         public void run() {
    27             while (true) {
    28                 synchronized (state) {
    29                     if ("车站内".equals(state.flag)) {
    30                         try {
    31                             // 如果在车站内,就不用进站,等待,释放锁
    32                             state.wait();
    33                         } catch (Exception e) {
    34                         }
    35                     }
    36 
    37                 }
    38                 System.out.println("进站");
    39                 state.flag = "车站内";
    40                 // 唤醒state等待的线程
    41                 state.notify();
    42             }
    43         }
    44     }
    45 
    46     static class OutThread implements Runnable {
    47         private State state;
    48 
    49         public OutThread(State state) {
    50             this.state = state;
    51         }
    52         public void run() {
    53             while (true) {
    54                 synchronized (state) {
    55                     if ("车站外".equals(state.flag)) {
    56                         try {
    57                             //就不用出站了,等待,释放锁
    58                             state.wait();
    59                         } catch (Exception e) {
    60                         }
    61                     }
    62                     System.out.println("出站");
    63                     state.flag = "车站外";
    64                     // 唤醒state等待的线程
    65                     state.notify();
    66                 }
    67             }
    68         }
    69     }
    70 }
    View Code

    5.4.waitsleep区别

    •  对于sleep()方法,首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的
    • sleep 控状态依然保持者,当指定的时间到了又会自动恢复运行状态。wait()是把控制权交出去,然后进入等待此对象的等待锁定池处于等待状态,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
    • 在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁 。

    5.5.线程停止

    结束线程有以下三种方法:
    1)设置退出标志,使线程正常退出。
    2)使用interrupt()方法中断线程。
    3)使用stop方法强行终止线程(不推荐使用Thread.stop, 这种终止线程运行的方法已经被废弃,使用它们是极端不安全的!) 

    (1)使用退出标志
    一般run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如:最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为truefalse来控制while循环是否退出,代码示例: 

     1 public class Demo8Exit {
     2 
     3     public static boolean exit = true;
     4 
     5     public static void main(String[] args) throws InterruptedException {
     6         Thread t = new Thread(new Runnable() {
     7             public void run() {
     8                 while (exit) {
     9                     try {
    10                         System.out.println("线程执行!");
    11                         Thread.sleep(100l);
    12                     } catch (InterruptedException e) {
    13                         e.printStackTrace();
    14                     }
    15                 }
    16             }
    17         });
    18         t.start();
    19 
    20         Thread.sleep(1000l);
    21         exit = false;
    22         System.out.println("退出标识位设置成功");
    23     }
    24 }
    View Code

    2)使用interrupt()方法中断线程

     1 public class Demo9Interrupt {
     2 
     3     public static boolean exit = true;
     4 
     5     public static void main(String[] args) throws InterruptedException {
     6         Thread t = new Thread(new Runnable() {
     7             public void run() {
     8                 while (exit) {
     9                     try {
    10                         System.out.println("线程执行!");
    11 
    12                         //判断线程的中断标志来退出循环
    13                         if (Thread.currentThread().isInterrupted()) {
    14                             break;
    15                         }
    16 
    17                         Thread.sleep(100l);
    18                     } catch (InterruptedException e) {
    19                         e.printStackTrace();
    20                         //线程处于阻塞状态,当调用线程的interrupt()方法时,
    21                         //会抛出InterruptException异常,跳出循环
    22                         break;
    23                     }
    24                 }
    25             }
    26         });
    27         t.start();
    28 
    29         Thread.sleep(1000l);
    30         //中断线程
    31         t.interrupt();
    32         System.out.println("线程中断了");
    33     }
    34 }
    View Code

    6.线程优先级

    6.1 优先级priority

    现今操作系统基本采用分时的形式调度运行的线程,线程分配得到时间片的多少决定了线程使用处理器资源的多少,也对应了线程优先级这个概念。JAVA线程中,通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。 

     1 public class Demo10Priorityt {
     2 
     3     public static void main(String[] args) {
     4         PrioritytThread prioritytThread = new PrioritytThread();
     5 
     6         // 如果8核CPU处理3线程,无论优先级高低,每个线程都是单独一个CPU执行,就无法体现优先级
     7         // 开启10个线程,让8个CPU处理,这里线程就需要竞争CPU资源,优先级高的能分配更多的CPU资源
     8         for (int i = 0; i < 3; i++) {
     9             Thread t = new Thread(prioritytThread, "线程" + i);
    10             if (i == 1) {
    11                 t.setPriority(4);
    12             }
    13             if (i == 2) {
    14                 t.setPriority(8);
    15             }
    16             t.setDaemon(true);
    17             t.start();
    18         }
    19 
    20         try {
    21             Thread.sleep(1000l);
    22         } catch (InterruptedException e) {
    23             e.printStackTrace();
    24         }
    25 
    26         System.out.println("线程1总计:" + PrioritytThread.count1);
    27         System.out.println("线程2总计:" + PrioritytThread.count2);
    28     }
    29 
    30     static class PrioritytThread implements Runnable {
    31         public static Integer count1 = 0;
    32         public static Integer count2 = 0;
    33 
    34         public void run() {
    35             while (true) {
    36                 if ("线程1".equals(Thread.currentThread().getName())) {
    37                     count1++;
    38                 }
    39                 if ("线程2".equals(Thread.currentThread().getName())) {
    40                     count2++;
    41                 }
    42                 if (Thread.currentThread().isInterrupted()) {
    43                     break;
    44                 }
    45             }
    46         }
    47     }
    48 }
    View Code

    6.2.join()方法

    两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。6.3.yield方法 

    6.3.yield方法

    yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

    7.线程的几个主要概念

    在多线程编程时,你需要了解以下几个概念:

    • 线程同步
    • 线程间通信
    • 线程死锁
    • 线程控制:挂起、停止和恢复

     8.多线程并发的3个特性

    多线程并发开发中,要知道什么是多线程的原子性,可见性和有序性,以避免相关的问题产生。
    8.1 原子性

    原子性: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
    一个很经典的例子就是银行账户转账问题:
    比如从账户A向账户B1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。1000元之后,操作突然中止。这样就会导致账户A虽然减去了1000元,但是账户B没有收到这个转过来的1000元。
    所以这2个操作必须要具备原子性才能保证不出现一些意外的问题。
    8.2 可见性

    可见性: 当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即
    看得到修改的值
    举个简单的例子,看下面这段代码:

    //线程1执行的代码
    int i = 0;
    i = 10;
    //线程2执行的代码
    j = i;

    当线程1执行 int i = 0 这句时, i 的初始值0加载到内存中,然后再执行 i =10 ,那么在内存中 i 的值变为10了。
    如果当线程1执行到 int i = 0 这句时,此时线程2执行 j = i,它读取 i 的值并加载到内存中,注意此时内存当中i的值是0,那么就会使得 j 的值也为0,而不是10。
    这就是可见性问题,线程1对变量 i 修改了之后,线程2没有立即看到线程1修改的值

    8.3.有序性

    有序性: 程序执行的顺序按照代码的先后顺序执行

    要想并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。 

  • 相关阅读:
    所有HTTP返回状态值,并说明用途
    几个简单的排序算法
    Linux命令大全
    存储过程中执行动态Sql语句
    IE8的背景不显示和图片错位 解决方案
    海量数据处理方法
    关于MSSQL的返回值问题
    SQL Server 2008不能修改表的解决方法
    转:读AD里特殊的属性in C#
    了解SMS的主要特性。
  • 原文地址:https://www.cnblogs.com/aaaazzzz/p/12783137.html
Copyright © 2020-2023  润新知