线程概述
在操作系统中,每个独立运行的程序就是一个进程,当一个程序进入内存运行时,即变成一个进程。
线程是进程的组成部分,一个线程必须在一个进程之内,而一个进程可以拥有多个线程,一个进程中至少有一个线程。线程是最小的处理单位,线程可以拥有自己的堆栈,计数器和局部变量,但不能拥有系统资源,多个线程共享其所在进程的系统资源。
Java线程模型提供线程所必需的功能指出,基本的Java线程模型有Thread类、Runnable接口、Callable接口和Future接口等,这些线程模型都是面向对象的。
创建线程
基于Java线程模型,创建线程的方式有三种:
- 继承Thead类,重写Thread类中的run()方法,直接创建线程;
- 实现runnable接口,再通过Thread 类和Runnbale的实现类间接创建一个线程;
- 使用Callbable和Future接口间接创建线程。
前面两种较为常见,也比较简单,第三种的创建方式的样例如下:
package me.huding.study.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i = 0;
for(;i < 100;i ++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
public static void main(String[] args){
FutureTask<Integer> task = new FutureTask<>(new CallableTask());
new Thread(task).start();
try {
task.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
for(int i = 1000;i < 1020;i ++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
线程生命周期
线程具有生命周期,当线程被创建并启动后,不会立即进入执行状态,也不会一直处于执行状态。在线程的生命周期中,线程要经过5种状态,新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。线程状态的转换如下图所示:
线程优先级
每个线程执行时都具有一定的优先级,线程的优先级代表该线程的重要程度,当有多个线程同时处于可执行状态并等待获得CPU处理器时,系统将根据各个线程的优先级来调度各线程,优先级高的线程获得CPU时间的机会多,而优先级低的线程则获得较少的执行机会。
每个线程都有默认的优先级,其优先级都与创建该线程的父线程的优先级相同。在默认情况下,主线程具有普通优先级,由主线程创建的子线程也具有普通优先级。
Thread类提供三个静态常量来标识线程的优先级:
- MAX_PRIORITY——最高优先级,其值为10;
- NORM_PRIORITY——普通优先级,其值为5;
- MIN_PRIORITY——最低优先级,其值为1;
Thread类提供了setPriority()方法来对线程的优先级进行设置,而getPriority()方法可以获取线程的优先级。setPriority()方法的参数时一个整数(范围是1~10),也可以使用Thread类提供的三个优先级静态变量。
线程同步
Java使用监控器(也称对象锁)实现同步,每个对象都有一个监视器,使用监控器可以保证一次只允许一个线程执行对象的同步语句。线程通常采用三种方式:同步代码块、同步方法、同步锁。
同步代码块:
使用同步代码块实现同步功能,只需将对实例的访问语句放入一个同步块中,其语法格式为:
synchronized(object){
//需要同步的代码块
}
其中:
- synchronizeds是同步关键字;
- object 是同步监视器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。
同步方法:
[访问修饰符] synchronized 返回类型 方法名([参数列表]) {
// 方法体
}
synchronized 关键字修饰的实例方法无需显式地指定同步监视器,同步方法的同步监视器是this,即该方法所属的对象。
一旦一个线程进入一个实例的任何同步方法,其他线程将不能进入该实例的任何同步方法,但该实例的非同步方法仍然能够被调用。
synchronized 锁定是对象,而不是方法或代码块;synchronized也可以修饰类,当用synchronized修饰类时,表示这个类的所有方法都是synchronized的。
同步锁Lock是一种更强大的线程同步机制,通过显式定义同步锁对象来实现线程同步。同步锁提供了,比同步块、同步方法更加广泛的锁定操作,实现更灵活。
Lock是控制多个线程对共享资源进行访问的工具,能够对共享资源进行独占访问。每次只能有一个线程对Lock对象加锁,线程访问共享资源之前需要先获得Lock对象。某些锁可能允许对共享资源并发访问,如ReadWriterLock(读写锁)。Lock和ReadWriterLock是Java 5 提供的关于锁的两个根接口,并为Lock提供了ReentrantLock实现类,为ReadWriterLock实现了ReentrantReadWriterLock实现类。
ReentrantLock类是常用的可重入同步锁,该类对象可以显式地加锁、释放锁。通常使用ReentrantLock的步骤如下:
1.定义一个ReentrantLock锁对象,该对象是final常量。
private final ReentrantLock lock = new ReentrantLock();
2.在需要保证线程安全的代码之前增加“加锁”操作。
lock.lock();
3.在执行完线程安全的代码后“释放锁”。
lock.unlock();
For example:
class Demo {
private final ReentrantLock lock = new ReentrantLock();
public void run {
lock.lock();
try {
} finally {
lock.unlock();
}
}
}
线程通信
线程通信可以使用Object类中定义的wait(),notify()和notifyAll()方法,使线程之间互相进行事件通知。执行这些方法时,必须拥有相关对象的锁。
- wait()方法:让当前线程等待,并释放对象锁,直到其他线程调用该监视器的notify()或者notifyAll()来唤醒该线程。wait()方法也可以带一个参数,用于指明等待的时间,使用此种方式不需要noitify()或者notifyAll()的唤醒。wait方法只能在同步方法中调用。
- notify()方法:唤醒在此同步监视器上等待的单个线程,解除该线程的阻塞状态。
- notifyAll()方法:唤醒在此同步方法上等待的所有线程,唤醒次序完全由系统来控制。
notify和notifyAll方法只能在同步方法或同步块中使用。wait方法区别sleep方法之处:wait方法调用时会释放对象锁,而sleep不会。
For example:
package me.huding.study.thread;
public class Product {
int n;
// 为true时表示有值可取,false表示需要放入新值
boolean valueSet = false;
// 生产方法
public synchronized void put(int n){
if(valueSet){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.n = n;
valueSet = true;
System.out.println(Thread.currentThread().getName() +"-生产:" + n);
notify();
}
// 消费方法
public synchronized void get(){
if(!valueSet){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
valueSet = false;
System.out.println(Thread.currentThread().getName() +"-消费:" + n);
notify();
}
}
package me.huding.study.thread;
public class Producer implements Runnable {
Product product;
public Producer(Product product) {
this.product = product;
}
@Override
public void run() {
for(int i = 0;i < 10;i ++){
product.put(i);
}
}
}
package me.huding.study.thread;
public class Consumer implements Runnable {
Product product;
public Consumer(Product product) {
super();
this.product = product;
}
@Override
public void run() {
for(int i = 0;i < 10;i ++)
product.get();
}
}
package me.huding.study.thread;
public class WaitNotifyDemo {
public static void main(String[] args) {
Product product = new Product();
Producer producer = new Producer(product);
Consumer consumer = new Consumer(product);
new Thread(producer,"Producer").start();
new Thread(consumer,"Consumer").start();
}
}