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

线程有创建和上下文切换的开销时间,所有单线程序不一定比多线程执行时间慢。(执行时间越短的越明显)
 
 
在命令式编程中,线程间的的通讯机制有两种:共享内存与消息传递
共享内存并发模型中,线程间共享的是公共的状态
消息传递并发模型中,线程间必须显示的通过消息来进行通讯
同步机制在共享内存模型中必须是显示的进行,例如synchronized,而在消息同步机制中同步是隐式的。
 
Java的并发采用的是共享内存模型。
 
堆内存在线程间共享。
局部变量、方法定义参数、异常处理了参数不会再线程间共享,他们不会有内存可见性问题,也不会受内存模型影响。
 
线程间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程已 读、写共享变量的副本。
 
Daemon线程:当Java虚拟机中不存在非Daemon线程时,Java虚拟机将会退出。
可以通过Thread.setDaemon(true)将线程设置为Daemon线程。
Java虚拟机退出时,Daemon线程中的finally块并不一定会执行。
 
启动线程start方法的含义是:当前线程同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start方法的线程。
 
 
Interrupt中断:中断可以理解为线程的一个标示位属性,它便是一个运行中的线程是否被其他线程进行了中断操作。
如果线程在sleep或者wait等(未运行状态)状态调用了interrupt中断,则会报错;
 
过期的suspend()暂停、resume()恢复、stop()停止三个方法:
不建议使用suspend方法时应该调用后,线程不会释放占有的资源即锁,而是占有着资源进入睡眠状态,这样容易引起死锁、
stop方法在终结一个资源时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此导致程序可能工作在不正确的状态下。
 
安全地终止线程:可以在线程类中增加boolean标识符,用于控制是否执行run方法中的代码。
public class Test {
 
public static void main(String[] args) {
PrintClass.flag = true;
Thread thread = new Thread(new PrintClass());
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();//暂停线程执行
thread = new Thread(new PrintClass());
thread.start();
PrintClass.flag = false;//终止执行线程
}
 
static class PrintClass implements Runnable {
public static volatile int i =1;
public static volatile boolean flag =false;
@Override
public void run() {
while(flag&&!Thread.interrupted()) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i++);
}
}
}
}
 
 
volitile关键字用于修饰变量,就时告知程序任何对该变量的访问均需要从共享内存中获得,同时对变量的修改也必须同步刷新会共享内存,他能保证所有线程对变量访问的可见性。
 
synchronized关键字可以修饰方法、同步块的形式来进行使用,主要确保多个线程在同一时刻只能有一个线程处于方法或者同步块中,它确保了线程对变量访问的可见性和排他性。
 
 
任何一个对象都拥有自己的监视器,当这个对象由同步快或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步快或者同步方法,而没有获取到监视器的线程将会被阻塞在同步块或者同步方法的入口处,进入BLOCKED状态。
任何线程对Object(由synchronized修饰)的访问,首先要获得Object的监视器,如果获取失败了,线程进入同步队列,线程状态变为BLOCKED。当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获得。
 
等待与通知机制是任何Java对象都具有的,因为这些方法被定义在所有对象的超类Object上,包括:
wait:调用该方法的线程进入waiting状态,只有等待另外线程的通知或者被中断才会返回,需要注意的是调用wait方法后会释放对象的锁。
notify:通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁。
notifyall:通知所有等待在该对象上的线程。
wait(long):超市等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回。
wait(long,int):对于超时时间更细粒度的控制,可以达到纳秒。
等待与通知机制是指一个线程A调用了对象O的wait方法进入等待状态,而另一个线程B调用了对象O的notify或者notifyAll方法,线程A收到通知后从对象O的wait方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait方法和notify或者notifAll方法的关系就如同开关信号一样,用来完成等待方与通知方之间的交互工作。
 
notify或者notifyAll方法被执行后,调用了wait方法的线程不会立即执行,必须等待notify或者notifyAll方法释放锁即执行完毕后,waiting状态的线程才可以执行。
 
notify方法时将等待队列中的一个等待线程从等待队列中移到同步队列中。
notifyAll方法是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由waiting转变为blocked。
 
等待/通知机制依托于同步机制(synchronized),其目的是确保等待线程从wait方法返回时能够感知到通知线程对变量做出的修改。
 
管道输入/输出流
管道输入输出流与普通文件输入输出流或者网络输入输出流区别在于他主要用于线程间的数据传输,而传输的媒介是内存。
包括:分为面向字节和对象的流;
PipeInputStream、PipeOutputStream
PipeReader、PipeWriter
 
