Java多线程编程核心技术,笔记

多线程是异步的(非同步方式,即synchronized、ReentrantLock等),线程被调用的时机是随机的。

使用多线程有两种方式:继承Thread、实现Runnable接口下的run方法。

Thread类实现了Runnable接口,它们之间具有多态关系。

线程是一个子任务,CPU以不确定的方式运行。

Thread.start方式通知线程规划器此线程已经准备就绪,等待调用线程对象的run方法。

如果调用Thread.run方法就不是异步执行了,而是同步。那么次线程对象并不是交给线程规划器来进行处理,而是有main主线程来调用run方法,也就是必须等run方法中的代码执行完毕后才可以执行后面的代码。?????

Thread.run()中返回Thread.currentThread().getName()的main主线程名称;

Thread.start()中返回Thread.currentThread().getName()的类似Thread-0这类线程名称;

自定义线程类中的实例变量针对于其他线程可以有共享与不共享之分:

共享数据的情况就是多个线程可以访问同一个变量。

synchronized可以再任意对象以及方法上加锁,而加锁的这段代码可以称为“互斥区”或者“临界区”。

当一个线程想要执行同步方法里的代码时,线程首先要获得锁,然后在执行代码块中的代码。

currentThread方法可以返回代码段正在被那些线程调用的信息。

线程类中普通方法中Thread.currentThread().getName()的main主线程名称

线程类中普通方法中this.currentThread().getName()的类似Thread-0这类线程名称(即使通过thread.setName("A")设置了线程名称,也会返回类似Thread-0这类线程名称);;

线程类中run方法中Thread.currentThread().getName()的类似Thread-0这类线程名称或者是自定义线程名称(通过thread.setName("A"));

线程类中run方法中this.currentThread().getName()的类似Thread-0这类线程名称(即使通过thread.setName("A")设置了线程名称,也会返回类似Thread-0这类线程名称);

isAlive方法:判断当前线程是否处于活动状态。

活动状态:线程已经启动并且尚未终止都称为活动状态(运行或者正在运行状态)。

sleep()方法:作用是在执行的毫秒数内让当前正在执行的线程休眠(暂停执行),这个正在执行的线程是指this.currentThread()返回的线程。

getId方法:当前执行代码的线程名称为main时,返回的线程Id是1

停止线程有三种方法:

使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。

使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend以及resume一样,都是过期作废的方法,使用它们可能产生不可预期的结果。

Thread.interrupte()方法中断线程。

Thread.interrupte()方法停止正在运行的线程,但是并不是立即停止,调用Thread.interrupte()方法时,仅仅是给当前线程打了一个停止标志,并不是正在停止。

判断线程是否停止:有两种方法this.interrupted()与this.isInterrupted();

this.interrupted():测试当前线程是否已经中断。(静态方法),interrupted方法会出去中断标志。多次调用interrupted方法时,仅第一次会返回true。

this.isInterrupted():测试线程是否已经中断。(正常方法),isInterrupted()方法并未清除中断状态标志,多次调用interrupted方法时,都会返回true。

sleep方法,如果线程在休眠状态被中断,则会报错。

线程退出方式:线程调用interrupt中断方法,然后在线程中使用interrupted方法判断线程中断标志是否为true,如果为true则代表当前线程调用了interrupt中断方法,然后可以有两种选择,一种是使用return返回的方式,第二种是throw exceptions的方式抛出异常。

suspend与resume方法的缺点是如果使用不当,可能造成公共资源同步对象被独占,使得其他线程无法访问公共同步对象。

yield方法:作用是放弃当前的cpu资源,将它让给其他的任务去占用cpu执行时间,但放弃的时间不确定,有可能刚刚放弃,马上又获得cpu时间片。

线程优先级具有继承性,比如A线程启动B线程,那B线程的优先级与A线程的优先级一样。

线程优先级具有随机性,即优先级高的线程不一定每一次都先执行。

方法内部的私有变量,不存在线程安全问题,永远都是线程安全的,这是方法内部的变量是私有的特性造成的。

实例变量非线程安全,多个线程访问同一个对象的实例变量,则有可能出现非线程安全问题。

synchronized取得的是对象锁,而不是把一段代码或者一个方法当做锁。

调用synchronized的方法一定是排队运行的

A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法

A线程先持有object对象的lock锁,B线程如果在这时调用object对象中的synchronized类型的方法,则需要等待,也就是同步。

一个对象的多个synchronized方法有唯一的对象锁

增加了synchronized的方法一定是同步执行的,例如对象A执行完后synchronized方法后,才可执行对象A的其他synchronized方法。或者说在执行一个对象中的synchronized方法时,必须等待该方法执行完毕。

synchronized锁可重入:对象A的synchronized方法可以调用A对象中的其他synchronized方法。

锁重入也适用于父子类关系的synchronized方法调用

synchronized出现异常时,会自动释放锁

synchronized同步不具有继承性,因此对象A不能继承父类的synchronized方法

同一个对象中的不同synchronized代码块,使用的是同一个锁。

同一个对象中的synchronized代码块与synchronized方法使用的是同一个锁

