JAVA并行程序基础

JAVA并行程序基础

一、有关线程你必须知道的事

进程与线程

  1. 在等待面向线程设计的计算机结构中,进程是线程的容器。我们都知道,程序是对于指令、数据及其组织形式的描述,而进程是程序的实体。
  2. 线程是轻量级的进程,是程序执行的最小单位。(PS:使用多线程去进行并发程序的设计,是因为线程间的调度和切换成本远小于进程)
  3. 线程的状态(Thread的State类):
    • NEW–刚刚创建的线程,需要调用start()方法来执行线程;
    • RUNNABLE–线程处于执行状态;
    • BLOCKED–线程遇到synchronized同步块,会暂停执行直到获得请求的锁;
    • WAITING–无限制时间的等待;
    • TIMED_WAITING–有限制时间的等待;
    • TERMINATED–执行完毕,表示结束。

二、线程的基本操作

创建线程

代码示例:start()方法会新建线程并调用run()方法!

Thread t = new Thread();
t.start();

注意以下代码:这段代码也可以编译执行,但不能新建一个线程,而是在当前线程中调用run()方法。(PS:就是作为一个普通方法来调用)

Thread t = new Thread();
t.run();

不能用run()来开启线程,它只会在当前线程中串行执行run()方法。

另外,Thread类有一个非常重要的构造方法:

public Thread(Runnable target)

这个构造方法在start()调用时,新线程会执行Runnable.run()方法。实际上,默认的Thread.run()也是这样子做的

public class MyThread implements Runnable {
    public static void main(String[] args) {
        Thread t = new Thread(new MyThread());
        t.start();
    }

    @Override
    public void run() {
        System.out.println("开启新线程执行run()方法");
    }
}
  • 继承Thread的方式定义线程后,就不能在继承其他的类了,导致程序的可扩展性大大降低。而且**重载**run()方法也只是和普通方法一样,不会被JVM主动调用。
  • 使用实现Runnable接口并传入实例给Thread,可以避免重载Thread.run(),单纯使用接口来定义Thread。

终止线程

JAVA提供了一个stop()方法来关闭线程,不过这是一个被标注为废弃的方法。原因是stop()方法太过暴力,强行将执行到一半的线程关闭可能导致一些数据不一致的问题。

一帮情况下,不要使用 stop() 方法!

解决方法:定义一个标记变量stopme,用于指示线程是否需要退出。

线程中断

在JAVA中,为了完善线程安全退出,有一种重要的线程协作机制————线程中断。(注意:线程中断会给线程发送一个通知,但线程接到通知后如何处理,则由目标线程自行决定)

  • 线程中断的三个重要方法
    public void Thread.interrupt()                  //中断线程
    public boolean Thread.isInterrupted()           //判断线程是否被中断
    public static boolean Thread.interrupted()      //判断线程是否被中断,并清除当前中断状态
  • Thread.interrupt()方法中断,线程不会立刻停下来,比如死循环体。可以通过isInterrupted()方法来判断是否跳出循环体。

线程休眠

  1. Thread.sleep()

    让当前线程休眠若干时间,其签名如下:

    public static native void sleep(long millis) throws InterruptedException

    • InterruptedException不是运行时异常,当线程处于休眠状态时,如果被中断就会抛出这个异常。
    • sleep()方法由于中断抛出异常时,会清除中断标记。所以,在异常处理中需要再次设置中断标记!
  2. Thread.wait()与notify()

    这两个方法是配套的,一个线程调用obj.wait()处于等待状态,需要其他线程调用obj.notify()来唤醒。obj对象就是多个线程间有效通信手段

    • Object.wait() 必须包含在 s’y’nchronzied 语句中,无论是 wait() 还是 notify() 都需要先获得目标对象的一个监视器。(PS:这两个方法都是执行后立刻释放监视器,防止其他等待对象线程因为该线程休眠而全部无法正常执行)
    • Object.wait() 同样会抛出InterruptedException异常。
    • Object.notifyAll() 方法会唤醒等待队列中所有等待的线程。
  3. suspengd()与resume()

    前者是线程挂起,后者是继续执行,这是一对互相配合的方法。这两个也是已经被废弃的方法,了解一下就行了。

    • suspend() 会导致休眠线程的所有资源都不会被释放,直到执行 resume() 方法。(如果两个方法,后者执行于前者之前,则线程很难有机会被继续执行)
    • 对于被挂起的线程,其状态是Runnable,这会严重影响到我们对于系统当前状态的判断。(不可饶恕)
  4. join()与yield()
    • join() 方法

      其签名如下:

      public final void join() throws InterruptedException;

      public final void join(long millis) throws InterruptedException;

      第一个方法表示无限等待;第二个方法表示一定时间的等待。join()方法会阻塞当前线程,直到目标线程结束,或者到达阻塞时间。

      同时,join()方法的本质是让在当前线程对象实例上调用线程的wait()方法。

    • yield() 方法

      其签名如下:

      public static native void yield();

      这个静态方法会使当前线程让出CPU。(PS:但还是会进行CPU资源的抢夺)

      当某个线程不是那么重要,或者优先级别较低,可以在适当时候调用 Thread.yield(),给予其他重要线程更多工作机会。