public class Test {
 
public static void main(String[] args) throws IOException {
PipedReader pipedReader = new PipedReader();
PipedWriter pipedWriter = new PipedWriter();
pipedReader.connect(pipedWriter);
Thread thread = new Thread(new ReadClass(pipedReader));
thread.start();
int receive = 0;
while((receive = System.in.read())!=-1) {
pipedWriter.write(receive);
}
}
 
static class ReadClass implements Runnable{
private PipedReader pipedReader;
public ReadClass(PipedReader pipedReader) {
this.pipedReader = pipedReader;
}
@Override
public void run() {
int receive = 0;
 
try {
while((receive = pipedReader.read())!=-1) {
System.out.print((char)receive);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
 
 
 
Thread.join()的使用
如果一个线程A执行了thread.join()方法,其含义是当前线程A等待thread线程终止之后才从thread.join()返回。
当线程A执行join结束时,会调用线程自身的notifyAll方法(调用时在 JVM里实现的,所有JDK里看不到),会通知所有等待在该线程对象上的线程。
 
 
ThreadLocal即线程变量,是一个以ThreadLocal对象为键、任意对象为值得存储结构,这个结构被附带在线程上,也就是说一个线程可以通过一个ThreadLocal对象查询到绑定在这线程上的一个值。
 
 
 
 
Java并发框架与容器
ConcurrentHashMap是线程安全且高效的HashMap。
HashMap在并发执行put操作时会引起死锁循环,是因为多线程会导致HashMap的Entry链表形成环形数据结构,一旦形成环形数据结构,Entry的next节点永远部位空,就会产生死循环获取Entry。
HashTable容器使用synchronized来保证线程安全,但是在线程竞争激烈的情况下HashTable效率非常低下,因为当一个线程访问HashTable的synchronized同步方法时,其他线程如果也访问HashTable的同步方法会进入阻塞或者轮训状态,如果一个线程进行了put操作,其他线程不但不能执行put操作,而且连get操作也不能进行。
ConcurrentHashMap使用锁分段技术可以有效提升并发访问率。
HashTable里面就一把对象锁,但ConcurrentHashMap使用多把锁,没把锁锁住一部分数据,那么多线程访问ConcurrentHashMap中的不同数据段的数据时,线程间就不会存在锁竞争的情况,从而有效提高并发访问效率。
 
 
ConcurrentHashMap中有Segment与HashEntry数组组成,Segment起到可重入锁的作用,每个ConcurrentHashMap中包含一组Segment,每个Segment用于锁定HashEntry数组中的一个元素(链表),当对HashEntry数组中的数据进行修改时,首先要获得与数据对应的Segment锁。
 
减少散列冲突的可以通过再hash的方式,把数据均匀分布到HashEntry数组链表上。
 
 
 
 
 
 
Java中的13个原子操作类
三个基本类型:AtomicInteger、AtomicLong、AtomicBoolean
说明compareAndSet(int expect,int update)方法:第一步获得AtomicInteger中的值,第二步对AtomicInteger中的值加一,第三步检查当前数值是否等于current,等于意味着AtomicInteger的值没有被其他线程修改偶,则将AtomicInteger的当前值更新为update值,如果不等于,则返回false。
 
原子更新数组
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
例如:
int[] value = {1,2};
AtomicIntegerArray ai = new AtomicIntegerArray(value);
 
原子更新引用类型
AtomicReference
 
原子更新字段类
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicStampedFieldUpdater
例如:
//必须使用newUpdater静态方法创建一个更新器,并且设置想要更新的类与属性
AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class,"old");
User userA = new User("Allen",10);
a.getAndIncrement(userA);//old值增加一
输出a.get(userA)时,old值变为11.
 
 
 
CountDownLatch:允许一个或者多个线程等待其他线程完成的操作,
初始化:只能初始化一次,不能修改计数器值。
countDown()方法没调用一次计数器就减一,直到为0.
await()方法会阻塞当前程序执行直到计数器为0
 
CountDownLatch countDownLatch = new CountDownLatch(2);
 
 
CyclicBarrier类作用是让一组线程达到一个屏障时被阻塞,直到最后一个线程达到屏障时,屏障才会开门,所有被屏障的线程才会继续执行。
CyclicBarrier c = new CyclicBarrier(2);
调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程阻塞。
如果有部分线程没有到达屏障(即调用await方法),则线程处于阻塞状态。
 
CyclicBarrier还提供一个高级的构造函数CyclicBarrier(int partis,Runnable barrierAction)用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。
 
CountDownLatch 与CyclicBarrier区别:CyclicBarrier可以通过reset重置,CountDownLatch 的计数器只能使用一次。
 
Semaphore类是用来控制同时访问特定资源的线程数量,他通过协调各个线程,以保证合理的使用公共资源。
 
 

猜你喜欢

转载自www.cnblogs.com/use-D/p/9570312.html