草庐IT

Java并发小结02

star037 2023-03-28 原文

主要参考自《实战Java高并发程序设计》。

线程与进程

进程是计算机系统进行资源分配和调度的基本单位,是线程的容器。

线程是处理器任务调度和执行的基本单位。

这里可以复习一下进程和线程的区别:

  1. 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

  2. 资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

  3. 包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

  4. 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。

  5. 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

  6. 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。

线程的生命周期

-新建 NEW
-就绪 RUNNABLE
-运行 RUNNING
-阻塞 BLOCKED
-死亡 DEAD

线程的所有状态都在Thread中State枚举中定义:

cpublic enum State{ NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED;}

、、NEW状态表示刚刚创建线程还没开始执行。等到线程的start()方法调用时,才表示线程开始执行。当线程开始执行时处于RUNNABLE状态,标识线程所需的一切资源都已经准备好了。如果线程在执行过程中遇到了synchronized同步块,就会进入BLOCKED阻塞状态,知道获得请求的锁。WAITING和TIMED——WAITING都表示等待状态,区别是WAITING会进入一个无时间限制的等待,TIMED_WAITING会进行一个有限的等待。
、、等待在等什么呢?一般来说WAITING的现场正是在等待一些特殊的时间。比如通过wait()方法等待的线程在等待notify()方法,而通过join()方法等待的线程则会等待目标线程的终止。一旦等到了期望的时间,线程就会继续执行,进入RUNNABLE状态。当前线程执行完毕后,则进入TERMINATED状态,表示结束。

线程的基本操作

新建线程

新建线程有三种方式:
-new Thread
-implements Runnable
-implements Callable

new Thread

Thread t1 =new Thread(){
	@override
	public void run(){
		...
	}
};
t1.start();

new一个Thread需要两个方法:run(),start()。

  • start()方法用来创建一个线程并且让这个线程执行run()方法。
  • run()方法是这个线程的任务,提供给使用者重写。

implements Runnable

源代码:

public interface Runnable {
        void run();
}

运用示例:
自定义类实现Runnable并重写run()方法,用Thread实现。

public class CreateThread implements Runnable{
	public static void main(String[] args){
	Thread t1=new Thread(new CreateThread()){
		t1.start();
	}
	@override
	public void run(){
		...
	}
}

上述代码实现了Runnable接口并将该实例传入线程Thread中,这样避免重写Thread.run()方法,单纯使用接口来定义线程Thread,也是最常用的做法。

  • Thread有一个特殊的构造方法:
    public Thread(Runnable target)
    默认的Thread.run()方法就是直接调用内部的Runnable接口。因此使用Runnable接口告诉线程该做什么更为合理。

implements Callable

源代码:

public interface Callable<V> {
        V call() throws Exception;
}

可以发现Callable内部有一个call()方法并且带有返回值。
运用示例:

	public class CallableTest implements Callable<String>{
		private String str;
		public CallableTest(String str){
			this.str=str;
		}
		@override
		public String call() throws Exception{
			···
			return this.str;
		}
	}

自定义类实现Callable并重写call()方法。

那么怎么实现这个线程呢?Thread是没有直接实现Callable的构造函数的。

在设计模式中建立两者之间的关系经常使用适配器。什么是适配器呢?
适配器就是接口转换器。把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

**找到Thread与Callable之间的适配类:FutureTask **

Thread有构造函数 public Thread(Runnable target)
Runnable的子类FutureTask可用于包装Callable或Runnable对象。

使用方式:
重写call()方法,用FutureTask封装Callable类,用Thread去实现线程。

public class CallableTest implements Callable<String>{
   private String str;

    public a01CreateCallableThread(String str) {
        this.str = str;
    }

