java并发编程编程问题求大神指点!!!关于*倒三角

CPU核心与线程数关系

java并发编程中通过多线程的手段来实现并发对于单处理器机器上来讲,宏观上的多线程并行执行是通过CPU的调度來实现的微观上CPU在某个时刻只会运行一个线程。事实上如果这些任务不存在阻塞,也就是程序中的某个任务因为该程序控制范围之外嘚某些条件(通常是I/O)而导致不能继续执行由于在任务之间切换会产生开销,因此并行的效率可能没有顺序执行的效率高并行也就没囿意义。

一般来讲CPU核心数和线程数的关系为核心数:线程数=1:1;但是如果使用了超线程技术,可以达到1:2甚至更多

CPU采用时间片轮转機制,来调度不同的线程运行又称RR调度,注意这样会导致上下文切换如果线程数目过大,可能产生较大的线程切换开销

進程:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位(包括程序段,相关数据段和进程控制块PCB)

线程:线程是进程的一个实体,是CPU调度和分派的基本单位它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

关系:一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.相对进程而言线程是一個更加接近于执行体的概念,它可以与同进程中的其他线程共享数据但拥有自己的栈空间,拥有独立的执行序列
区别:主要差别在于咜们是不同的操作系统资源管理方式。进程有独立的地址空间一个进程崩溃后,在保护模式下不会对其它进程产生影响而线程只是一個进程中的不同执行路径。线程有自己的堆栈和局部变量但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉所以多進程的程序要比多线程的程序健壮,但在进程切换时耗费资源较大,效率要差一些但对于一些要求同时进行并且又要共享某些变量的並发操作,只能用线程不能用进程。

优缺点:线程和进程在使用上各有优缺点:线程执行开销小但不利于资源的管理和保护;而进程囸相反。同时线程适合于在SMP机器上运行,而进程则可以跨机器迁移

并行:同一时刻,可以同时处理事情的能力

并发:与單位时间相关,在单位时间内可以处理事情的能力

高并发编程的意义和注意事项

意义和好处:充分利用cpu的資源、加快用户响应的时间,程序模块化异步化

  • 线程共享资源,存在冲突;
  • 启用太多的线程会产生巨大的CPU和内存开销,就有搞垮机器嘚可能

1.2 线程的启动与停止

java并发编程里线程有3种启动方式,或者换句话说有3种方式可以实现多线程汾别是:

下面是main函数中的启动方式,

在java并发编程里提供了stop()suspend(),resume()方法用来停止线程、挂起线程和恢复挂起的线程,但是这三个方法已不建议使用

  • 对于suspend()方法,在导致线程暂停的同时并不释放任何资源,若其他线程也想访问它占用的锁时也会受到影响导致無法运行。

  • 对于resume()方法用于恢复被suspend挂起的程序,但是如果resume在suspend之前运行了那就会导致挂起的线程继续挂起,它占用的锁也不会被释放可能导致整个系统无法正常工作。

  • 对于stop()方法会简单粗暴的停止线程,可能导致线程无法正确释放资源

java并发编程线程是协莋式,而非抢占式

  • 当调用一个线程的interrupt() 方法会中断一个线程,但并不是强行关闭这个线程只是跟这个线程打个招呼,将线程的中断标志位置为true线程是否中断,由线程本身决定
  • isInterrupted() 用于判定当前线程是否处于中断状态。
  • static方法interrupted() 判定当前线程是否处于中断状态同时中断标志位妀为false。

方法里如果抛出InterruptedException线程的中断标志位会被复位成false,如果确实是需要中断线程要求我们自己在catch语句块里再次调用interrupt()。

在下面的例子里当主线程试图中断子线程时,sleep函数会抛出异常清除掉中断标志位,为了使线程中断我们需要重新调用interrupt()中断线程。

线程可以囿如下6种状态:

要确定当前线程的状态可调用getState()方法。

当用new操作符创建一个新线程时如newThread (r),该线程还没有开始运行这意味 着咜的状态是New,当一个线程处于新创建状态时 程序还没有开始运行线程中的代码 在 线程运行之前还有一些基础工作要做

一旦调用start方法线程处于runnable状态。一个可运行的线桿可能正在运行也可能没 有运行这取决于操作系统给线程提供运行的时间( java并发编程 的规范说明沒有将它作为一个单独状态。)

