线程的创建:
- 继承Thread类,并重写run()方法
- 实现Runnable接口,实现接口中run()方法,以该接口实例作为Thread的target创建Thread对象
- 创建callable接口的实现类,并实现call()方法,再使用FutureTask类(Future类的一个实现类)来包装该Callable对象,再以该Future对象作为Thread的target来创建Thread对象。
需要注意:
Callable接口中的call()同样作为线程的入口,与Runnable接口的run()不同在于,call是有返回值的并且可以抛出异常。Callable同样也是函数式编程接口,可以使用lambda表达式创建。
使用callable接口创建线程:
public static voidcreateThreadByCallable() throws InterruptedException, ExecutionException {
FutureTask<String> task = new FutureTask<>(() ->
"this is aThread created by callable"
);
Thread thread = newThread(task);
thread.start();
System.out.println(task.get());
System.out.println(task.isDone());
}
通过调用Future接口中的get方法获得call方法的返回值,用Future接口中的cancel来取消该Future里关联的Callable任务(call()中)
线程的启动:
都是通过调用Thread对象的start()方法。不要直接调用线程对象的run()方法。
线程的生命周期
状态:新建,就绪,运行,阻塞,死亡
控制线程的执行:
join:
当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入join线程执行完为止。 join方法通常由使用线程的程序调用,以将大问题划分成多个小问题,每个小问题分配一个线程,当所有的小问题都得到处理后,再调用主线程来进一步操作。
Join( ) , join(long millis) ,join ( longmillis, int nanos )(毫秒,毫微秒)
Sleep:
使当前线程休眠,使线程进入阻塞状态。sleep是静态方法,最好不要用Thread的实例对象调用它,因为它睡眠的始终是当前正在运行的线程,而不是调用它的线程对象,它只对正在运行状态的线程对象有效。值得注意的是 该方法不会释放同步监视器。sleep方法比yield有更好地可移植性
yield:
线程让步,暂停当前正在执行的线程,与sleep不同的是,该方法不会阻塞线程,只是将线程转入就绪状态。让系统的线程调度器重新调度一次。当某个线程调用yield方法后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会。同样值得注意的是 该方法不会释放同步监视器。
setDaemon:
设置后台线程(守护线程)。当前台线程都死亡后,后台线程会自动死亡。
调用Thread类的 setDaemon(true) ,将该Thread类设置为后台线程。
前台线程创建的子线程一定是前台线程,后台线程创建的子线程一定是后台线程。
要将某个线程设置为后台线程,必须在该线程启动之前,否则会引发IllegalThreadStateException异常。
setPriority(int level):
设置线程优先级: level 1-10
Thread中的三个静态优先级常量:
MAX_PRIORITY : 10
MIN_PRIORITY : 1
NORM_PRIORITY : 5
虽然java提供了10个优先级别,但是这些优先级别需要操作系统的支持。应尽量避免为线程指定优先级,而应使用三个静态常量来设置优先级。
线程同步:
同步代码块:
synchroinzed(lock){
//
}
其中lock为同步监视器,可以使用任何对象作为同步监视器,推荐使用可能被并发访问的共享资源充当同步监视器。
同步方法:
使用synchronized关键字修饰方法。这样相当于直接使用当前对象this充当同步监视器。
同步锁:
由Lock对象充当同步锁对象,显示的定义同步锁对象。比较常用的是ReentrantLock类,使用该Lock对象可以显示的枷锁、释放锁。
ReentrantLocklock = new ReentrantLock();
lock.lock();
try {
//需要保证线程安全的代码
} finally{
lock.unlock();
}
加锁后完成相关操作后最好使用finally块来保证释放锁。
线程间通信:
通过调用object类提供的wait(), notify(),notifyAll(),来协调线程的运行。
这三个方法必须由同步监视器调用。
①对用用synchronized修饰的同步方法,该类的实例this就是同步监视器,所以可以在同步方法中直接调用。
②对用用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用方法。
wait():
使得当前线程等待,直到其他线程调用该同步监视器的notify或者notifyAll方法来唤醒该线程。括号中可以带时间参数。带时间参数则表示时间到后自动苏醒。这个方法会使得当前线程释放对该同步监视器的锁定。
notify():
唤醒在此同步监视器上等待的单个线程。选择被唤醒的线程是任意的。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
notifyAll():
唤醒在此同步监视器上等待的所有线程。同样的,只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
对于直接使用Lock对象来同步的,则系统中不存在隐式的同步监视器。
使用Condition类来协调。充当同步监视器的角色。
①调用Lock实例的newCondition()方法获取Condition实例。
②通过该Condition实例调用await、 signal、signalAll方法。其功能分别与 wait notify notifyall一致。
还可以通过阻塞队列(BlockingQueue)控制线程通信
BlockingQueue(接口):当生产者线程试图向其放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从中取出元素时,如果队列已空在该线程被阻塞。
比较常用的实现类:
ArrayBlockingQueue :基于数组
LinkedBlockingQueue:基于链表
SynchronousQueue:同步队列,存取操作必须交替进行。适用于消费者充足的时候。
put( )存,take ( )取。
线程组(ThreadGroup):
可以用线程组实现对一批线程的管理。对线程组的控制相当于同时控制这些线程。用户创建的线程都属于指定线程组,如果没有显示指定,则该线程属于默认线程组。在默认情况下,子线程和创建他的父线程属于同一个线程组。一旦某个线程加入指定线程组后,该线程将一直属于该线程组,直至该线程死亡。
构造方法:
ThreadGroup(String name):以指定的线程组名字来创建新的线程组。
ThreadGroup(ThreadGroup parent ,String name):以指定的父线程组,指定的名字创建线程组。
将线程加入线程组:
必须在构造方法中指定线程组 。如:
Thread(ThreadGroupgroup,Runnable target),
Thread(ThreadGroup group ,Runnabletarget, String name)
相关方法:
Int activeCount():返回线程组中活动的线程的数目
interrupt():中断此线程组中所有的线程
isDaemon():判断该线程组是否是后台线程组
setDaemon(boolean daemon):将该线程组设置成后台线程组,当后台线程组中的最后一个线程执行结束或者最后一个线程销毁后,后台线程组将自动销毁
需要注意:
后台线程组和后台线程是两个不想关的概念。也就是说将线程组设置为后台线程组并不会改变里面线程的相关信息。
线程池:
系统启动一个新线程的成本是比较高的,因为它涉及到与操作系统的交互。尤其当程序中需要创建大量生存期很短的线程时,使用线程池可以很好的提高性能。线程池在系统启动时即创建大量空闲线程,程序将一个Runnable对象或者一个callable对象传给线程池,线程池就会启动一个线程来执行它,执行完毕后该线程不会死亡,而是再次返回线程池中,等待执行下一个任务。
Executors :工厂类,用于创建线程池