    @Override
    public String call() throws Exception {
        System.out.println("callable is called ");
        return this.str;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> c1 = new a01CreateCallableThread("xxx");
        FutureTask<String> task = new FutureTask<String>(c1);
        long beginTime = System.currentTimeMillis();
        //创建线程
        new Thread(task).start();
        //调用get()方法阻塞主线程
        String str = task.get();
        long endTime = System.currentTimeMillis();
        System.out.println("hello :" + str);
        System.out.println("time :" + (endTime - beginTime) / 1000);

    }
}

所以Callable与Runnable最大的区别就是Callable的执行方法call()有返回值,换个说法,Callable可以生成有返回值的线程。Future是一种异步任务监视器,以后的章节应该会说明,此处可忽略。

终止线程

一般来说,线程执行完毕就会结束,无需手动关闭。但是一些服务端的后台线程可能会常驻系统,它们通常不会正常终结。
如何正常地关闭一个线程呢?
Thread提供了一个stop()方法,但是已经被弃用了,因为stop()会强行把执行到一半的线程终止,可能会引起数据不一致的问题。

  • 使用退出标志退出线程
    用一个boolean值来控制while循环是否退出
	@Override
	public void run(){
		while(!exit){
		//do something
	}
}

中断线程

Thread.interrupt()

Thread提供interrupt()方法来中断线程(只是给一个中断的标记,并不会真正中断线程,真正中断是因为退出while循环)

提供isInterrupted()方法来判断是否被中断

提供interrupted()方法既可以判断是否中断又可以清除中断状态

示例:

public class a02InterruptThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("~~~t1在执行~~~");
                    if (Thread.interrupted()) {
                        System.out.println("Interrupted!");
                        break;
                    }
                }
            }
        };

        t1.start();
        /**
         * 主线程等待两秒
         */
        Thread.sleep(2000);
        /**
         * 中断t1
         */
        t1.interrupt();
    }
}

结果是t1运行两秒后会中断.

Thread.sleep(long millis)

Thread.sleep()方法会让当前线程休眠一段时间,之后继续执行,期间如果被中断会抛出InterruptedException中断异常.

等待和通知

JDK提供了两个非常重要的方法:Object.wait()和Object.notify(),这两个方法在Object类中.

  • 当一个对象实例调用wait()方法后,当前线程就会在这个对象上等待.比如在线程A中,调用了obj.wait()方法,那么线程A就会停止执行转为等待状态.一直等待到其他线程调用该对象obj.notify()方法为止.

  • 如果一个线程调用了obj.wait()方法,那么它会进入object对象的等待队列(可能有多个线程在等待).当obj.notify()方法被调用时,随机从等待队列中随机选择一个线程唤醒.

  • Object提供notifyAll()方法唤醒等待队列中的所有线程.

需要注意的一点:
wait()和notify()方法都需要先获得目标对象的一个监视器,也就是目标对象需要有锁并且运行wait()或notify()方法的线程还需要获得这个锁.

代码示例:

public class a03WaitXNotify {
    final static Object object = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (object) {
                System.out.println("t1 start");
                try {
                    System.out.println("t1 is waiting");
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t1 is end");
        });

