资讯专栏INFORMATION COLUMN

多线程

Scholer / 1295人阅读

摘要:线程启动后系统就自动调用方法。守护线程的使用必须在之前设置,否则会跑出一个异常。你不能把正在运行的常规线程设置为守护线程。在线程中产生的新线程也是的。线程的同步控制使用方法可以释放对象锁,使用或可以让等待的一个或所有线程进入就绪状态。

线程的创建 线程:程序中单个顺序的流控制称为线程

一个进程中可以含有多个线程

在操作系统中可以查看线程数

如:在Windows中,在任务管理器,右键,选择列,选中“线程数”

一个进程中的多个线程

分享CPU(并发的或以时间片的方式)
图示如下:

共享内存(如多个线程访问同一对象)

Java从语言级别支持多线程
如:
Object中wait(), notify(),java.lang中的类 Thread

线程体---- run()方法来实现的。

线程启动后,系统就自动调用run()方法。参考相关书:


所以我们自己调用run是不会产生新的线程的。

通常,run()方法执行一个时间较长的操作

如一个循环

显示一系列图片

下载一个文件

创建线程 1. 通过继承Thread类创建线程可以实现新线程:
class MyThread extends Thread {
    public void run() {
        for(int i=0;i<100;i++) {
            System.out.print (" " + i);
            public void run() { ...}
        }
//生成新线程
Thread thread = new Thread(mytask); thread.start();
    }
}

然而,并不推荐上述方式。参阅了《Introduction to Java Programming》:

This approach is, however, not recommended because it mixes the task and the mechanism of running the task. Separating the task from the thread is a preferred design.

因为Thread中还有其他的方法,这样可能会混淆了Thread中固有的机制。
Thread类的方法很多,如下:

所以,更好的方式是重新定义一个类来实现抽象类Runnable类,然后采用基于接口的对象注入的方式新建一个Thread的对象。如第二个方法。

2. 通过向Thread()构造方法传递Runnable对象来创建线程
class MyTask implements Runnable {
    public void run() { ...}
    }
//生成新线程
Thread thread = new Thread(mytask); thread.start();
}

图示如下:

2.1 匿名类及Lambda表达式

使用示例:

public class TestThread4Anonymous {
    public static void main(String args[]) {
        //匿名类
        new Thread(){
            public void run() {
                for(int i=0; i<10; i++)    
                    System.out.println(i);
            }
        }.start();
        //Lambda表达式
        new Thread( ( ) -> { 
            for(int i=0; i<10; i++) 
                System.out.println(" "+ i); 
        } ).start();
    }
}
线程的控制 对线程的基本控制

线程的启动:thread.start()

线程的结束:常用的方法是设定一个标记变量,结束相应的循环及方法。

暂时阻止线程的执行,比如在线程中用暂停方法:thread.sleep( 1000 );

线程的优先级

调用设定线程的优先级setPriority( int priority)方法,默认有以下三种
MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY

后台线程

线程有两种:

一类是普通线程(非Daemon线程)
在Java程序中,若还有非Demon线程,则整个程序就不会结束

一类是Daemon线程(守护线程,后台线程)
使用setDaemon(true);如果普通线程结束了,则后台线程自动终止。

注:垃圾回收线程是后台线程

代码
public class daemon {
    public static void main(String args[]) {
        //创建子线程
        Runnable t = new MyThread2();
        Thread thread = new Thread(t);
        thread.setDaemon(true);
        thread.start();

        System.out.println( "主线程开始运行." );
        try{
            Thread.sleep(500);
        }
        catch(InterruptedException ex){}
        System.out.println("主线程结束,子线程由于设置为守护线程,所以也应该提前结束.");
    }
}

class MyThread2 implements Runnable {
    public void run() {
        for(int i=0; i<10; i++ ){
            System.out.println(  "子线程应该循环10次,当前的第"+i+"次");
            try{ Thread.sleep(100); }
            catch(InterruptedException ex){}
        }
    }
}