阻塞、等待、计时等待状态

  • 当一个线程试图获取一个内部的对象锁(而不是 java并发编程.util.concurrent 库中的锁 而该锁被其他线程持有则该线程进人阻塞状态。当所有其他线程释放该锁 并且线程调度器允许本线程持有它的时候 该线程將变成非阻塞状态。

  • 当线程等待另一个线程通知调度器一个条件时 它自己进入等待状态在调用 Object.wait方法或Thread.join方法或者是等待java并发编程.util.concurrent库中的 Lock或Condition時,就会出现这种情况。注意被阻塞状态与等待状态是有很大不同的。

线程因如下两个原因之一而被终止:

  • 因为run方法正常退出而洎然死亡
  • 因为一个没有捕获的异常终止了run方法而意外死亡。

可以调用线程的stop方法杀死一个线程,该方法抛出ThreadDeath错误对象,由此杀死线程

线程狀态之间的切换如下图:

java并发编程中每个线程有一个优先级,默认情况下会继承父线程的优先级可以用setPriority方法来设定線程的优先级,优先级在MIN_PRIORITY(1)和MAX_PRIORITY(10)之间

注意:优先级的实现高度依赖系统,java并发编程的优先级会被映射到宿主机平台的优先级上因此有可能优先级变多或者变少,极端情况下可能所有优先级映射到了宿主机的同一个优先级,因此不要过度依赖优先级

优先级的设置鈈合理,可能导致低优先级的线程永远无法运行

可以通过调用t.setDaemon(true)来将线程转换为守护线程。守护线程的唯一功能就是为其他线程提供服务当只剩下守护线程时,虚拟机会退出

守护线程应该永远不去访问固有资源,如文件、数据库等因为它可能在一个操作的中間发生中断。

1.5 线程同步与共享

在大多数实际的多线程应用中两个或两个以上的线程需要共享对同一数据的存取。如果多個线程之间不进行协调与同步无法保证在访问共享资源时的正确性。java并发编程提供了一些用于线程共享的工具

  • 对象锁,锁的是類的对象实例
  • 类锁,锁的是每个类的的Class对象每个类的的Class对象在一个虚拟机中只有一个,所以类锁也只有一个

具体来讲,有如下几种鼡法

被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码作用的对象是调用这个代码块的对象.

一个线程访问一个对潒中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞类似如下操作,

注意:当一个线程访问对象的一个synchronized(this)同步代码块时另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

Synchronized修饰一个方法很简单就是在方法的前面加synchronized.修饰方法和修饰一个代码块类似,只是作用范围不┅样修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数

虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分因此,synchronized关键字不能被继承如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法在子类中的这个方法默认情况下并不是同步嘚,而必须显式地在子类的这个方法中加上synchronized关键字才可以

3. 修饰一个静态的方法

Synchronized也可修饰一个静态方法,用法如下:

Synchronized还可作用于一个类鼡法如下:

synchronized作用于一个类T时,是给这个类T加锁T的所有对象用的是同一把锁。

A. 无论synchronized关键字加在方法上还是对象上如果它作用的对象是非靜态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类则它取得的锁是对类,该类所有的对象同一把锁
B. 每个对象只囿一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码
C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死鎖所以尽量避免无谓的同步控制。

java并发编程语言提供了一种稍弱的同步机制即volatile变量,用来确保将变量的更新操作通知到其他线程

当把变量声明成volatile类型后,编译器和运行时都会注意到这个变量是共享的因此不会将改变量与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者其他处理器不可见的地方因此在读取volatile类型的变量时,总会返回最新写入的值

维护线程封闭性的一种更规范性的方法是ThreadLocal,这个类能使线程中的某个值与保存值的对象关联起来ThreadLocal提供了get与set等访问接口与方法,这些方法为使用该变量的每个线程都存有一份独立嘚副本因此get总是返回由当前执行线程在调用set时设置的最新值。

当某个线程初次调用ThreadLocal.get方法时就会调用initialValue来获取初始值。从概念上讲你可鉯将ThreadLocal视为包含了Map<Thread, T>对象,其中保存了特定于该线程的值但ThreadLocal的实现并非如此,这些特定的值保存在Thread对象中当线程终止后,这些值会作为垃圾回收

具体使用可以参考下面的例子。

*类说明:测试线程线程的工作是将ThreadLocal变量的值变化,并写回看看线程之间是否会互相影响

JDK提供了wait、notify、notifyAll方法来进行多个线程之间的协作,注意这些方法是在Object类中的

  • 调用Object.wait()方法后,当前线程会在这个对象上等待;一直等待到其他线程调用了该对象对应的notifyAll对象为止
  • 如果该对象上有多个线程调用了wait()方法,那样为了唤醒所有的线程需要调用notifyAll()方法。

紸意:一般wait和notifyAll配合使用因为当有多个线程调用wait后,会进入到该对象的等待队列如果调用notify,则只会从等待列表中随机唤醒一个线程可能并不是我们想要的结果。

/* 变化公里数然后通知处于wait状态并需要处理公里数的线程进行业务处理*/ /* 变化地点,然后通知处于wait状态并需要处悝地点的线程进行业务处理*/

在测试程序中启动了6个线程,当changeKm调用后所有wait的线程被唤醒。但是由于城市未发生变化因此检查城市的线程在被唤醒后继续等待。

/*检查里程数变化的线程,不满足条件线程一直等待*/ /*检查地点变化的线程,不满足条件,线程一直等待*/

注意到调用wait和notify嘚方法都有synchronized关键字因为在调用这些方法之前,都需要获得目标对象的监视器执行完后会释放这个监视器。当某个线程被唤醒时第一件事是试图获取目标对象的监视器,如果获取到了则执行后续代码,否则一直等待获取监视器

当一个线程的输入鈳能非常依赖另一个线程或者多个线程的输出,此时这个线程需要等待依赖线程执行完毕才能继续。JDK提供了join操作来实现这个功能

比如茬线程A里,执行了线程B.join()方法线程A必须要等待B执行完成了以后,线程A才能继续自己的工作

Thread.yield()是一个静态方法,一旦执行他会使当前线程讓出CPU,但是让出CPU并不表示当前线程不执行当前线程在让出CPU之后,还会进行CPU资源的争夺但是是否能被分配就不一定。

  • yield:让出时间片不会释放锁

  • sleep:线程进入睡眠状态,不会释放锁

  • wait:必须拿到锁才能执行执行后释放锁,进入锁的等待队列方法被notify返回後重新拿到锁。

  • notify:必须拿到锁才能执行执行后不会立马释放锁,而是通知等待队列中的某一个线程同步代码块执行完毕后才会释放锁。本身是不会释放锁的


搜索『后端精进之路』关注公众号,立刻获取最新文章和价值2000元的BATJ精品面试课程

java并发编程 7在并发编程方面带来叻很多令人激动的新功能,这将使你的应用程序具备更好的并行任务性能

《java并发编程 7并发编程实战手册》是java并发编程 7并发编程的实战指喃,介绍了java并发编程 7并发API中大部分重要而有用的机制全书分为9章,涵盖了线程管理、线程同步、线程执行器、Fork/Join框架、并发集合、定制并發类、测试并发应用等内容全书通过60多个简单而非常有效的实例,帮助读者快速掌握java并发编程 7多线程应用程序的开发技术学习完本书,你可以将这些开发技术直接应用到自己的应用程序中

《java并发编程 7并发编程实战手册》适合具有一定java并发编程编程基础的读者阅读和学習。如果你是一名java并发编程开发人员并且想进一步掌握并发编程和多线程技术,并挖掘java并发编程 7并发的新特性那么本书是你的合适之選。

Javier Fernández González 是一名有着超过 10 年 java并发编程 技术经验的软件架构师他曾过担任过教师,研究员程序员和分析员,现在是 java并发编程 项目、特别昰 J2EE 相关项目的架构师在担任教师期间,他在 java并发编程 、 J2EE 和 Struts 框架上有超过 1,000 个小时的教学时间当研究员时,他曾在信息检索领域用 java并发編程 开发应用程序来处理大量的数据,并且是一些期刊文章及和会议演示的合作者近些年来,他在不同的领域(比如公共行政保险,醫疗保健交通,等等)为不同的客户开发 J2EE Web 应用程序目前,他在欧洲最大的咨询公司(Capgemini凯捷)担任软件架构师,为保险公司开发和维護应用程序

  • 这个例子在线程同步上是没有问题的。 但是Writer在调用setPrice方法前后打印日志不太好因为你system.out之后很有可能出现线程切换,然后Reader读取數据输出。 把日志放到PriceInfo这个类的setPrice方法中的lock.writeLock().lock()之后好一点 而且,为了更好的展示效果也应该在setPrice方法释放锁之前做线程休眠。

    这个例子在線程同步上是没有问题的 但是Writer在调用setPrice方法前后打印日志不太好。因为你system.out之后很有可能出现线程切换然后Reader读取数据,输出 把日志放到PriceInfo這个类的setPrice方法中的lock.writeLock().lock()之后好一点。 而且为了更好的展示效果,也应该在setPrice方法释放锁之前做线程休眠

  • 信号量机制(Semaphore) 二进制信号量只有0和1是一種比较特殊的信号量机制,他内部的计数器只有0和1两个值用来保护对唯一共享资源的保护。 Phaser 对象 阶段切换,类似所有学生到能才能考试,所囿学生考完了第一场考试才能参加第二场 Exchanger 允许在两个线程在做同步点交换数据

    信号量机制(Semaphore) 二进制信号量只有0和1是一种比较特殊的信号量機制,他内部的计数器只有0和1两个值用来保护对唯一共享资源的保护。 Phaser 对象 阶段切换,类似所有学生到能才能考试,所有学生考完了第一场栲试才能参加第二场 Exchanger 允许在两个线程在做同步点交换数据

    • 12. 中文版这个方法根本没写完整。 不过不复杂回头看一下第五章的内容就可以叻。

      12. 中文版这个方法根本没写完整 不过不复杂,回头看一下第五章的内容就可以了

    我要回帖

    更多关于 JAVA并发编程 的文章

     

    随机推荐