        Thread t2 = new Thread(() -> {
            synchronized (object) {
                System.out.println("t2 start");
                System.out.println("object notify");
                object.notify();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        /**
         * wait 和 notify 方法,都必须在synchronized代码块中执行,目的是为了获得监视器
         * t1 获得监视器,执行object.wait进行等待,并释放监视器
         * t2 获取监视器,执行object.notify释放t1,并且释放监视器
         * t1 被唤醒,继续执行
         */
        t1.start();
        t2.start();
    }
}

结果是:



t1与t2的执行流程如下:

挂起和继续执行

Thread类提供suspend()方法和resume()方法来将线程挂起和继续执行.

已经被废弃.原因是suspend()方法在导致线程暂停的同时并不会释放任何的锁资源.其他任何线程想要访问被它占用的锁都会被牵连.
而且,如果suspend()方法意外的在suspend()方法前面执行就会导致死锁.线程即使suspend()了也在RUNNABLE状态

可以用wait()和notify()来替代,wait()会在等待的时期让出锁资源避免了资源浪费.

代码示例:

public class a03WaitXNotifyKillSuspend {
    /**
     * wait notify 替代 suspend 的线程挂起方案
     */
    public static Object u = new Object();

    public static class ChangeObjectThread extends Thread {
        volatile boolean suspendme = false;

        /**
         * 让线程挂起的方法
         */
        public void suspendMe() {
            suspendme = true;
        }

        /**
         * 让线程继续执行的方法
         */
        public void resumeMe() {
            suspendme = false;
            synchronized (this) {
                notify();
            }
        }

        @Override
        public void run() {
            /**
             * 看这里,之前是对while的中断标志进行改变来控制中断
             */
            while (true) {
                synchronized (this) {
                    /**
                     * 现在获得锁的情况下控制线程是否等待
                     * 判断自己是否被挂起(挂起标志),如果是则等待
                     * 否则正常执行
                     */
                    while (suspendme) {
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    /**
                     * 让出cpu
                     */
                    synchronized (u) {
                        System.out.println(" in ChangeObjectThread");
                    }
                    Thread.yield();
                }
            }
        }
    }

    

    public static void main(String[] args) throws InterruptedException {
        ChangeObjectThread t1 = new ChangeObjectThread();

        t1.start();
        Thread.sleep(1000);


        System.out.println("-----------------------------");
        t1.suspendMe();
        System.out.println("suspend t1 30s");
        Thread.sleep(30000);

        System.out.println("-----------------------------");
        System.out.println("resume t1");
        t1.resumeMe();

    }
}

结果是: t1 输出1s后被挂起30s,之后继续执行.
如果在t1挂起期间此时有另外的线程是可以正常执行的,因为wait()会释放锁资源.

等待线程和谦让

Thread类提供join()来等待线程
提供静态方法yeild()来礼让线程

join():

第一个join()会无线等待,一直阻塞当前线程,知道目标线程执行完毕.
第二个join等待一段时间,即使目标线程没有执行完毕也会向下执行.(不等了)

public class Test {
    private static volatile long _longVal = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(new LoopVolatile1());
        t1.start();
        try {
	//让主线程等待t1执行完毕
            t1.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("final _longVal is: " + _longVal);
    }

    private static class LoopVolatile1 implements Runnable {
        public void run() {
            long val = 0;
            while (val < 100000) {
                _longVal++;
                val++;
            }
        }
    }

结果是100000
如果没有join(),大家可以试一下,结果是0,或者其他数字,因为主线程执行太快了t1都没执行完就结束了.


这里插一个t2线程,大家觉得结果是多少呢? 按理来说主线程和t2等待t1执行完,然后主线程等待t2执行完,结果应该是200000.
但是结果几乎不可能为200000

	public class Test {
    private static volatile long _longVal = 0;

    public static void main(String[] args) {
        Thread t1 = new Thread(new LoopVolatile1());
        t1.start();
        Thread t2 = new Thread(new LoopVolatile2());
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("final _longVal is: " + _longVal);
    }

    private static class LoopVolatile1 implements Runnable {
        public void run() {
            long val = 0;
            while (val < 100000) {
                _longVal++;
                val++;
            }
        }
    }

    private static class LoopVolatile2 implements Runnable {
        public void run() {
            long val = 0;
            while (val < 100000) {
                _longVal++;
                val++;
            }
        }
    }
}


这就涉及到了volatile对复合操作没有原子性,而i++是一个复合操作哦!

下次看volatile

yield():

yield()会让线程让出当前cpu,重新加入争夺cpu的队列.

有关Java并发小结02的更多相关文章

  1. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  2. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  3. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  4. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  5. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  6. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  7. postman——集合——执行集合——测试脚本——pm对象简单示例02 - 2

    //1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json

  8. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  9. 牛客网专项练习30天Pytnon篇第02天 - 2

    1.在Python3中,下列关于数学运算结果正确的是:(B)a=10b=3print(a//b)print(a%b)print(a/b)A.3,3,3.3333...B.3,1,3.3333...C.3.3333...,3.3333...,3D.3.3333...,1,3.3333...解析:    在Python中,//表示地板除(向下取整),%表示取余,/表示除(Python2向下取整返回3)2.如下程序Python2会打印多少个数:(D)k=1000whilek>1:    print(k)k=k/2A.1000 B.10C.11D.9解析:    按照题意每次循环K/2,直到K值小于等

  10. java - 为什么 ruby​​ modulo 与 java/other lang 不同? - 2

    我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.

随机推荐