输出结果:

主线程开始运行.
子线程应该循环10次,当前的第0次
子线程应该循环10次,当前的第1次
子线程应该循环10次,当前的第2次
子线程应该循环10次,当前的第3次
子线程应该循环10次,当前的第4次
主线程结束,子线程由于设置为守护线程,所以也应该提前结束.

可以从上面看出,守护线程即使在运行中,也应该随着主线程的结束而提前结束。

守护线程的使用:

thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。

在Daemon线程中产生的新线程也是Daemon的。

守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

线程的同步

为什么线程需要同步:
同时运行的线程需要共享数据、就必须考虑其它线程的状态与行为,这时就需要实现同步。

Java实现流程:

Java引入了对象互斥锁的概念,来保证共享数据操作的完整性。
每个对象都对应于一个monitor(监视器),它上面 一个称为“互斥锁(lock, mutex)”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized用来与对象的互斥锁联系。

synchronized的用法(2种)

对代码片断:

synchronized(对象){ 。。。。}

对某个方法:

synchronized放在方法声明中,如
public synchronized void push(char c ){ 。。。。}

以上相当于对synchronized(this), 表示整个方法为同步方法。

线程的同步控制

使用wait()方法可以释放对象锁,使用notify()notifyAll()可以让等待的一个或所有线程进入就绪状态。
Java里面可以将wait()notify()放在synchronized里面,是因为Java是这样处理的:

synchronized代码被执行期间,线程调用对象的wait()方法,会释放对象锁标志,然后进入等待状态,然后由其它线程调用notify()或者notifyAll()方法通知正在等待的线程。

并发的类

JDK1.5中增加了更多的类,以便更灵活地使用锁机制
Lock接口、ReentrantLock类,如下:

ReadWriteLock接口、ReentrantReadWriteLock类
.writeLock(),.lock(), .readLock(),.unlock(),这些方法。

并发API(线程池) Concurrent包

java.util.concurrent包中增加了一些方便的类

常用于很少写入而读取频繁的对象

CopyOnWriteArrayListCopyOnWriteArraySet

ConcurrentHashMap

putIfAbsent(), remove(), replace()

ArrayBlockingQueue

使用线程池 线程池相关的类

ExecutorService 接口、ThreadPoolExecutor 类

Executors 工具类
他们同样位于Concurrent接口的实现下面,如下:

常见的用法

ExecutorService pool = Executors.newCachedThreadPool();

使用其execute( Runnable r)方法

代码
import java.util.concurrent.*;
/** 创建线程池示例
   */
public class ExecutorDemo {
  public static void main(String[] args) {
    // 创建线程池对象
    ExecutorService  executor = Executors.newCachedThreadPool();
    
    // 提交任务到线程池
    executor.execute(new PrintChar("a", 4));
    executor.execute(new PrintChar("b", 2));
    executor.execute(new PrintNum(2));
    //关闭线程池
    executor.shutdown();
   }
}
class PrintChar implements Runnable {
  private char charToPrint; // The character to print
  private int times; // The times to repeat

  /** 给定打印次数,打印特定字符
   */
  public PrintChar(char c, int t) {
    charToPrint = c;
    times = t;
  }

输出结果

aaaabb 1 2
流式操作及并行流

这里是指Java8新增的函数式思想,并不是具体的文件输入输出流。使得某些编程语句更加流畅的表达。
比如,对数组进行流化:

Arrays.stream(a)
    .filter( i -> i>20 )
    .map(i->i*i)
    .sorted()
    .distinct()
    .limit(10)
    .max();
stream的操作种类

流操作分成两类:

中间的 -中间的操作保持流打开状态,并允许后续的操作。

如: filter sorted limit map

末端的 - 末端的操作必须是对流的最终操作。

如: max min count forEach findAny

流步骤

从某个源头获得一个流。

执行一个或更多的中间的操作。

执行一个末端的操作。

如何得到流

对于数组

Arrays.stream(ary)

对于collection (包括List)

list.stream()

对于Map,没有流,但提供了类似的方法

map.putIfAbsent

map.computeIfPresent

map.merge

代码
import java.util.*;
class UseStream
{
    public static void main(String[] args)
    {
        List a = Arrays.asList(1,2,5,7,3);
        System.out.println(
                a.stream()
                        .mapToInt(i->(int)i)
                        .filter( i -> i>2 )
                        .map( i -> i*i )
                        .sorted()
                        .distinct()
                        .limit(10)
                        .max()
        );
    }
}

运行结果:

OptionalInt[49]
流的并行计算(略)

只需将上面代码的.stream()换成 .parallelStream()

其他都不变,就可以实现并行计算

可以说,stream就是为并行运算而生的,它封装的并行计算的大量内部细节。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65946.html

相关文章