synchronized代码块参数可以使用非this参数,即可以使用对象的变量或者方法的参数(变量必须是对象的变量,不能是方法内的局部变量)

同一个对象中方法A的synchronized方法与方法B的(非this参数)synchronized代码块线程是非安全的

synchronized加在static静态方法上时,是对类进行加锁,而synchronized加载非static方法上时,是给对象上锁

synchronized代码块中参数为非this参数时,如果一个对象中的有两个方法中各自有一个代码块并且参数不为this,则这两个方法执行时为异步方式,即两个线程调用两个方法时,两个方法同时执行。

volatile关键字的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

使用volatile关键字增加了实例变量在多个线程之间的可见性,但是volatile关键字不支持原子性。

关键字volatile是线程同步的轻量级实现,所有volatile性能肯定比synchronized要好,并且volatile只修饰变量,而synchronized可以修饰方法、代码块。

多线程访问volatile不会发生阻塞,而synchronized会发生阻塞。

volatile能保证数据的可见性,但不能保证原子性了,而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。

volatile关键字用于解决变量在多线程之间的可见性。

synchronized关键字解决的事多线程之间访问资源的同步性。

关键字synchronized可以使多个线程访问同一个资源具有同步性,而且他还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能。

sleep与yield是Thread的方法

wait与notify是object类的方法

wait释放锁,notify锁不释放

notify操作会试图唤醒一个因调用wait操作而除去阻塞状态中的线程,如果没有处于阻塞状态的线程,则该命令会被忽略。

wait方法会使调用该方法的线程释放共享资源所,然后从运行状态退出,进入等待队列,直到被再次唤醒。

wait与notify必须在synchronized同步方法或者同步块中调用,如果调用notify与wait是没有持有锁,则报错,notify方法执行后,当前线程不会马上释放该对象 锁,同时wait状态的线程也不能马上获得对象的锁,要等到notify方法的线程执行完后,才会释放对象锁。

每个锁对象有两个队列,一个是就绪队列(存储将要获得锁的线程),一个是阻塞队列(存储被阻塞的线程),wait与notify可以是线程在就绪队列与阻塞队列之间转换。

通过通道进行线程间的通讯:字节流,调用完毕后,需要执行close关闭方法。

PipeInputStream、PipeOutStream

PipeWrite、PipeRead

方法join的作用是等待线程对象销毁。

在调用join的过程中,如果当前线程对象中断了,则当前线程出现异常。

join方法在内部使用了wait方法进行等待,而synchronized关键字使用的是"对象监视器"原理做的他同步。

方法join可以使所属的线程对象X正常执行run方法中的任务,而使当前线程x进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。

join(Long)设定等待时间

join与sleep区别:join方法内部使用了wait方法来实现,所有join方法具有释放锁的特定。而Thread.sleep()方法不能释放锁。

ThreadLocal:主要解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存储数据的盒子,盒子中可以存储每个线程的私有数据。

多个线程中各自线程设置的ThreadLocal变量是独立的。

InheritableThreadLocal可以在子线程中获得父线程中设置的InheritableThreadLocal值。

如果需要在子线程中对父线程中获得的值就行修改,可以继承InheritableThreadLocal类,同时在childValue方法对父类中获得的值进行修改。

但是需要注意的是,如果子线在获取父类InheritableThreadLocal值得时候,父线程对值进行了更改,那么子线程获取的InheritableThreadLocal值还是旧值。

ReentrantLock

调用ReentrantLock对象的lock()方法获得锁,调用unlock方法释放锁。

lock方法的线程持有的是“对象监视器”

ReentrantLock使用Condition实现等待与通知。

synchronized的线程执行notifyAll时,需要通知所有waiting的线程。没有选择权,会出现相当大的效率问题。

Condition对象调用await()方法之前,必须执行ReentrantLock对象的lock方法,否则会报错。(即必须获得“同步监视器”后才可以执行await方法)

Condition类的signal与await相对应。类似于Object类的wait与notify。

如果一个ReentrantLock类获得多个Condition对象,可以根据Condition不同等待或者唤醒指定的Condition对象。

公平锁与非公平锁

锁Lock 分为公平锁与非公平锁。公平锁便是线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序,而非公平锁就是一种获取锁的抢占机制,是随机获取锁的,和公平锁不一样的是就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁,结果也就是不公平了。

ReentrantLock通过构造参数设置公平锁与非公平锁,默认为公平锁,而ReentrantLock(false)为非公平锁。

ReentrantLock类的getHoldCount方法作用是查看当前线程保持次锁定的个数,也就是调用lock方法的次数(针对于lock嵌套即A方法中有lock方法,同时A方法调用了B方法,同时B方法中也调用了lock的方法,在A方法中getHoldCount返回1,在B方法中getHoldCount返回2)

ReentrantLock类的getQueueLength方法作用是返回正等待获取此锁的线程计数器。

ReentrantLock类的getWaitQueueLength(Condition condition)的作用是返回等待与此锁相关的给定条件Condition的线程估计数,比如有5个线程,每个线程都执行了同一个Condition对象的await方法,则调用getWaitQueueLength方法时返回int值为5.

