Java多线程技能

前言:本系列将从零开始讲解java多线程相关的技术,内容参考于《java多线程核心技术》与《java并发编程实战》等相关资料,希望站在巨人的肩膀上,再通过我的理解能让知识更加简单易懂。

并发历史

  • 在没有操作系统的时候,一台计算机只执行一个程序,在那个时候,对珍贵的计算机资源来说是一种浪费
  • 为了提高资源利用率(比如在等待输入的时候,可以执行其他程序),为了提高公平性(不同用户和程序对计算机上的资源有平等的使用权),为了提高便利性(实现多个任务的时候,可以通过多个程序,而不用一个程序实现多个任务)计算机加入了操作系统
  • 同样,相同的原因,线程诞生了。线程可以共享进程的资源。

线程优势

发挥多处理器的强大功能

  • 随着技术的发展,多处理器系统越来越普及。在一个双处理器系统上,如果只用一个线程,那么无疑浪费了资源。

线程状态

  • 新建(New):创建后尚未启动的线程
  • 运行(Runanle):包括了操作系统线程中的Running和Ready,处于此状态的线程可能正在执行或者等待cpu分配时间片
  • 无限期等待(Waiting):等待被其他线程显式唤醒,执行wait或者join方法或者LockSupport的park方法
  • 限期等待(Timed Waiting):一定时间后会由系统自动唤醒
  • 阻塞(Blocked):等待获取到一个排它锁
  • 结束(Terminated):线程执行结束

多线程编程的两种方式

  • 继承Thread
  • 实现Runnable接口

通过继承Thread

代码的执行顺序与代码的顺序无关

     public class T1 {

    public static void main(String[] args) {

        MyThread myThread=new MyThread();

        myThread.start();

        System.out.println("代码的执行结果与代码的顺序无关");

    }

}

class MyThread extends Thread

{

    public void run()

    {

        System.out.println("创建的线程");

    }

}

如果直接执行run方法是同步(主线程调用),start方法是让系统来找一个时间来调用run方法(子线程调用),

public class T1 {

    public static void main(String[] args) {

        MyThread myThread=new MyThread();

        myThread.run();

        System.out.println("如果是直接执行run方法,肯定是按代码顺序执行的,因为是通过主线程调用的");

    }

}

class MyThread extends Thread

{

    public void run()

    {

        System.out.println("创建的线程");

    }

}

通过实现Runnable接口

比继承Thread的方式更有优势

  • java不能多继承,所以如果线程类已经有一个父类,那么无法再继承Thread类
public class MyRunnable implements Runnable {

    @Override

    public void run() {

        System.out.println("运行中!");

    }

}

public class Run {



    public static void main(String[] args) {

        Runnable runnable=new MyRunnable();

        Thread thread=new Thread(runnable);

        thread.start();

        System.out.println("运行结束!");

    }



}

线程数据非共享

public static void main(String[] args) {

            MyThread a = new MyThread("A");

            MyThread b = new MyThread("B");

            MyThread c = new MyThread("C");

            a.start();

            b.start();

            c.start();

        }

        

class MyThread extends Thread {



    private int count = 5;



    public MyThread(String name) {

        super();

        this.setName(name);

    }



    @Override

    public void run() {

        super.run();

        while (count > 0) {

            count--;

            System.out.println("由 " + this.currentThread().getName()

                    + " 计算,count=" + count);

        }

    }

}
  • 这里的i并不共享,每一个线程维护自己的i变量

线程数据共享

public static void main(String[] args) {

    MyThread mythread=new MyThread();

    //线程a b c启动的时候,执行的是myThread的方法,此时数据共享

    Thread a=new Thread(mythread,"A");

    Thread b=new Thread(mythread,"B");

    Thread c=new Thread(mythread,"C");



    a.start();

    b.start();

    c.start();



}
  • 由于i++不是原子操作(先获取i的值,让后再加一,再把结果赋给i),所以输出的值会有重复的情况,比如4 4 2

synchronized关键字让i++同步执行

 public synchronized void run() {

        super.run();

            count--;

            System.out.println("由 "+this.currentThread().getName()+" 计算,count="+count);//输出的一定是4 3 2

    }
  • synchronized 关键字,给方法加上锁,多个线程尝试拿到锁,拿到锁的线程执行方法,拿不到的不断的尝试拿到锁

