线程 / 锁(Java并发编程的艺术笔记)

线程简介

什么是线程

OS在运行一个程序时,会为其创建一个进程。例如:在启动一个Java程序时,就会创建一个Java程序。现代OS调度的最小单位是线程(轻量级进程)。在一个进程里可以创建多个线程,这些线程拥有各自的计数器,堆栈和局部变量等属性,并且能访问共享的内存变量。处理器在这些线程上高速切换,让人感觉这些线程在同时执行。

一个Java程序从main()方法开始执行,执行main()方法的是一个名为main的线程

 

为什么使用多线程

(1)更多的处理器核心

线程是大多数OS调度的基本单元,一个程序作为一个进程运行,程序运行过程中能创建多个线程,而一个线程在一个时刻只能运行在一个处理器核心。而现代处理器上的核心数量越来越多,如果是单线程程序,在运行时只能使用一个核心,那么再多的处理器核心加入也无法提升程序的执行效率;而如果程序是多线程技术,将计算逻辑分配到多个核心上,就会显著减少程序的处理时间。

(2)更快的响应时间

有时我们会编写一些较为复杂的代码,比如说:一笔订单的创建,它包括插入订单数据,生成订单,发送邮件通知卖家等业务操作。用户从点击订购按钮开始,就要等到这些操作全部完成,才能看到订购成功的结果。对于这么多的业务操作,我们可以使用多线程技术,将数据一致性不强的操作发给其他线程处理。这样就可以缩短响应时间。

 

线程优先级

现代OS会分出一个个时间片,而每个线程会分配若干时间片。当线程的时间片用完后,就会发生线程调度。等待下次分配。而线程优先级则决定了线程需要多或者少分配一些处理器资源的属性。

在Java线程中,通过一个整型成员变量priority来控制优先级,在线程构建的时候可以通过setPriority(int)方法来修改优先级。优先级的范围从1~10,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。

设置线程优先级时,针对频繁阻塞(休眠或者I/O操 作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较 低的优先级,确保处理器不会被独占。

/*
        比较5个低优先级和5个高优先级的线程
        yield()方法来让掉CPU,重新争抢。判断低优先级和高优先级的争抢强度
 */
public class Priority {
    private static volatile boolean notStart = true;
    private static volatile boolean notEnd = true;

    static class MyTask implements Runnable {
        private long count;
        private int priority;

        public MyTask(int priority) {
            this.priority = priority;
        }

        @Override
        public void run() {
            while(notStart) {
                Thread.yield();
            }
            while(notEnd) {
                Thread.yield();         //正在运行的线程重新就绪,重新竞争CPU
                count++;
            }
        }
    }

    public static void main(String[] args) throws Exception{
        List<MyTask> taskList = new ArrayList<>();
        for(int i = 0; i < 10; i++) {       // 0~4的优先级为1,之后的优先级为10
            int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
            MyTask task = new MyTask(priority);
            taskList.add(task);
            Thread thread = new Thread(task, "Thread:" + i);
            thread.setPriority(priority);
            thread.start();
        }

        notStart = false;            //开始计数
        TimeUnit.SECONDS.sleep(10);
        notEnd = false;             //结束
        for(MyTask task : taskList) {
            System.out.println("优先级:" + task.priority + "  总计数:" + task.count);
        }
    }
}

本人在win10下运行,两者的结果相差还是较大。但书中的运行环境为MAC和Ubuntu,两者的输出非常相近。

这表示程序正确性不能依赖线程的优先级高低,OS可以完全不理会Java线程对优先级的设定。

 

线程的状态

注:Java将OS中的运行和就绪合并为运行状态;阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块时的状态,但阻塞在进入java.concurrent包中Lock接口的线程状态却是等待状态,因为该接口对于阻塞的实现均使用了LockSupport类的相关方法。

 

守护线程

只要当前JVM中存在一个非守护线程没有结束,守护线程就全部工作。通过调用Thread.setDaemon(true)将线程设置为守护线程。

守护线程主要被用作程序中后台调度和支持性工作。

注:

  • Daemon属性需要在启动线程之前设置,不能在启动线程之后设置。
  • JVM退出时,守护线程中的finally块并不一定会执行,如下面的代码所示。因此不能依靠finally块中的内容来确保执行关闭或清理资源。
public class Daemon {
    public static void main(String[] args) {
        Thread thread = new Thread(new DaemonRunner(), "DaemonRunner");
        thread.setDaemon(true);
        thread.start();
    }
    static class DaemonRunner implements Runnable {
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                    e.printStackTrace();
            } finally {
                System.out.println("DaemonThread finally run.");
            }
        }
    }
}

 

中断

它表示一个运行中的线程是否被其他线程进行了中断操作,其他线程通过调用该线程x的interrupt()方法对x进行中断。

线程通过方法isInterrupted()来判断是否被中断,不过如果该线程已处于终结状态,即便该线程被中断过,调用该线程对象的isInterrupted()时依旧会返 回false。同时可以调用静态方法Thread.interrupted()对当前线程中断标识位进行复位。

许多声明抛出InterruptedException的方法(例如Thread.sleep(long millis)方法),这些方法在抛出异常前,JVM会先将该线程的中断标识位清除,然后抛异常,此时调用isInterrupted()方法返回的是false。

 

安全的终止线程

除了中断以外,还可以利用一个boolean变量来控制是否要停止任务并终止线程。

public class CountThread {
    private static class RunnerRunnable implements Runnable {
        private long i;
        private volatile boolean isRun = true;
        @Override
        public void run() {
            while(isRun && !Thread.currentThread().isInterrupted()) {
                i++;
            }
            System.out.println("i = " +i);
        }

        public void needStop() {
            isRun = false;
        }
    }