关于线程操作的补充

  1. sleep() 与 wait() 的区别

    Object.wait() 和 Thread.sleep() 都可以让线程等待若干时间。除了** wait() 可以被唤醒外,另外一个主要区别:**wait() 方法会释放目标对象的锁,而 sleep() 方法不会释放任何资源

三、分门别类的管理:线程组

简单建立一个线程组(代码来源于《实战Java高并发程序设计》书中)

public class MyThread implements Runnable {

    public static void main(String[] args) {
        //创建一个叫"PrintGroup"的线程组
        ThreadGroup tg = new ThreadGroup("PrintGroup");
        Thread t1 = new Thread(tg, new MyThread(), "T1");   //加入线程组
        Thread t2 = new Thread(tg, new MyThread(), "T2");
        t1.start();
        t2.start();
        //由于线程是动态的,activeCount()获得活动线程总数的估算值
        System.out.println("活动线程总数 = " + tg.activeCount());
        //打印出线程组的线程信息
        tg.list();
        //注意这个方法与Thread.stop()方法遇到问题一样
        tg.stop();
    }

    @Override
    public void run() {
        String groupAndName = Thread.currentThread().getThreadGroup().getName()
                + "---" + Thread.currentThread().getName();
        while (true) {
            System.out.println("I am " + groupAndName);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

四、驻守后台的守护线程

守护线程,是系统的守护者,在后台完成系统性的服务,比如垃圾回收线程、JIT线程等。当一个Java应用内只有守护线程时,Java虚拟机就会自然退出

将线程设置为守护线程:

        Thread t = new Thread(new MyThread());
        t.setDaemon(true);
        t.start();

注意:设置守护线程必须在线程start()之前设置,否则会得到一个IllegalThreadStateException,但程序和线程依然可以正常执行,只是线程被当作用户线程而已。

五、线程优先级

Java中线程可以有自己的优先级。由于线程的优先级调度和底层操作系统有密切关系,在各个平台表现不一,无法精准控制。

在Java中,使用 1 到 10 表示线程优先级。一般可以使用三个内置的静态标量表示:

    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;

PS:数字越大,优先级越高(一般情况)。

六、synchronized 关键字

synchronized 关键字用法简单记录:

  • 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
  • 直接作用于实例对象:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
  • 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。

synchronized 可以保证线程间的原子性、可见性和有序性(被 synchronized 限制的多个线程是串行执行的)。

七、部分隐秘的错误记录

  • 执行(synchronized)同步代码的两个线程指向了不同的Runnable实例,即两个线程使用了两个不同的锁。解决方法:将同步代码修改为静态(static)方法。
  • 使用线程不安全的容器,如 ArrayList、HashMap 等。解决方法:改用线程安全的容器,如 Vector、ConcurrentHashMap。
  • 不变对象加锁,导致对于临界区代码控制出现问题。
    • 不变对象:对象一旦被创建,就不能修改。

      (如,Integer i; i++的本质是创建一个新的Integer对象,并将引用赋值给i,同时这里涉及到Integer引用的特性)

八、参考资料

  • 《实战Java高并发程序设计》(葛一鸣 郭超 著)
时间: 2024-10-13 15:27:25

JAVA并行程序基础的相关文章

Java并行程序基础总结

1-1 线程概念 1. 同步(Synchronous)和异步(Asynchronous) 2. 并发和并行 3. 临界区 4. 阻塞与非阻塞 5. 死锁.饥饿.活锁 6. 并发级别:阻塞.无饥饿.无阻碍.无锁.无等待 7. 并发的两个重要定律:Amdahl.Gustafson定律 2-1.线程的基本操作 2.1 创建线程 创建线程的第一种方式:继承Thread类. 步骤: 1,定义类继承Thread. 2,复写Thread类中的run方法. 目的:将自定义代码存储在run方法.让线程运行. 3,

关于Java并行程序

因为某种需要,要编写类似于C++OpenMP的Java多线程程序.但是,由于对Java编程不熟悉.对于Java多线程编程,有大概4-5中实现方法.我采用的是Executor框架和线程池. 具体内容见链接 http://blog.csdn.net/ns_code/article/details/17465497

Java并行程序设计模式小结

转自:http://www.cnblogs.com/panfeng412/p/java-program-tuning-reading-notes-of-concurrent-program-design-pattern.html 这里总结几种常用的并行程序设计方法,其中部分文字源自<Java程序性能优化>一书中,还有部分文字属于个人总结,如有不对,请大家指出讨论. Future模式 一句话,将客户端请求的处理过程从同步改为异步,以便将客户端解放出来,在服务端程序处理期间可以去干点其他事情,最后

java并发程序基础——BlockingQueue

概述 BlockingQueue顾名思义'阻塞的队列',是指在它上面的操作都是被阻塞的(部分操作存在例外,如add等).BlockQueue是java.util.concurrent工具包的重要基础工具,在ThreadPoolExcutor及tomcat等服务端容器中都有使用到. BlockingQueue的分类 按照对象存放的数据结构分类 BlockingQueue中的对象可以存放在:数组.链表中,对应的就是ArrayBlockingQueue.LinkedBlockingQueu: Bloc

Java并发程序基础

Thread.stop() 直接终止线程,并且会立即释放这个线程所持有的锁. Thread.interrupt() 并不会是线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出啦,至于目标线程接到通知后会如何处理,则完全由目标线程自行决定. Thread.sleep() 使当前线程休眠若干时间,如果线程sleep时被中断,就会产生InterruptedException wait()和notify()方法不属于Thread,而是Object T1 T2 获取object监视器 Ob

java并发程序基础——Excutor

概述 Excutor这个接口用的不多,但是ThreadPoolExcutor这个就用的比较多了,ThreadPoolExcutor是Excutor的一个实现.Excutor体系难点没有,大部分的关键点和设计思路都在javadoc中描述的特别详细,但有必要做一下梳理,以便在后续开发场景中,更好地使用jdk提供的灵活的api. Excutor接口主要用于解耦任务执行者和任务发起者,jdk8的javadoc写的非常详细.因此,Excutor这个系列的类都冲着这个目标来实现,当然,很多实现类更是在特定的

2 java并行基础

我们认真研究如何才能构建一个正确.健壮并且高效的并行系统. 进程与线程 进程(Process):是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础. 进程是线程的容器.程序是指令.数据和其组织形式的描述,进程是程序的实体.进程中可以容纳若干个线程. 进程和线程的关系:线程就是轻量级的进程,是程序执行的最小单位.为什么我们使用多线程而不是多进程?因为线程间的切换调度成本远远小于进程,所以我们使用多线程而不是多进程. 线程的生命周期 线程的所有状

黑马程序员——Java I/O基础知识之I/O流

I/O流基础知识--字节流和字符流 文件存储在硬盘中,是以二进制表示的,只有内存中才能形成字符.数据的来源可以有硬盘,内存,控制台,网络,Java把数据从一个地方转到另一个地方的现象称为流,用InputStream和OutputStream接口来表示,这两个流里面的都是以字节为单位的,后来加入了Reader和Writer,里面操作的是字符,是两个字节为单位的. 字节流 字节流将数据写入文件 try { File file =new File("d:" +File .separator+

java web 程序---jsp连接mysql数据库的实例基础+表格显示

<%@ page language="java" import="java.util.*,java.sql.*" pageEncoding="gb2312"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <body> <center>JSP连接mysql数据库</