hasQueueThread作用是查询指定的线程是否正在等待获取此锁定。

hasQueueThreads作用是查询是否有线程正在等待获取此锁定。

hasWaiters作用是查询是否有线程正在等待与此锁有关的Condition条件。

isFair()判断是否是公平锁

isHeldByCurrentThread()查看当前线程是否保持此锁定。

isLocked作用是查询此锁定是否有任意线程保持。

void lockInterruptibly()的作用是:如果当前线程未被中断,则获取锁定,如果已经被中断(即线程调用了interrupt方法)则返回异常。

boolean tryLock()方法作用是仅在调用时锁定未被另一个线程保持的情况下,才获得该锁定。

awaitUntil(Calendar.getTime())作用是等待到达指定时间,在开始执行之后的代码,该方法可以被notify或者notifyAll唤醒,即未到达指定时间也可以获得锁,继续执行后续代码。

ReentrantReadWriteLock类的多个读线程可以共享锁,但是只要多个线程中有一个写线程,则所有线程就是互斥锁。

Timer定时器

public class Test {
    public static class TaskService extends TimerTask {
        @Override
        public void run() {
            System.out.println("开始运行"+System.currentTimeMillis());
        }
    }
    public static void main(String[] args) throws ParseException {
//Timer timer = new Timer();
        Timer timer = new Timer();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        TaskService taskService = new TaskService();
        timer.schedule(taskService,simpleDateFormat.parse("2017-02-27 22:13:10"));
    }
}

计划运行时间早于当前时间,则立即执行Task任务。

TimerTask是以队列的方式一个一个被顺序执行的,所以执行的时间有可能和预期的时间不一致,因为前面的任务有可能消耗的时间比较长,则后面的任务运行的时间也会被延期。

schedule(TimerTask task,Date firstTime,long period)方法的作用是在指定日期后,按指定的时间间隔周期性的无限循环地执行某一任务。 period参数为间隔时间(毫秒)

TimerTask类的cancel方法作用是将自身从任务队列中清除。其他任务不受影响。

public class Test {
    public static class TaskServiceA extends TimerTask {
        @Override
        public void run() {
            System.out.println("A开始运行"+System.currentTimeMillis());
            this.cancel();
        }
    }
    public static class TaskServiceB extends TimerTask {
        @Override
        public void run() {
            System.out.println("B开始运行"+System.currentTimeMillis());
        }
    }
    public static void main(String[] args) throws ParseException {
//Timer timer = new Timer();
        Timer timer = new Timer();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        TaskServiceA taskServiceA = new TaskServiceA();
        timer.schedule(taskServiceA,simpleDateFormat.parse("2017-02-27 22:13:10"),1000);
        TaskServiceB taskServiceB = new TaskServiceB();
        timer.schedule(taskServiceB,simpleDateFormat.parse("2017-02-27 22:13:10"),1000);
    }
}

A开始运行1488209066938

B开始运行1488209066939

B开始运行1488209067939

B开始运行1488209068965

B开始运行1488209069965

B开始运行1488209070965

.....

Timer类的cance方法作用是将任务队列中的全部任务清空。

public class Test {
    public static Timer timer = new Timer();
    public static class TaskServiceA extends TimerTask {
        @Override
        public void run() {
            System.out.println("A开始运行"+System.currentTimeMillis());
            timer.cancel();
        }
    }
    public static class TaskServiceB extends TimerTask {
        @Override
        public void run() {
            System.out.println("B开始运行" + System.currentTimeMillis());
        }
    }
    public static void main(String[] args) throws ParseException {
//Timer timer = new Timer();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        TaskServiceA taskServiceA = new TaskServiceA();
        timer.schedule(taskServiceA,simpleDateFormat.parse("2017-02-27 22:13:10"),1000);
        TaskServiceB taskServiceB = new TaskServiceB();
        timer.schedule(taskServiceB,simpleDateFormat.parse("2017-02-27 22:13:10"),1000);
    }
}

A开始运行1488209178347

Timer类的cance方法作用是将任务队列中的全部任务清空,但是有时候并不一定会停止执行计划任务,而是继续正常执行任务,原因是Timer的cancel方法并没有获得queue锁。

源码:

public void cancel() {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.clear();
            queue.notify(); // In case queue was already empty.
        }
    }

schedule(TimerTask task,long delay)方法的作用是在当前的时间为参考时间,在此时间基础上延迟指定的毫秒数后执行一次TimerTask任务。

方法schedule与scheduleAtFixRate方法都是按顺序执行的,不需要考虑非线程安全问题

方法schedule与scheduleAtFixRate方法主要区别只在于不延时的情况。

方法schedule如果执行任务的时间没有被延时,那么下一次任务的执行时间参考的是上一次任务的开始时间来计算。

scheduleAtFixRate方法如果执行任务的时间没有被延时,那么下一次任务的执行时间参考的是上一次任务的结束时间来计算。

Thread.sleep()方法执行后,进入TIMED_WAITING状态

BLOCKED阻塞状态出现在某一个线程在等待锁的时候。

WAITING状态是Object.wait()方法后所处的状态