  • 线程编程完全指南

    摘要:在这个范围广大的并发技术领域当中多线程编程可以说是基础和核心,大多数抽象并发问题的构思与解决都是基于多线程模型来进行的。一般来说,多线程程序会面临三类问题正确性问题效率问题死锁问题。 多线程编程或者说范围更大的并发编程是一种非常复杂且容易出错的编程方式,但是我们为什么还要冒着风险艰辛地学习各种多线程编程技术、解决各种并发问题呢? 因为并发是整个分布式集群的基础,通过分布式集群不仅可以大...

    mengera88 评论0 收藏0
  • 操作系统:进程、线程

    摘要:实际工作并不是非此即彼,往往都是进程线程结合的方式。操作系统会保证当线程数不大于数目时,不同的线程运行于不同的上改善程序结构。关于操作系统内部如何创建销毁进程线程,即为什么这些操作进程消耗会比线程大,还没有搞明白。 一、浅层理解 进程是资源分配的最小单位,线程是CPU分配的最小单位——简单明了的说明了进程与线程的区别特点,然而在实际工作中并没有什么卵用。 二、多个维度下,进程与线程的优...

    Java3y 评论0 收藏0
  • 线程程序,单核cpu与核cpu如何工作相关的探讨

    摘要:对多线程程序,单核与多核如何工作相关的探讨我们程序员在编码的时候,涉及到技术方案时,往往会忽略掉代码对性能方面的影响,或者没有足够的敏感度来帮助自己判断自己的技术方案对系统性能造成的影响。 对多线程程序,单核cpu与多核cpu如何工作相关的探讨 我们程序员在编码的时候,涉及到技术方案时,往往会忽略掉代码对性能方面的影响,或者没有足够的敏感度来帮助自己判断自己的技术方案对系统性能造成的影...

    Chiclaim 评论0 收藏0
  • 线程程序,单核cpu与核cpu如何工作相关的探讨

    摘要:对多线程程序,单核与多核如何工作相关的探讨我们程序员在编码的时候,涉及到技术方案时,往往会忽略掉代码对性能方面的影响,或者没有足够的敏感度来帮助自己判断自己的技术方案对系统性能造成的影响。 对多线程程序,单核cpu与多核cpu如何工作相关的探讨 我们程序员在编码的时候,涉及到技术方案时,往往会忽略掉代码对性能方面的影响,或者没有足够的敏感度来帮助自己判断自己的技术方案对系统性能造成的影...

    wslongchen 评论0 收藏0
  • Java线程学习(一)Java线程入门

    摘要:最近听很多面试的小伙伴说,网上往往是一篇一篇的多线程的文章,除了书籍没有什么学习多线程的一系列文章。将此线程标记为线程或用户线程。 最近听很多面试的小伙伴说,网上往往是一篇一篇的Java多线程的文章,除了书籍没有什么学习多线程的一系列文章。但是仅仅凭借一两篇文章很难对多线程有系统的学习,而且面试的时候多线程这方面的知识往往也是考察的重点,所以考虑之下决定写一系列关于Java多线程的文章...

    Donne 评论0 收藏0

发表评论

0条评论

Scholer

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<