程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process):是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。
运行中的QQ,运行中的MP3播放器;
程序是静态的,进程是动态的;
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。
线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的;
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小;
- 一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患;
- 每个Java程序都有一个隐含的主线程:
main
方法。
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。
如果是多核的话,才能更好的发挥多线程的效率,现在的服务器都是多核的。
- 程序需要同时执行两个或多个任务;
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等;
- 需要一些后台运行的程序时。
Java语言的
JVM
允许程序运行多个线程,它通过java.lang.Thread
类来体现。
Thread
类的特性:
- 每个线程都是通过某个特定
Thread
对象的run()
方法来完成操作的,经常把run()
方法的主体称为线程体;- 通过该
Thread
对象的start()
方法来启动这个线程,而非直接调用run()
。构造器:
Thread()
:创建新的Thread
对象;Thread(String threadname)
:创建线程并指定线程实例名;Thread(Runnable target)
:指定创建线程的目标对象,它实现了Runnable
接口中的run
方法;Thread(Runnable target, String name)
:创建新的Thread
对象。相关方法:
start()
:启动线程,并执行对象的run()
方法;run()
:线程在被调用时执行的操作;getName()
:返回线程的名称;setName()
:设置线程的名称;currentThread()
:返回当前线程。
创建线程的方式:
- 继承
Thread
类的方式- 实现
Runnable
接口的方式- 实现
Runnable
接口- 使用线程池
Thread
类的步骤:
- 定义子类继承
Thread
类;- 子类重写
Thread
类中的run
方法;- 创建
Thread
子类对象,即创建线程对象;- 调用线程对象
start
方法(启动线程,调用run
方法)。
public class ChatThread extends Thread{
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("聊天");
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class DrinkThread extends Thread {
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("喝酒");
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class EatThread extends Thread{
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("吃饭");
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class MyTest2 {
public static void main(String[] args) {
//创建线程
ChatThread chat = new ChatThread();
DrinkThread drink = new DrinkThread();
EatThread drink = new EatThread();
//启动线程
chat.start();
drink.start();
drink.start();
}
}
Runnable
接口步骤:
- 定义子类实现
Runnable
接口;- 子类中重写
Runnable
接口中的run
方法;- 通过
Thread
类构造方法创建线程对象;- 将
Runnable
接口的子类对象作为实际参数传递给Thread
类的构造方法中;- 调用
Thread
类的start
方法:开启线程,调用Runnable
子类接口的run
方法。
public class ChatThread implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("聊天");
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class DrinkThread implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("喝酒");
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class EatThread implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("吃饭");
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class MyTest2 {
public static void main(String[] args) {
Thread chat = new Thread(new ChatThread());
Thread drink = new Thread(new DrinkThread());
Thread eat = new Thread(new EatThread());
chat.start();
drink.start();
eat.start();
}
}
优势:
- 避免了单继承的局限性;
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
Callable
接口与使用
Runnable
相比,Callable
功能更强大些
- 相比
run()
方法,可以有返回值;- 方法可以抛出异常;
- 支持泛型的返回值;
- 需要借助
FutureTask
类,比如获取返回结果。关于
Future
接口
- 可以对具体
Runnable
、Callable
任务的执行结果进行取消、查询是否完成、获取结果等;FutrueTask
是Futrue
接口的唯一的实现类;FutureTask
同时实现了Runnable
、Future
接口。它既可以作为Runnable
被线程执行,又可以作为Future
得到Callable
的返回值。
import java.util.concurrent.Callable;
/**
* 使用实现Callable接口的方式创建线程
*/
public class NumThread implements Callable<Integer> {
//重写call方法
@Override
public Integer call() throws Exception {
//1+2+3+4+...+100
int sum = 0;
for(int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
public class MyTest8 {
public static void main(String[] args) {
NumThread numThread = new NumThread();
FutureTask<Integer> task = new FutureTask<>(numThread);
//创建线程
Thread thread = new Thread(task);
//启动线程
thread.start();
//设置线程优先级
thread.setPriority(Thread.MAX_PRIORITY);
try {
Integer sum = task.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理:
corePoolSize
:核心池的大小maximumPoolSize
:最大线程数keepAliveTime
:线程没有任务时最多保持多长时间后会终止
JDK 5.0起提供了线程池相关API:
ExecutorService
和Executors
ExecutorService
:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command)
:执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task)
:执行任务,有返回值,一般又来执行Callable
void shutdown()
:关闭连接池
Executors
:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool()
:创建一个可根据需要创建新线程的线程池Executors.newFixedThreadPool(n)
:创建一个可重用固定线程数的线程池Executors.newSingleThreadExecutor()
:创建一个只有一个线程的线程池Executors.newScheduledThreadPool(n)
:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
public class NumberThread implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class NumberThread1 implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if(i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class MyTest9{
public static void main(String[] args) {
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
NumberThread numberThread = new NumberThread();
NumberThread1 numberThread1 = new NumberThread1();
//执行线程
service.execute(numberThread);
service.execute(numberThread1);
//关闭线程
service.shutdown();
}
}
调度策略:
- 基于时间片轮转;
- 抢占式:高优先级的线程抢占CPU。
调度方法:
- 同优先级线程组成先进先出队列,使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
- Java中线程优先级的范围是1~10,默认的优先级是5
MAX_PRIORITY(10)
MIN_PRIORITY(1)
NORM_PRIORITY(5)
涉及的方法:
setPriority(int newPriority)
:设置线程的优先级getPriority()
:获取线程的优先级yield()
:线程让步
- 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- 若队列中没有同优先级的线程,忽略此方法
join()
:
- 当某个程序执行流中调用其他线程的
join()
方法时,调用线程将被阻塞,直到join()
方法加入的join
线程执行完为止- 低优先级的线程也可以获得执行
sleep(long millis)
:令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队stop()
:强制线程生命期结束isAlive()
:判断线程是否还活着
public class MyTest3 {
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().setName("主线程");
MyThread mythread = new MyThread();
mythread.setName("子线程");
mythread.start();
//设置线程的优先级
mythread.setPriority(Thread.MAX_PRIORITY);
//打印100行%%%%%
for (int i = 0; i < 30; i++) {
System.out.println("%%%%%" + Thread.currentThread().getName() + ":" + i);
if(i == 2) {
//Thread.currentThread().yield();
//mythread.stop();
mythread.join();
System.out.println(mythread.isAlive());
}
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
新建: 当一个
Thread
类或其子类的对象被声明并创建时,新生的线程对象处于新建状态;就绪:处于新建状态的线程被
start()
后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件;运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态,
run()
方法定义了线程的操作和功能;阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态;
死亡:线程完成了它的全部工作或线程被提前强制性地中止。
public class Window implements Runnable {
private int count = 100;
@Override
public void run() {
while(true) {
if(count > 0) {
try {
Thread.currentThread().sleep(6);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
} else {
break;
}
}
}
}
public class MyTest4 {
public static void main(String[] args) {
Window window = new Window();
Thread window1 = new Thread(window);
Thread window2 = new Thread(window);
Thread window3 = new Thread(window);
window1.start();
window2.start();
window3.start();
}
}
现象:
- 多个窗口卖同一张票;
- 票的编号出现了0或者负值。
问题的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决思路:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式:同步机制。
synchronized
代码块public class Window1 implements Runnable {
private int count = 100;
@Override
public void run() {
while(true) {
//只剩一张票
/* synchronized代码块运行完毕,才会切换到另一个线程执行
* 什么样的内容要放到synchronized代码块中?
* 涉及到多个线程修改的地方要放到synchronized代码块中
* 锁:一定要是所有线程共享的
*/
synchronized (this) {
if(count > 0) {
System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
} else {
break;
}
}
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//所有窗口共用100张票
public class Window2 implements Runnable {
private int count = 100;
@Override
public void run() {
while(true) {
sale();
if(count <= 0) {
break;
}
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//同步方法 该方法运行完毕之后,其他的线程才会运行
public synchronized void sale() {
if(count > 0) {
System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
}
}
}
Lock
锁从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用
Lock
对象充当。
java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock
类实现了Lock
,它拥有与synchronized
相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock
,可以显式加锁、释放锁。
import java.util.concurrent.locks.ReentrantLock;
public class Window3 implements Runnable {
private int count = 100;
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while(true) {
try {
lock.lock();
if(count > 0) {
System.out.println(Thread.currentThread().getName() + "卖第" + (count--) + "张票");
} else {
break;
}
} finally {
lock.unlock();
}
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//synchronized代码块方式
public class SingleObject {
private static SingleObject obj = null;
private SingleObject() {
}
public static SingleObject getInstance() {
synchronized(SingleObject.class) {
if(obj == null) {
obj = new SingleObject();
}
}
return obj;
}
}
//synchronized方法方式
public class SingleObject {
private static SingleObject obj = null;
private SingleObject() {
}
public static synchronized SingleObject getInstance() {
if(obj == null) {
obj = new SingleObject();
}
return obj;
}
}
//Lock方式
public class SingleObject {
private static SingleObject obj = null;
private static ReentrantLock lock = new ReentrantLock(true);
private SingleObject() {
}
public static SingleObject getInstance() {
try {
lock.lock();
if(obj == null) {
obj = new SingleObject();
}
} finally {
lock.unlock();
}
return obj;
}
}
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
public class Lock {
static Object objA = new Object();
static Object objB = new Object();
}
public class MyThreadA extends Thread {
@Override
public void run() {
while(true) {
synchronized (Lock.objA) {
synchronized (Lock.objB) {
System.out.println("MyThreadA...");
}
}
}
}
}
public class MyThreadB extends Thread {
@Override
public void run() {
while(true) {
synchronized (Lock.objB) {
synchronized (Lock.objA) {
System.out.println("MyThreadB...");
}
}
}
}
}
public class MyTest5 {
public static void main(String[] args) {
MyThreadA a = new MyThreadA();
MyThreadB b = new MyThreadB();
a.setPriority(Thread.MAX_PRIORITY);
b.setPriority(Thread.MIN_PRIORITY);
a.start();
b.start();
}
}
解决方法:
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
相关方法:
wait()
:令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()
或notifyAll()
方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行;notify()
:唤醒正在排队等待同步资源的线程中优先级最高者结束等待;notifyAll()
:唤醒正在排队等待资源的所有线程结束等待。以上三个方法只有在
synchronized
方法或synchronized
代码块中才能使用。两个线程,交替打印1~100。
public class PrintNum implements Runnable {
int i = 1;
@Override
public void run() {
while (true) {
synchronized (Object.class) {
//唤醒正在排队等待同步资源的线程中优先级最高者结束等待
Object.class.notify();
if (i <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + i++);
} else
break;
try {
//令当前线程挂起并放弃CPU、同步资源并等待
Object.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class MyTest6 {
public static void main(String[] args) {
PrintNum printNum = new PrintNum();
Thread th1 = new Thread(printNum);
Thread th2 = new Thread(printNum);
th1.start();
th2.start();
}
}
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个线程——即所谓的
生产者
和消费者
——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。
import java.util.ArrayList;
import java.util.List;
//仓库
public class Factory {
//最大容量
public static final int MAX_SIZE = 100;
private static List<Object> list = new ArrayList<Object>();
public static List<Object> getList() {
return list;
}
public static void setList(List<Object> list) {
Factory.list = list;
}
}
public class Producer extends Thread {
@Override
public void run() {
while(true) {
synchronized (Factory.getList()) {
//仓库没有满
if(Factory.getList().size() < Factory.MAX_SIZE) {
//生产
Factory.getList().add(new Object());
System.out.println(Thread.currentThread().getName() + ":生产\t" + "总数量:" + Factory.getList().size());
Factory.getList().notifyAll();
try {
Thread.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {//仓库已满
try {
System.out.println(Thread.currentThread().getName() + ":仓库已满,取消生产任务\t" + "总数量:" + Factory.getList().size());
Factory.getList().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Consumer extends Thread {
@Override
public void run() {
while(true) {
synchronized (Factory.getList()) {
Factory.getList().notifyAll();
//有商品
if(Factory.getList().size() > 0) {
//消费商品
Factory.getList().remove(0);
System.out.println(Thread.currentThread().getName() + ":消费\t" + "总数量:" + Factory.getList().size());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else { //无商品
try {
System.out.println(Thread.currentThread().getName() + ":仓库已空,取消消费任务\t" + "总数量:" + Factory.getList().size());
Factory.getList().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class MyTest7 {
public static void main(String[] args) {
Producer p = new Producer();
p.start();
Consumer c1 = new Consumer();
Consumer c2 = new Consumer();
c1.start();
c2.start();
}
}