Java并行程序基础
本节是《Java高并发程序设计》第二章的读书笔记,为尊重原创,需要详细阅读的建议购买原著。
2.1 线程和进程
1、进程是操作系统调度资源分配和调度的基本单位,进程相当于线程的容器,线程是轻量级进程,是程序执行的最小单位,使用多线程并行,是因为线程比进程的切换和调度的成本低。
2、线程的生命周期,
new创建线程,start()后,线程进入runnable状态,没有获得临界区资源进入BLOCKED阻塞状态,调用wait()等方法,进入waiting状态,等待某个预期状态出现后重新回到runnable,调用sleep()等方法,进入TIME_WATING状态,时间片用完或异常报错,进入TERMINATED状态。
2.2 初始线程
2.2.1 新建线程
1、继承Thread类,重写run方法,使用start()调用线程的run()
2、实现Runnable,可以用匿名内部类,实现run方法,将Runnable接口传入Thead的构造函数,因为Thread中有一个传入接口的构造函数,会调用接口的run方法,使用start()调用线程的run()。
2.2.2 线程中断
1、如果出现了wait()或sleep()方法,需要用Thread的中断函数
Thread.interrupt() //中断线程,置标志位为true
Thread.isInterrupted() //判断是否中断
Thread.interrupter() //判断是否中断,并清除当前中断中断状态
使用sleep()或wait()方法时遇到中断会抛出checked异常,需要捕获此异常,设置Thread.interrupt()中断状态,通过代码中Thread.isInterrupted()检查中断状态来判断中断。
2.2.3 等待(wait)和通知(notify)
1、wait和notify是属于object方法的,二者都必须在sychronized代码块中使用,因为调用前必须要获取监控对象(monitor)。
2、wait()方法调用后,线程进入wait等待队列,notify()是随机唤醒从等待队列唤醒线程,notifyAll()是唤醒所有。
3、wait()方法调用后会释放所有的锁,保证其他线程能不被阻塞继续执行。sleep不释放锁。
2.2.4 挂起(suspend)和继续执行(resume)线程
1、suspend挂起后,不会释放锁,直到遇到resume()后线程才会继续执行,如果resume()在suspend()方法前执行了,那么系统可能工作异常,因此,这两个方法已经被丢弃。
2.2.5 等待线程结束(join)和谦让(yield)
1、join有两个方法
public final void join() throws InterruptedException
public final synchronized void join(long mills) throws InterruptedException
第一个方法是无限等待,直到目标线程执行完成
第二个方法是到指定时间后,线程继续执行
2、join的底层原理
本质也是调用了wait(),在目标线程执行完成后,调用notifyAll()唤醒所有的线程,因此,不要在Thread对象使用wait和notify等方法,避免引起API工作异常。
3、yield()是让线程让出CPU,但是让出后,同样会加入线程竞争。
2.3 volatile关键字
1、volatile保证了可见性、有序性和原子性
2、可见性和有序性不再赘述。
3、原子性可以理解为是sychronized的轻量级线程,不能保证复合操作和线程的原子性。
4、能避免重排序。
2.4 线程组
ThreadGroup tg = new ThreadGroup("PrintGroup");
1、将功能相同的线程放在一个线程中统一的管理,如使用activeCout()方法,stop()注意和Thread.stop()一样,可能会有数据不一致问题,list()方法等
2、注意的是,创建线程和线程组的命名要尽可能便于理解。
2.5 守护线程
1、用户线程是系统工作的线程,守护线程只有在守护对象结束,才退出。
2、设置守护线程,要在start()前
Thread t = new DaemonT();
t.setDaemon(true);
Java中线程的优先级是1-10,线程.setPriority(优先级),设置线程优先级。
2.6 sychronized关键字
1、使用方法,可以用于对象,实例方法,或者静态方法,代码段
2、加锁对象、方法和代码段的原理(略)
3、给静态方法加锁,本质是给当前的类加锁,因此,可访问实例的非静态方法。
4、注意的是,在创建线程中,要注意使用同一把锁。
5、sychronized保证原子性、可见性、有序性(因为每次只有一个线程执行,因此,只要保证串行化语意就能保证执行结果的一致,因此,有序性并不能防止指令重排序,和volatile不同)。
补充:
synchronized有两种形式上锁,一个是对方法上锁,一个是构造同步代码块。他们的底层实现其实都一样,在进入同步代码之前先获取锁,获取到锁之后锁的计数器+1,同步代码执行完锁的计数器-1,如果获取失败就阻塞式等待锁的释放。只是他们在同步块识别方式上有所不一样,从class字节码文件可以表现出来,一个是通过方法flags标志,一个是monitorenter和monitorexit指令操作。
2.7 隐蔽的错误
1、使用线程不安全的容器,如:ArrayList、HashMap等。
2、使用Integer对象i作为锁对象,如果出现i++,则会因为i++会创建Integer对象,将引用赋值给i,导致加锁对象出现不一致。