Thread方法

  • currentThread(),获得当前线程
  • isLive() 线程是否活跃状态(启动还未运行或者启动了正在运行即为活跃状态)
  • sleep()方法,让线程休眠
  • getId()方法 获得该线程的唯一标识
  • suspeend()方法,让线程暂停(已报废)
  • ressume()方法,让线程恢复(已报废)
  • stop()方法,让线程终止(已报废)

停止线程的方法

  • 线程自己执行完后自动终止
  • stop强制终止,不安全
  • 使用interrupt方法

interrupt方法

  • 线程对象有一个boolean变量代表是否有中断请求,interrupt方法将线程的中断状态设置会true,但是并没有立刻终止线程,就像告诉你儿子要好好学习一样,但是你儿子怎么样关键看的是你儿子。
public static void main(String[] args) {

        try {

            MyThread thread = new MyThread();

            thread.start();

            Thread.sleep(200);

            thread.interrupt();

        } catch (InterruptedException e) {

            System.out.println("main catch");

            e.printStackTrace();

        }

        System.out.println("end!");

    }

    class MyThread extends Thread {

        @Override

        public void run() {

            super.run();

            for (int i = 0; i < 500000; i++) {

                System.out.println("i=" + (i + 1));

            }

        }

    }
  • 上面的代码虽然执行了interrupt方法,但是并没有中断run方法里面的程序,并且run方法全部执行完,也就是一直执行到500000

判断线程是否中断

  • interrupted方法判断当前线程是否中断,清除中断标志
  • isInterrupt 方法判断线程是否中断,不清除中断标志

interrupted方法

public class MyThread extends Thread {

    @Override

    public void run() {

        super.run();

        for (int i = 0; i < 500000; i++) {

            System.out.println("i=" + (i + 1));

        }

    }

}

public class Run {

