cat的第一条博客

来源:互联网 发布:android 网络图片尺寸 编辑:程序博客网 时间:2024/04/18 13:41
1.顺序编程:顺序执行、任何时刻,执行一个步骤


21.1并发的多面性 
需要解决的并发的问题有多个,解决的办法也有多个,但是之间没有一定的对应关系。
并发解决的问题大体上是:速度、设计可管理性
tips:并发通常是提高运行在单处理器上的程序的性能
(看起来不合理,因为将单处理器上的程序执行并发操作,好需要“上下文切换”的开销,看起来更为的耗时,但是这是在没有遇到阻塞的前提下,一单程序存在阻塞,那么程序将不能够执行,直到条件满足才能够执行,那么将使得整个程序不能向下执行。)

最直接实现方式是在操作系统级别下使用进程。(每个进程在自己的环境中执行)
java多线程的最基本的困难在于协调不同线程之间的资源问题。
函数型语言 Golang Erlang

Java(顺序型语言)提供了对多线程的支持
采用的是协作多线程 可抢占式的多线程

21.2 基本的线程机制
并发变化才能是我们可以将程序划分为多个分离的,独立运行的任务(也称为子任务)中每一个都将由执行的线程驱动。

21.2.1定义任务
a.Runable接口(编写run方法

b.Threadyield()对线程调度//必须显示让任务符合到线程上

21.2.2    Thread类
Cpu通过时间切片的方式实现多线程

21.2.3使用Executor(执行器)
用来管理Thread类,从而简化并发编程
Executor在客户端和任务执行之间提供一个间接层,与客户端直接执行任务不同,这个中介对象将执行任务。
ExecutorService exec = Execitor.new CachedThreadPool | FixedThreadPool |SingleThreadPool

需要从任务中返回值使用Callable类

21.2.5休眠
TIMEUNIT.MILLISECOND.sleep(100);

21.2.6优先级
调度器(优先级不会导致死锁)
JDK有是个优先级
优先级是在run的开头设定的,在构造器中设置它们不会有好处

21.3共享受限资源
21.3.1不正确的访问资源
消费者:EvenChecker
生产者:intGenerator

21.3.2 解决共享资源
防止两个任务访问相同资源,至少在关键阶段不能出现。基本上所有并发模式在解决线程冲突问题时,都采用序列化访问共享资源。
(互斥量 mutex)

java提供synchronized共享资源一般是以对象的形式存在的。然后访问这个资源的方法是用synchronized作为标识private,否则synchronized就不能阻止其他任务访问时产生冲突。

使用显示的lock对象
lock对象必须被显示的创建。锁定和释放(缺乏优雅,但对于其他情况它更灵活)
显示的lock对象在加锁和释放锁的方面,相对于內建的synchronized所来说,还赋予你更多细粒度的控制力。这对于实现内部同步结构是很有用的,例如用于遍历链接列表中的节点的节节传递上加锁机制(也成为锁耦合),这种遍历代码必须释放当前节点的锁之前捕获下一个节点的锁。

21.3.3原子性和易变性
一个不正确的认识:"原子操作不需要进行 同步操作控制" (原子操作是不能被线程调用机制中断的,在执行上下文切换之前,原子操作一定能执行完成。)
原子性可以应用于除long 和 double之外的所以基本类型操作之上的"简单操作",JVM将64位的long double分为两个部分的32位,就会产生读写的上下文切换,从而导致“字撕裂”。

对于多核CPU 可视性问题远比原子性问题多的多。
(一个任务做修改,即使在不中断的意义上是原子性的,对其他任务是不可见的。)

volatile关键字还可以确保应用中的可视性,volatile域读写操作会发生在内存中,因而可以确保每个任务都可以看到最新的域值。

21.3.4原子类
AtomicInteger AtomicLong AtomicReference //对于常规编程来说它们很少会派上用场,到那时涉及性能优化的时,它们就有很大的用武之地了。)

21.3.7线程本地存储
防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以使用相同变量的每个不同线程都创建不同的存储。
使用ThreadLocal

21.3.5 临界区
为防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法。通过这种方式分离分离出来的代码被称为临界区,也是使用synchronized关键字实现。synchronized(syncObject) //也被称为同步控制块。

21.4终结任务



线程状态可以处于以下四种状态之一:
1.新建:当线程被创建时,它只是会暂时的处于这种状态。此时它已经分配了必须的系统资源,并执行了初始化。此刻
线程已将有资格获得CPU时间了,之后调度器将把这个线程 转变成可运行状态或者阻塞状态。
2.就绪:在这种状态下,只要调度器能分配时间片给线程,它就可以运行,这不同于死亡和阻塞状态。
3.阻塞:线程能够运行,但有某个机会阻止它运行。当线程处于阻塞状态时,调度器会忽略线程,不分配给线程任何CPU时间。直到线程重新进入了就绪状态,它才能重新执行 。
4.死亡:处于死亡或者终止状态的线程将不再是可调度的,并且再也不会得到CPU时间,它的任务已结束,或不再是可运行的。任务死亡的通常方式是从run()方法返回,但是任务的线程还可以被中断。

任务进入阻塞状态可能有如下原因:
1.通过调用sleep是任务进入休眠状态,在这种情况下,任务将在指定的时间内不会运行 。
2.
通过调用wait使线程挂起。直到线程得到notyfy或者notifyAll线程才会进入就绪状态。
3.任务等待某个输入输出完成。
4.
任务试图在某个对象调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获得任务锁。

21.5 线程之间的协作
当使多个线程同时访问相同的资源的时候,我们通过互斥去实现。现在的问题不是彼此之间的干涉,而是彼此之间的协调。当任务协作是,关键的问题是握手。在互斥之上,我们为任务添加了一种途径,可以使其自身挂起,直至某些外部条件发生变化,表示是时候让这个任务向前开动了为止。

21.5.1 wait 与 notifyAll
wait使你可以等待某个条件发生变化,而改变这个条件超出了当前方法的控制力。wait提供了一种在任务之间对活动同步的方式。
有两种形式的wait。第一种版本是接受毫秒数作为参数,含义与sleep里面的参数一个意义,都是指“在此期间暂停”。但是sleep不同的是,对于wait而言
1.wait期间对象锁是释放的
2.可以通过notify和notifyAll,或者指令时间到期,从wait中恢复执行。
第二种,更通常的形式是wait不接受参数。这种wait将无限的等待下去,直到notiyfy或者notifyAll。

21.6 死锁
死锁:某个任务在等待另个任务,而后者又等待另个任务,这样一直下去,直到这个链条上的任务又在等待第一个任务解放锁。这得到了一个任务之间相互等待的连续循环,没有哪个线程能继续。这就称作死锁。
当两个任务可以修改它们的状态(它们不会堵塞)时,你还可以使用活锁。

死锁的例子:哲学家就餐问题
 
以下四个条件同时满足就会发生死锁
1.互斥条件:任务使用的资源至少有一个条件是不能共享的。
2.至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源
3.资源不能被任务枪占,任务必须把资源释放当做普通事件。
4.循环等待。

防止死锁最简单的方式就是破坏第四个条件。在哲学家就餐这个问题上,可以将最后一个哲学家拿筷子的顺序初始化为先右后左。

21.7 新类库中的构件
21.7.1CountDownLatch:它被用来同步一个或多个任务,强制它们等待由其他任务执行的一组操作。
你可以向CountDownLatch对象设置一个初始计数值,任何在这个对象上调用wait的方法都将阻塞,直到计数值达到0.其他任务在结束其工作时,可以在该对象上调用countDown来减小计数器的值。
CountDownLatch的典型用法就是将一个程序分为n个相互独立的可解决的任务,并创建值为0的CountDownLatch。当每个任务完成时,都会在这个锁存器上调用countDown。等待问题被解决的任务在这个锁存器上调用await,将它们自己拦住,直到锁存器计数结束。

21.7.2CyclicBarrier:使用的情况:你希望创建一组任务,它们并行执行工作,然后进行下一步骤之前等待,直至所有任务都完成。

21.7.3DelayQueue:只是一个无界的BlockingQueue,用于放置实现Delayed接口的对象,其中的对象只能在其到期时才能从队列中取出。这种队列是有序的,即对头对象的延迟到期

0 0