Java多线程
1. 基本概念
1.1 进程
进程是一个程序的运行状态和资源占用(内存,CPU)的描述
进程是程序的一个动态过程,它指的是从代码加载到执行完毕的一个完成过程
进程的特点:
a.独立性:不同的进程之间是独立的,相互之间资源不共享(举例:两个正在上课的教室有各自的财产,相互之间不共享)
b.动态性:进程在系统中不是静止不动的,而是在系统中一直活动的
c.并发性:多个进程可以在单个处理器上同时进行,且互不影响
1.2 线程
线程是进程的组成部分,一个进程可以有多个线程,每个线程去处理一个特定的子任务。
线程的执行是抢占式的,多个线程在同一个进程中可以并发执行,其实就是CPU快速的在不同的线程之间切换,也就是说,当前运行的线程在任何时候都有可能被挂起,以便另外一个线程可以运行。
1.3 线程和进程关系
a.一个程序运行后至少有一个进程
b.一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的
c.进程间不能共享资源,但线程之间可以
d.系统创建进程需要为该进程重新分配系统资源,而创建线程则容易的多,因此使用线程实现多任务并发比多进程的效率高
e.系统创建进程需要为该进程重新分配系统资源,而创建线程则容易的多,因此使用线程实现多任务并发比多进程的效率高
2. 多线程实现
实现线程有两种方式:继承Thread类和实现Runnable接口
2.1 继承Thread类
Thread类是所有线程类的父类,实现了对线程的抽取和封装
继承Thread类创建并启动多线程的步骤:
a.定义一个类,继承自Thread类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此,run方法的方法体被称为线程执行体
b.创建Thread子类的对象,即创建了子线程
c.用线程对象的start方法来启动该线程
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("我是MyThread的run方法");
Thread thread = Thread.currentThread();// 获得当前线程
System.out.println(thread.getName());
System.out.println(super.getName());
}
}
测试方法:
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.setName("子线程");// 设置线程名字
thread.setPriority(10);// 设置线程优先级,但优先级高并不一定先执行。默认与父线程相同优先级
thread.start();// 启动线程
System.out.println("主线程");
}
使用线程的start()方法启动线程,而不能直接调用线程的run()方法,如果调用了run()方然,它就是个普通的对象方法了,而不是线程。线程之间征用资源,可以设置线程优先级,优先级1(最低)~10(最高),默认优先级是5。但并不能保证优先级高的一定会先执行,只是获得资源的概率大一些。
2.2 实现Runnable接口
实现Runnable接口创建并启动多线程的步骤:
a.定义一个Runnable接口的实现类,并重写该接口中的run方法,该run方法的方法体同样是该线程的线程执行体
b.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
c.调用线程对象的start方法来启动该线程
public class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("我是MyThread2的run方法");
Thread thread = Thread.currentThread();// 获得当前线程
System.out.println(thread.getName());
}
}
public static void main(String[] args) {
MyThread2 my=new MyThread2();
Thread thread=new Thread(my);//真正的线程对象
thread.start();
System.out.println("主线程");
}
2.3 两种方式比较
实现Runnable接口的方式:
a.线程类只是实现了Runnable接口,还可以继承其他类【一个类在实现接口的同时还可以继承另外一个类】
b.可以多个线程共享同一个target对象,所以非常适合多个线程来处理同一份资源的情况
c.弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()
继承Thread类的方式:
a.编写简单,如果要访问当前线程,除了可以通过Thread.currentThread()方式之外,还可以使用super关键字
b.弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】
实际上大多数的多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】
Thread thread=new Thread(new Runnable(){
@Override
public void run() {
}
});
2.4 Callable和Future
无论是继承Thread类还是实现Runnable接口,都无法在执行任务之后获取到任务的结果,因为run()方法没有返回值。在java1.5之后提供了Callable和Future接口,在任务执行完毕后获取结果。
2.4.1 Callable接口
Callable的方法是有返回值的call()。它有一个泛型,代表返回值的类型:
import java.util.concurrent.Callable;
public class CallableTest implements Callable<Integer> {
private int num;//指定sum,本线程用于计算1~num的和
public CallableTest(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= num; i++) {
sum += i;
}
return sum;
}
}
测试方法:
public static void main(String[] args) {
CallableTest thread = new CallableTest(100);
//1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Integer> result = new FutureTask<>(thread);
new Thread(result).start();
//2.接收线程运算后的结果
try {
Integer sum = result.get();
System.out.println("sum=" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
也可以使用Executors
public static void main(String[] args) {
CallableTest thread = new CallableTest(100);
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> result = executor.submit(thread);
try {
Integer sum = result.get();
System.out.println("sum=" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
Callable+Future最终也是以Callable+FutureTask的方式实现的,executor的submit方法执行的是AbstractExecutorService的submit
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
2.4.2 Future
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
Future有三个功能:
- 判断任务是否完成
- 获取任务执行结果
- 取消任务
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null
cancel()方法中的mayInterruptIfRunning表示是否可以打断已经执行的任务。本方法如果取消成功,就返回true,取消失败就返回false。
2.5 线程常用方法
2.5.1 线程休眠
Thread.sleep(毫秒数);可以让线程暂停一定时间,时间到了之后线程继续执行
2.5.2 线程合并
在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();// 启动线程
/**
* join的意思是放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
* 程序在main线程中调用thread线程的join方法,则main线程放弃cpu控制权,
* 并返回thread线程继续执行直到线程thread执行完毕
* 所以结果是thread线程执行完后,才到主线程执行,相当于在main线程中同步thread线程,thread执行完了,main线程才有执行的机会
*/
thread.join();
System.out.println("主线程");
}
join方法中如果传入参数,则表示这样的意思:如果A线程中调用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并行执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。join方法必须在线程start方法调用之后调用才有意义。
2.5.3 守护线程
隐藏起来一直在默默运行的线程,直到进程结束,又被称为守护线程或精灵线程,JVM的垃圾回收线程就是典型的后台线程
特征:如果所有的前台线程都死亡,后台线程会自动死亡,必须要在start之前执行。前台线程死亡后,JVM需要通知后台线程,后台线程接到指令并响应,需要一定的时间。main方法默认是前台线程。前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。
public class MyThread extends Thread {
@Override
public void run() {
while(true){
System.out.println(123);
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.setDaemon(true);//设置成守护线程,如果不加这个,main退出后它还会一直运行
thread.start();// 启动线程
System.out.println("主线程");
}
2.5.4 线程让步
可以让当前正在执行的线程暂停,但它不会阻塞该线程,他只是将该线程转入就绪状态,完全可能出现的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行
实际上,当某个线程调用了yield()方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程才会获得执行的机会
public class MyThread extends Thread {
@Override
public void run() {
for(int i = 0;i < 50;i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if(i==20) {
//线程让步,不会让线程进入阻塞状态
Thread.yield();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread1 = new MyThread();
thread1.setName("t1");
MyThread thread2 = new MyThread();
thread2.setName("t2");
thread1.start();// 启动线程
thread2.start();// 启动线程
}
sleep和yield的区别:
- sleep暂停当前线程,会给其它线程执行的机会,不管其它线程优先级如何。yield方法只给相同或更高优先级的线程执行机会。
- sleep将线程进入阻塞状态,当sleep的时间过后进入就绪状态。而yield方法强制线程进入就绪状态。
2.6 线程生命周期
对于线程,当线程被创建并启动之后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,他会经历各种不同的状态【在一个进程中,多个线程同时运行,是在争抢CPU时间片】
New(新生):线程被实例化,但是还没有开始执行,就是刚new出来的。
Runnable(就绪):调用了start()方法,但还没有抢到CPU
Running(运行):抢到了CPU,CPU开始处理这个线程中的任务。当一条线程开始运行后,它不会一直处于运行状态(除非线程执行体足够短,瞬间就执行完了)。线程在运行的时候需要被中断,目的是让其它线程获得执行机会,线程的调度细节取决于底层平台的策略。对于抢占资源策略的系统,系统会给每个可执行的线程一个小时间段处理任务,当该事件段用完了,系统就会剥夺该线程所占用的资源,让下一个线程获得执行的机会。在选择下一个线程时,会考虑线程的优先级。
Blocked(阻塞):线程在执行过程中遇到特殊情况(如sleep),使得其他线程就可以获得执行的机会,被阻塞的线程会等待合适的时机重新进入就绪状态,注意是就绪状态不是运行状态。它必须等待调度器再次调用它才会进入运行状态。就绪和运行状态的转换不受认为控制,由系统调度完成。但一个情况除外,程序可以通过yield()方法让一个线程从运行状态到就绪状态。
Dead(死亡):线程终止
a.run方法执行完成,线程正常结束【正常的死亡】
b.直接调用该线程的stop方法强制终止这个线程
一个线程死亡后不能通过start()方法重新恢复,死亡就是死亡了。只能对新建状态的线程执行start()
3. 线程同步
当多个线程访问同一个资源的时候,可能会出现问题。如银行取款的时候,一个卡和一个存折操作的是同一个账户,张三拿着卡,张三老婆拿着存折,我们模拟这个过程:
public class Account {//账户
private int amount = 0;
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
}
取钱的线程:
public class DrawThread extends Thread {
private Account account;// 取钱的时候操作一个账户
private int money;// 每次取多少钱
public DrawThread(Account account, String name, int money) {
super(name);
this.account = account;
this.money = money;
}
public void run() {
while (true) {// 一直取钱,目的是让多个线程一直执行,抢占CPU资源
if (account.getAmount() >= money) {// 如果余额足够就取钱,假设每次取money
System.out.println(super.getName() + "取钱" + money);
try {
Thread.sleep(1000);// 模拟银行系统卡顿的状况,或者银行职员正在数钱
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setAmount(account.getAmount() - money);// 减掉账户余额
System.out.println(super.getName() + "取款后余额为:" + account.getAmount());
} else {
System.out.println("余额不足" + account.getAmount());
return;
}
}
}
}
测试方法:
public static void main(String[] args) throws InterruptedException {
Account account = new Account();
account.setAmount(500);//开始有500块
DrawThread card = new DrawThread(account, "张三", 100);
DrawThread paper = new DrawThread(account, "老婆", 100);
card.start();
paper.start();
}
运行结果(注意因为线程抢占资源是随机的,所以不一定是下面的顺序,多运行几次,看到余额是负数的情况就是我们想要的结果):
出现这种情况的原因,可能是
- 最后还剩下100块的时候,A线程判断余额足够,进入if块,然后sleep
- 在A线程sleep的期间内,B线程获得资源,此时A线程余额还没减掉,所以B判断余额足够,也进入if块。B线程sleep
- A线程sleep结束,重新获得资源,继续执行剩下的代码,减掉余额,余额是0
- B线程sleep结束,重新获得资源,继续执行剩下的代码,减掉余额,但刚刚A已经把余额减成0了,所以B减掉之后会变成-100
不用纠结A和B谁是张三谁是老婆,最终的结果余额是负数 ,就不是我们期望看到的。
解决这个问题,我们可以使用synchronized同步代码块或者方法。
3.1 同步代码块
synchronized(object){
//代码块
}
其中的object是同步监视器,任何时刻,只能有一条线程可以获得对同步监视器的锁定,当同步代码块结束后,该线程释放对同步监视器的锁定。
3.2 同步方法
除了同步代码块,我们还可以同步方法:
public class Account {
private int amount = 0;
public int getAmount() {
return amount;
}
//同步方法,同一时刻只能有一个线程执行,同步监视器是this
public synchronized void draw(int money) {
if (amount >= money) {// 如果余额足够就取钱,假设每次取money
System.out.println(Thread.currentThread().getName() + "取钱" + money);
try {
Thread.sleep(1000);// 模拟银行系统卡顿的状况,或者银行职员正在数钱
} catch (InterruptedException e) {
e.printStackTrace();
}
amount -= money;// 减掉账户余额
System.out.println(Thread.currentThread().getName() + "取款后余额为:" + amount);
} else {
System.out.println("余额不足" + amount);
}
}
}
public class DrawThread extends Thread {
private Account account;// 取钱的时候操作一个账户
private int money;// 每次取多少钱
public DrawThread(Account account, String name, int money) {
super(name);
this.account = account;
this.money = money;
}
public void run() {
for (int i = 0; i < 10; i++) {// 一直取钱,目的是让多个线程一直执行,抢占CPU资源
account.draw(money);
}
}
}
3.3 同步锁
线程进入到同步代码块和同步方法之前,必须获得对同步监视器的锁定。程序无法显示的释放对同步监视器的锁定。线程会在如下几种情况下释放锁定:
a) 当线程的同步方法、同步代码块执行结束。当前线程会释放同步监视器。
b) 当前线程在同步代码块、同步方法中遇到break、return终止该代码块或方法的继续执行,当前线程会释放同步监视器。
c) 当前线程在同步代码块、同步方法中遇到未处理的Error或Exception,导致该代码块或方法异常结束的时候
d) 当前线程在同步代码块、同步方法中执行了同步监视器的wait()方法,则当前线程暂停,释放同步监视器。
以下情况不会释放同步监视器:
a) 当前线程在同步代码块、同步方法中,调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器
b) 线程执行同步代码块时,其它线程调用了该方法的suspend方法将该线程挂起,当前线程不会释放同步监视器。实际开发中应避免这种情况。
JDK1.5之后提供了同步锁Lock。它通过显式定义同步锁对象,实现同步。锁提供了对共享资源的独占访问。每次只能有一个线程对Lock对象加锁。
ReentrantLock是Lock的实现,称为可重入锁。
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private int amount = 1000;
private final ReentrantLock lock = new ReentrantLock();//锁对象
public int getAmount() {
return amount;
}
public void draw(int money) {
lock.lock();//加锁
try {
if (amount >= money) {// 如果余额足够就取钱,假设每次取money
System.out.println(Thread.currentThread().getName() + "取钱" + money);
try {
Thread.sleep(1000);// 模拟银行系统卡顿的状况,或者银行职员正在数钱
} catch (InterruptedException e) {
e.printStackTrace();
}
amount -= money;// 减掉账户余额
System.out.println(Thread.currentThread().getName() + "取款后余额为:" + amount);
} else {
System.out.println("余额不足" + amount);
}
} finally {
lock.unlock();//释放锁
}
}
}
3.4 死锁
当两个线程同时等待对方释放同步监视器时就会发生死锁。
public class ClassA {
public synchronized void methodA(ClassB b) {
System.out.println(Thread.currentThread().getName() + "-methodA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"要调用b.lastB()");
b.lastB();
}
public synchronized void lastA() {
System.out.println("lastA");
}
}
public class ClassB {
public synchronized void methodB(ClassA a) {
System.out.println(Thread.currentThread().getName() + "-methodB");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"要调用a.lastA()");
a.lastA();
}
public synchronized void lastB() {
System.out.println("lastB");
}
}
public class DeadLock implements Runnable {
private ClassA a = new ClassA();
private ClassB b = new ClassB();
public void run() {
a.methodA(b);
}
public void init() {
b.methodB(a);
}
}
public static void main(String[] args) throws InterruptedException {
DeadLock thread = new DeadLock();
new Thread(thread).start();
thread.init();
}
上面代码运行结果:
程序会卡住。
- 通过new Thread()启动的线程是Thread-0,主线程将thread当成普通对象,调用init()方法。
- 在init()方法中调用b.methodB()它是一个同步方法,所以主线程获得了b对象的锁。
- 接着b.methodB()中sleep的时候,Thread-0开始执行,调用了a.methodA,它也是个同步方法,Thread-0获得a的锁。
- 在a.methodA中调用b.lastB()需要获得B的锁,但此时b的锁在main手里。
- b.methodB()中sleep结束,开始调用a.lastA()需要a的锁,但此时a的锁在Thread-0手里,形成死锁。
死锁形成的原因:
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
破坏任何一个条件就能解除死锁。修改ClassA的代码:
public void methodA(ClassB b) {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "-methodA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "要调用b.lastB()");
}
b.lastB();
}
4. 线程通信
当线程在系统内运行时,程序无法准确控制线程轮换执行,但可以通过一些机制保证线程协调运行。
4.1 线程间协调运行
假设线程中有存款者和取款者两个线程。要存款者在指定账号中存入钱后,取款者立即取走钱;存款者不能连续存两次,取款者也不能连续取两次。两个线程交替运行。
实现线程间协调运行,可以借助Object类的wait(),notify()和notifyAll()。这是三个方法属于Object而不属于Thread。这三个方法必须由同步监视器对象来调用。
使用synchronized修饰的同步方法,同步监视器是该类的默认实例this,所以在同步方法中可以直接调用。
对于synchronized修饰的同步代码块,同步监视器是synchronized后面括号里的对象,所以必须由该对象调用者三个方法。
三个方法的作用:
wait():让当前线程等待,直到其它线程调用该同步监视器的notify()方法或notifyAll()唤醒。无参数的wail()会一直等待,带毫秒参数的是等待指定时间后自动苏醒。
notify():唤醒在此同步监视器上等待的单个线程。如果所有同步监视器都在这个线程上等待,会随意选择其中一条唤醒。只有当前线程放弃对同步监视器的锁定后(wait())才能执行被唤醒的线程。
notifyAll():唤醒在同步监视器上等待的所有线程。
public class Account {
private int balance = 0;//账户余额
public boolean flag = false;// false代表该存钱,true代表该取钱
public synchronized void add(int money) {
try {
if (flag) {// 该取钱线程执行
this.wait();// 当前线程等待
}
balance += money;
System.out.println("存钱余额为" + balance);
flag = true;//下次改取钱
this.notifyAll();//唤醒取钱线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void get(int money) {
try {
if(!flag){//该存钱执行,当前线程等待
this.wait();
}
if (money <= balance) {
balance -= money;
System.out.println("取款余额为" + balance);
} else {
System.out.println("余额不足" + balance);
}
flag = false;//下次改存钱了
this.notifyAll();//唤醒存钱线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
final Account account = new Account();
Thread add = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
account.add(1);
}
}
});
Thread get = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
account.get(1);
}
}
});
add.start();
get.start();
}
4.2 使用条件变量
当程序使用Lock而不是synchronized关键字同步的时候,不存在隐式的同步监视器,就不能使用wait、notify、notfyALL了。Java提供了一个Condition来保持协调。
Condition类提供了三个方法:
await():类似于wait()方法,导致当前线程等待。直到其它线程调用signal()或signalAll()方法来唤醒该线程。
signal():唤醒在此Lock对象上等待的单个线程。如果所有线程都在该Lock对象上等待,则会任意选择唤醒其中一个线程。只有当前线程放弃对该Lock对象的锁定后(await)才会被唤醒。
signalAll():唤醒此Lock对象上等待的所有线程。
public class Account {
private int balance = 0;// 账户余额
public boolean flag = false;// false代表该存钱,true代表该取钱
private final Lock lock = new ReentrantLock();
private final Condition cond = lock.newCondition();
public void add(int money) {
lock.lock();
try {
if (flag) {// 该取钱线程执行
cond.await();// 当前线程等待
}
balance += money;
System.out.println("存钱余额为" + balance);
flag = true;// 下次改取钱
cond.signalAll();// 唤醒取钱线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void get(int money) {
lock.lock();
try {
if (!flag) {// 该存钱执行,当前线程等待
cond.await();
}
if (money <= balance) {
balance -= money;
System.out.println("取款余额为" + balance);
} else {
System.out.println("余额不足" + balance);
}
flag = false;// 下次改存钱了
cond.signalAll();// 唤醒存钱线程
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
4.3 使用管道流(选学)
另一种线程间通信的方式是管道流,管道流有:
管道字节流:PipedInputStream、PipedOutputStream
管道字符流:PipedReader、PipedWriter
新IO流管道Channel:Pipe.SinkChannel、Pipe.SourceChannel
使用管道流实现多线程通信的步骤:
- 使用new操作符分别创建管道输入流和管道输出流
- 使用管道输入流或管道输出流的connect方法把两个输入流和输出流连接起来
- 将管道输入流、管道输出流分别传入两个线程
- 两个线程可以分别依赖各自的管道输入流、管道输出流进行通信
public class ReaderThread extends Thread {
private PipedReader pipedReader;
private BufferedReader bufferedReader;
public ReaderThread() {
}
public ReaderThread(PipedReader pipedReader) {
super();
this.pipedReader = pipedReader;
this.bufferedReader = new BufferedReader(pipedReader);
}
public void run() {
String buf = null;
try {
while ((buf = bufferedReader.readLine()) != null) {
System.out.println(buf);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
public class WriterThread extends Thread {
private PipedWriter pipedWriter;
public WriterThread() {
}
public WriterThread(PipedWriter pipedWriter) {
this.pipedWriter = pipedWriter;
}
public void run() {
try {
pipedWriter.write("java基础入门\r\n");
pipedWriter.write("多线程同步\r\n");
pipedWriter.write("我爱编程\r\n");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (pipedWriter != null)
pipedWriter.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
public static void main(String[] args) {
PipedWriter pw = null;
PipedReader pr = null;
try {
pw=new PipedWriter();
pr=new PipedReader();
pw.connect(pr);
new WriterThread(pw).start();
new ReaderThread(pr).start();
} catch (Exception e) {
e.printStackTrace();
}
}
5. 线程组
ThreadGroup表示线程组,它可以对一批线程进行分类管理。默认情况下,子线程和创建它的父线程属于同一个线程组。只能在线程开始前设置线程组,直到该线程死亡,线程只能属于该线程组。
public class GroupTestThread extends Thread {
public GroupTestThread(ThreadGroup group, String name) {
super(group, name);
}
public void run() {
while (!this.isInterrupted()) {//当线程没有被打断
System.out.println(getName() + "线程运行");
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadGroup group=new ThreadGroup("测试线程组");
GroupTestThread t1=new GroupTestThread(group,"t1");
GroupTestThread t2=new GroupTestThread(group,"t2");
t1.start();
t2.start();
Thread.sleep(2000);
group.interrupt();//组内线程会被打断
}
6. 线程池
系统启动一个新线程的成本较高,当程序需要创建大量的生命周期短暂的线程时,可以使用线程池。线程池在系统启动时就创建大量空闲的线程,程序将一个Runnable对象传给线程池,线程池就会启动一条线程来执行该对象的run方法,当run方法执行结束后,该线程不会死亡,而是再次返回线程池成为空闲状态,等待执行下一个Runnable对象。线程池可以有效的控制系统中并发线程的数量,控制并发线程不超过线程池要求的最大线程数。
public class PoolTestThread extends Thread {
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread() + "的i=" + i);
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(3);//如果超过3条,多余要等待执行
pool.submit(new PoolTestThread());
pool.submit(new PoolTestThread());
pool.shutdown();
}
newFixedThreadPool(int num)同时最多有num条线程执行,超过限制的线程需要等待
newCachedThreadPool()可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们
newSingleThreadExecutor()线程会按照sumbit()的顺序依次执行
newScheduledThreadPool(int size)线程定时执行:
ScheduledExecutorService exec = Executors.newScheduledThreadPool(2);
MyThread t1 = new MyThread();
exec.execute(t1);// 将线程放入池中进行执行
MyThread t2 = new MyThread();
exec.schedule(t2, 1000, TimeUnit.MILLISECONDS);// 使用延迟执行风格的方法
exec.shutdown();
7. ThreadLocal
ThreadLocal线程局部变量,多个线程共享同一个对象的时候,线程局部变量属于单独的线程。
public class LocalTestObject {
public ThreadLocal<String> name = new ThreadLocal<String>();
public String getName() {
return this.name.get();
}
public void setName(String str) {
this.name.set(str);
}
}
public class LocalTestThread extends Thread {
private LocalTestObject object;
public LocalTestThread(LocalTestObject object,String name) {
super(name);
this.object = object;
}
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "中的name:" + object.getName());
if (i == 5) {
object.setName(getName()) ;
}
}
}
}