    public static void main(String[] args) throws InterruptedException{
        RunnerRunnable rr = new RunnerRunnable();
        Thread countThread = new Thread(rr, "countThread");
        countThread.start();
//        (1)随眠1s后通过中断来使计数线程感知到中断后结束,
        TimeUnit.SECONDS.sleep(1);
        countThread.interrupt();

        RunnerRunnable rr2 = new RunnerRunnable();
        Thread countThread2 = new Thread(rr2, "countThread");
        countThread2.start();
//        (2)随眠1s后通过改变isRun变量来使计数线程感知到中断后结束,
        TimeUnit.SECONDS.sleep(1);
        rr2.needStop();
    }
}

 


线程间通信

volatile和synchronized关键字

java支持多个线程同时访问一个对象或对象的成员变量,由于每个线程可以拥有这个变量的拷贝(对象和成员变量分配的内存是在共享内存中的,而每个执行的线程可以拥有一份拷贝),因此线程在执行过程中,一个线程看到的变量不一定是最新的。

关键字volatile用来修饰字段,他可以告知程序任何对变量的访问都需要从共享内存中获取,而对volatile字段的改变必须同步刷新到共享内存。不过,过多的使用volatile是不必要的,因为他会降低程序执行的效率。

关键字synchronized可以修饰方法或同步块。任何一个对象都拥有自己的监视器,当这个对象被同步块或该对象的同步方法调用时,执行方法的线程必须先获取该对象的监视器,才能进入,而没有获取到的线程会进入同步队列,状态变为阻塞状态。

 

等待/通知机制

等待/通知机制是指 一个线程A调用对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,执行后续操作。

需要注意的是:

  • wait()、notify()和notifyAll()时需要先对调用对象加锁。
  • 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的WaitQueue。
  • notify()方法和notifyAll() 方法调用后,被移动的线程由WaitQueue移到 SynchronizedQueue,它的状态由WAITING变为 BLOCKED。当调用notify()或 notifAll()的线程释放锁之后,被移动的线程再次获取到锁并从wait()方法返回继续执行

 

Thread.join()的使用

若一个线程A调用了thread.join(),它的含义是:当前线程A等待thread终止后,才从thread.join()返回。

下面的代码,创建了10个线程类Domino ,这个Domino 线程类有一变量preThread,它是前一个线程的引用。每个线程调用前一个线程的 join()方法。

public class Join {
    static class Domino implements Runnable {
        private Thread preThread;           //该线程类的变量preThread,它是前一个线程的引用。
        public Domino(Thread preThread) {
            this.preThread = preThread;
        }

        @Override
        public void run() {
            try {
                preThread
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
水资源是人类社会的宝贵财富,在生活、工农业生产中是不可缺少的。随着世界人口的增长及工农业生产的发展,需水量也在日益增长,水已经变得比以往任何时候都要珍贵。但是,由于人类的生产和生活,导致水体的污染,水质恶化,使有限的水资源更加紧张。长期以来,油类物质(石油类物质和动植物油)一直是水和土壤中的重要污染源。它不仅对人的身体健康带来极大危害,而且使水质恶化,严重破坏水体生态平衡。因此各国都加强了油类物质对水体和土壤的污染的治理。对于水中油含量的检测,我国处于落后阶段,与国际先进水平存在差距,所以难以满足当今技术水平的要求。为了取得具有代表性的正确数据,使分析数据具有与现代测试技术水平相应的准确性和先进性,不断提高分析成果的可比性和应用效果,检测的方法和仪器是非常重要的。只有保证了这两方面才能保证快速和准确地测量出水中油类污染物含量,以达到保护和治理水污染的目的。开展水中油污染检测方法、技术和检测设备的研究,是提高水污染检测的一条重要措施。通过本课题的研究,探索出一套适合我国国情的水质污染现场检测技术和检测设备,具有广泛的应用前景和科学研究价值。 本课题针对我国水体的油污染,探索一套检测油污染的可行方案和方法,利用非分散红外光度法技术,开发研制具有自主知识产权的适合国情的适于野外便携式的测油仪。利用此仪器,可以检测出被测水样中亚甲基、甲基物质和动植物油脂的污染物含量,为我国众多的环境检测站点监测水体的油污染状况提供依据。
### 内容概要 《计算机试卷1》是一份综合性的计算机基础和应用测试卷,涵盖了计算机硬件、软件、操作系统、网络、多媒体技术等多个领域的知识点。试卷包括单选题和操作应用两大类,单选题部分测试学生对计算机基础知识的掌握,操作应用部分则评估学生对计算机应用软件的实际操作能力。 ### 适用人群 本试卷适用于: - 计算机专业或信息技术相关专业的学生,用于课程学习或考试复习。 - 准备计算机等级考试或职业资格认证的人士,作为实战演练材料。 - 对计算机操作有兴趣的自学者,用于提升个人计算机应用技能。 - 计算机基础教育工作者,作为教学资源或出题参考。 ### 使用场景及目标 1. **学习评估**:作为学校或教育机构对学生计算机基础知识和应用技能的评估工具。 2. **自学测试**:供个人自学者检验自己对计算机知识的掌握程度和操作熟练度。 3. **职业发展**:帮助职场人士通过实际操作练习,提升计算机应用能力,增强工作竞争力。 4. **教学资源**:教师可以用于课堂教学,作为教学内容的补充或学生的课后练习。 5. **竞赛准备**:适合准备计算机相关竞赛的学生,作为强化训练和技能检测的材料。 试卷的目标是通过系统性的题目设计,帮助学生全面复习和巩固计算机基础知识,同时通过实际操作题目,提高学生解决实际问题的能力。通过本试卷的学习与练习,学生将能够更加深入地理解计算机的工作原理,掌握常用软件的使用方法,为未来的学术或职业生涯打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值