    public static void main(String[] args) {

        try {

            MyThread thread = new MyThread();

            thread.start();

            Thread.sleep(1000);

            thread.interrupt();

            //Thread.currentThread().interrupt();

            System.out.println("是否停止1?="+thread.interrupted());//false

            System.out.println("是否停止2?="+thread.interrupted());//false main线程没有被中断!!!

      //......       
  • 这里thread线程执行了interrupt方法,按到里thread的中断状态应该为true,但是因为interrupted方法判断的是当前线程的中断状态,也就是main线程(main线程执行thread.interrupted方法),所以他的中断状态是false
public static void main(String[] args) {

        MyThread thread=new MyThread();

        thread.start();

        thread.interrupt();

        System.out.println(thread.isInterrupted());//true

        System.out.println(thread.isInterrupted());//true

    }
  • 主线程执行interrupt方法,第一次执行interrupted方法的时候,中断状态为true,但是interrupted方法有清除中断标志的作用,所以再执行的时候输出的是false

isInterrupt方法

public static void main(String[] args) {        MyThread thread=new MyThread();        thread.start();        thread.interrupt();        System.out.println(thread.isInterrupted());//true        System.out.println(thread.isInterrupted());//true    }
  • 这里也有判断线程中断的作用,而判断的是他的调用者的中断状态,而且没有清除中断标志的作用,所以两次都是true

停止线程

  • 在上面的代码中,我们虽然执行了interrupt方法,但是并没有中断进程,那么我们如果来中断呢?我们可以在run方法中进行判断,判断中断状态,状态为true,那么就停止run方法。
import exthread.MyThread;



import exthread.MyThread;



public class Run {



    public static void main(String[] args) {

        try {

            MyThread thread = new MyThread();

            thread.start();

            Thread.sleep(2000);

            thread.interrupt();

        } catch (InterruptedException e) {

            System.out.println("main catch");

            e.printStackTrace();

        }

        System.out.println("end!");

    }



}



public class MyThread extends Thread {

    @Override

    public void run() {

        super.run();

        for (int i = 0; i < 500000; i++) {

            if (this.interrupted()) {

                System.out.println("已经是停止状态了!我要退出了!");

                break;

            }

            System.out.println("i=" + (i + 1));

        }

        System.out.println("666");

    }

}
  • 还有一个问题,我们要中断进程,通过上面的的操作我们终止了for循环,但是后面的输出666仍然执行,这并不是我们想要的中断。于是我们可以顺便抛出异常,然后直接捕获,这样的话后面的代码就不执行了。

异常法停止线程

public class MyThread extends Thread {

    @Override

    public void run() {

        super.run();

        try {

            for (int i = 0; i < 500000; i++) {

                if (this.interrupted()) {

                    System.out.println("已经是停止状态了!我要退出了!");

                    throw new InterruptedException();

                }

                System.out.println("i=" + (i + 1));

            }

            System.out.println("我在for下面");

        } catch (InterruptedException e) {

            System.out.println("进MyThread.java类run方法中的catch了!");

            e.printStackTrace();

        }

    }

}
  • 当然我们也可以直接return,但是抛出异常比较好,因为后面可以继续将异常抛出,让线程中断事件得到传播

return 停止线程

for (int i = 0; i < 500000; i++) {

                if (this.interrupted()) {

                    System.out.println("已经是停止状态了!我要退出了!");

                    return;

                }

                System.out.println("i=" + (i + 1));

            }

sleep与interrupt

  • 中断状态,进入sleep抛出异常
  • 睡眠进入中断状态,抛出异常
public static void main(String[] args) {

        try {

            MyThread thread = new MyThread();

            thread.start();

            Thread.sleep(200);

            thread.interrupt();

        } catch (InterruptedException e) {

            System.out.println("main catch");

            e.printStackTrace();

        }

        System.out.println("end!");

    }

}

    class MyThread extends Thread {

        @Override

        public void run() {

            super.run();

            try {

                System.out.println("run begin");

                Thread.sleep(200000);

                System.out.println("run end");

            } catch (InterruptedException e) {

                System.out.println("在沉睡中被停止!进入catch!"+this.isInterrupted());

                e.printStackTrace();

            }

        }

    }*/

暂停线程

  • suspend (作废)会让同步方法直接锁住
public static void main(String[] args) {

        try {

            final SynchronizedObject object = new SynchronizedObject();



            Thread thread1 = new Thread() {

                @Override

                public void run() {

                    object.printString();

                }

            };



            thread1.setName("a");

            thread1.start();



            Thread.sleep(1000);



            Thread thread2 = new Thread() {

                @Override

                public void run() {

                    System.out

                            .println("thread2启动了,但进入不了printString()方法!只打印1个begin");

                    System.out

                            .println("因为printString()方法被a线程锁定并且永远的suspend暂停了!");

                    object.printString();

                }

            };

            thread2.start();

        } catch (InterruptedException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

    }



}

    class SynchronizedObject {



        synchronized public void printString() {

            System.out.println("begin");

            if (Thread.currentThread().getName().equals("a")) {

                System.out.println("a线程永远 suspend了!");

                Thread.currentThread().suspend();

            }

            System.out.println("end");

        }



    }
  • 当thread1执行了suspend方法后,printString方法直接就被锁住了,也就是thread1把这个锁占住了,但是却不工作
public void println(String x) {

        synchronized (this) {

            print(x);

            newLine();

        }

    }
  • 用System.out.println() 方法替换pringString方法同样也会锁住,因为这个方法也加锁了,当thread1暂停之后,同样占住了这个锁

yield方法

  • 放弃当前cpu资源,让其他任务去占用,但是什么时候放弃不知道,因为放弃后,可能又开始获得时间片

线程优先级

  • cpu先执行优先级高的线程的对象任务, setPriority方法可以设置线程的优先级
  • 线程优先级具有继承性,也就是说A线程启动B线程,二者优先级一样,同样main主线程启动线程A,main和A的优先级也是一样的
public static void main(String[] args) {        System.out.println("main thread begin priority="                + Thread.currentThread().getPriority());        Thread.currentThread().setPriority(6);        System.out.println("main thread end   priority="                + Thread.currentThread().getPriority());        MyThread1 thread1 = new MyThread1();        thread1.start();    }

    class MyThread1 extends Thread {        @Override        public void run() {            System.out.println("MyThread1 run priority=" + this.getPriority());            MyThread2 thread2 = new MyThread2();            thread2.start();        }    }

优先级特性

  • 规则性,cpu尽量将资源给优先级高的
  • 随机性,优先级较高的不一定先执行完run方法

守护线程

  • 线程有两种一种是用户线程,一种是守护线程
  • 垃圾回收线程是典型的守护线程,当jvm中还有非守护线程,守护线程就一直还在,知道非守护线程不存在了,守护线程才销毁

总结

  • 线程提高了资源利用率
  • 线程的实现可以通过继承Thread类,也可以通过实Runnable接口
  • 线程中断方式有3种,常用的是interrupt方法,该方法并没有立即中断线程,只是做了一个中断标志
  • interrupted和isInterrupt两种方法都可以查看线程中断状态,第一种查看的是当前线程的中断状态,第二种查看的该方法调用者的中断状态
  • interrupted方法会清除中断状态,isInterrupt不会清除中断状态
  • interrupt方法没有真正中断线程,所以可以在run方法里面判断中断状态,然后通过抛出异常或者return来中断线程
  • 当线中断状态为true,再进入sleep会抛出异常,反之一样,sleep状态执行interrupt方法,同样会抛出异常
  • 线程暂停容易将锁占住
  • 线程具有优先级,可以通过方法设置线程优先级,cpu会将资源尽量给优先级高的线程,但是当优先级差别不大的时候,优先级高的不一定先执行完run方法
  • 线程有两种,一种用户线程,一种守护线程,直到用户线程都销毁,守护线程才销毁
  • 原文:http://www.cnblogs.com/-new/p/7156811.html

原文发布于微信公众号 - Java团长(javatuanzhang)

原文发表时间:2018-02-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java达人

多线程设计模式解读1—Guarded Suspension(保护性暂挂模式)

大家好,今天我们给大家介绍一个多线程设计模式的一个概念,我们平时业务代码写得比较多,因此,如果刚上手写比较复杂多线程代码,很有可能会埋下一些坑,而这些坑一时之间...

10540
来自专栏公众号_薛勤的博客

Java多线程编程核心技术(三)多线程通信

通过本节可以学习到,线程与线程之间不是独立的个体,它们彼此之间可以互相通信和协作。

13380
来自专栏我是攻城师

深入理解Java类加载器机制

Java里面的类加载机制,可以说是Java虚拟机核心组件之一,掌握和理解JVM虚拟机的架构,将有助于我们站在底层原理的角度上来理解Java语言,这也是为什么我们...

37220
来自专栏专注 Java 基础分享

Java并发之线程

     在前面我们介绍的一些内容中,我们的程序都是一条执行流,一步一步的执行。但其实这种程序对我们计算机的资源的使用上是低效的。例如:我们有一个用于计算的程序...

20450
来自专栏Jerry的SAP技术分享

Java代理设计模式(Proxy)的四种具体实现:静态代理和动态代理

面试问题:Java里的代理设计模式(Proxy Design Pattern)一共有几种实现方式?这个题目很像孔乙己问“茴香豆的茴字有哪几种写法?”

3.2K30
来自专栏xingoo, 一个梦想做发明家的程序员

Java多线程之Runable与Thread

Java多线程是Java开发中的基础内容,但是涉及到高并发就有很深的研究可做了。 最近看了下《Java并发实战》,发先有些地方,虽然可以理解,但是自己在应用...

26890
来自专栏Java架构师历程

JVM加载class文件的原理

当Java编译器编译好.class文件之后,我们需要使用JVM来运行这个class文件。那么最开始的工作就是要把字节码从磁盘输入到内存中,这个过程我们叫做【加载...

54620
来自专栏炉边夜话

JNI设计实践之路

本文为在 32 位 Windows 平台上实现 Java 本地方法提供了实用的示例、步骤和准则。本文中的示例使用 Sun公司的 Java Development...

18730
来自专栏安恒网络空间安全讲武堂

WriteUp分享 | CTF-web

题目 各种绕过哦 TXT? 文件上传测试 本地包含 考细心 正则? PHP很烦人? 一道签到题 抽抽奖 never give up I have a j...

3.5K80
来自专栏Java编程

Java多线程技能

我有一个微信公众号,经常会分享一些Java技术相关的干货。如果你喜欢我的分享,可以用微信搜索“Java团长”或者“javatuanzhang”关注。

42710

扫码关注云+社区

领取腾讯云代金券