JAVA基础系列(八) 多线程
假如一个程序有多条执行流程,那么,该程序就是多线程程序。
1.多线程概述
1.1 进程与线程
进程:
正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
线程:
是进程中的单个顺序控制流,是一条执行路径
一个进程如果只有一条执行路径,则称为单线程程序。
一个进程如果有多条执行路径,则称为多线程程序。
1.2 线程调度
CPU分配使用权的机制
线程有两种调度模型:
分时调度模型: 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 : 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
1.3 Java程序运行原理
java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。
思考题:jvm虚拟机的启动是单线程的还是多线程的?
答:多线程的。原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
2.Thread类
public class Thread extends Object implements Runnable
继承了Object类实现了Runnable接口
2.1 构造方法
public Thread();
public Thread(String name);
public Thread(Runnable target);
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, Runnable target);
...
2.2 成员方法即线程控制
//线程休眠
public static void sleep(long millis)
//线程加入,等待该线程终止。
public final void join()
//线程礼让,暂停当前正在执行的线程对象,并执行其他线程。
public static void yield()
//后台线程,将该线程标记为守护线程或用户线程。
//当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用
public final void setDaemon(boolean on)
//中断线程
//让线程停止,过时了,但是还可以使用。
public final void stop()
中断线程。 把线程的状态终止,并抛出一个InterruptedException。
public void interrupt()
// 设置线程优先级,范围为1-10,默认为5
// 线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
public final void setPriority(int newPriority)
注意:
run与Start 的区别:
1.start方法
用 start方法来启动线程,是真正实现了多线程, 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法。但要注意的是,此时无需等待run()方法执行完毕,即可继续执行下面的代码。所以run()方法并没有实现多线程。
2.run方法
run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码。
2.3 Thread.State
枚举类,表示线程运行周期中的几种状态。
**NEW **
状态是指线程刚创建, 尚未启动
**RUNNABLE **
状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等
**BLOCKED **
这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区
**WAITING **
这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在临界点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束
**TIMED_WAITING **
这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态
**TERMINATED **
这个状态下表示该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)
几种状态之间的关系图解如下:
注意:在JDK8的API中 Thread.State 并没有RUNNING这个状态,这里是为了便于理解画在图中。
3.多线程实现的几种方式
JAVA多线程实现方式主要有三种:
3.1、继承Thread类,并重写run()方法
** Demo:**
public class SellTicketDemo {
public static void main(String[] args) {
// 创建三个线程对象
SellTicket st1 = new SellTicket();
SellTicket st2 = new SellTicket();
SellTicket st3 = new SellTicket();
// 给线程对象起名字
st1.setName("窗口1");
st2.setName("窗口2");
st3.setName("窗口3");
// 启动线程
st1.start();
st2.start();
st3.start();
}
}
class SellTicket extends Thread {
// 定义100张票
// 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
private static int tickets = 100;
@Override
public void run() {
// 是为了模拟一直有票
while (tickets > 0) {
System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
3.2、 实现Runnable接口,实现run()方法
** Demo:**
/*
* 实现Runnable接口的方式实现
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
@Override
public void run() {
while (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票");
}
System.out.println(Thread.currentThread().getName() + ":没票了哦!");
}
}
3.3、使用ExecutorService、Callable、Future实现有返回结果的多线程。
其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。
** Demo:**
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableDemo {
public static void main(String[] args) throws InterruptedException,
ExecutionException {
FutureTask<Integer> f1 = new FutureTask<Integer>(new MyCallable(100));
FutureTask<Integer> f2 = new FutureTask<Integer>(new MyCallable(200));
// FutureTask实现了两个接口,Runnable和Future
new Thread(f1).start();
new Thread(f2).start();
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
}
}
class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
** Demo:**使用线程池
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableDemo {
public static void main(String[] args) throws InterruptedException,
ExecutionException {
// 创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
// 结束
pool.shutdown();
}
}
class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
3.4 匿名内部类方式使用多线程
new Thread(){代码…}.start();
New Thread(new Runnable(){代码…}).start();
** Demo:**
/*
* 匿名内部类的格式:
* new 类名或者接口名() {
* 重写方法;
* };
* 本质:是该类或者接口的子类对象。
*/
public class ThreadDemo {
public static void main(String[] args) {
// 继承Thread类来实现多线程
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}.start();
// 实现Runnable接口来实现多线程
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":"
+ x);
}
}
}) .start();
// 更有难度的
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("hello" + ":" + x);
}
}
}) {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println("world" + ":" + x);
}
}
}.start();
}
}
4.线程同步
4.1 同步代码块
** Demo:**
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
class SellTicket implements Runnable {
// 定义100张票
private int tickets = 100;
// 创建锁对象
private Object obj = new Object();
@Override
public void run() {
while (tickets > 0) {
synchronized (obj) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
}
}
System.out.println(Thread.currentThread().getName() + ":没票了哦!");
}
}
4.2 同步方法
** Demo:**
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个线程对象
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
class SellTicket implements Runnable {
// 定义100张票
private static int tickets = 100;
private int x = 0;
@Override
public void run() {
while (tickets > 0) {
if (x % 2 == 0) {
// 当同步方法为静态时锁对象应该为SellTicket.class
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票 ");
}
}
} else {
sellTicket();
}
x++;
}
System.out.println(Thread.currentThread().getName() + ":没票了哦!");
}
// 当同步方法为静态时锁对象应该为SellTicket.class
private synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票 ");
}
}
}
** 注意:**
1.同步代码块的锁对象是任意对象。
2.同步方法的锁对象是this。
3.静态同步方法的锁对象是类的字节码文件对象。
4.3 Lock锁的使用
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Demo:
/*
* Lock:
* void lock(): 获取锁。
* void unlock():释放锁。
* ReentrantLock是Lock的实现类.
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicketDemo {
public static void main(String[] args) {
// 创建资源对象
SellTicket st = new SellTicket();
// 创建三个窗口
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
class SellTicket implements Runnable {
// 定义票
private int tickets = 100;
// 定义锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (tickets > 0) {
try {
// 加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
} finally {
// 释放锁
lock.unlock();
}
}
System.out.println(Thread.currentThread().getName() + ":没票了哦!");
}
}
4.4 死锁
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象.
如果出现了同步嵌套,就容易产生死锁问题
Demo:
/*
* 同步的弊端:
* A:效率低
* B:容易产生死锁
*
*
* 举例:
* 中国人,美国人吃饭案例。
* 正常情况:
* 中国人:筷子两支
* 美国人:刀和叉
* 现在:
* 中国人:筷子1支,刀一把
* 美国人:筷子1支,叉一把
*/
public class DieLockDemo {
public static void main(String[] args) {
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);
dl1.start();
dl2.start();
}
}
class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (MyLock.objA) {
System.out.println("if objA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
} else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}
}
class MyLock {
// 创建两把锁对象
public static final Object objA = new Object();
public static final Object objB = new Object();
}
5. 线程通信
针对同一个资源的操作有不同种类的线程
Demo:生产者消费者模型
import java.util.Vector;
/*
* 篮子容量为5,生产者每1秒钟生产一个产品,消费者每2秒钟消费一个产品
* 当篮子为空时消费者会等待生产者生产,当篮子装满时生产者等消费者消费
*/
public class ProcucerAndConsumerDemo {
public static void main(String[] args) {
// 用Vector来模拟篮子
Vector obj = new Vector();
// 通过构造函数来共享一个对象
Thread consumer = new Thread(new Consumer(obj));
Thread producter = new Thread(new Producer(obj));
consumer.start();
producter.start();
}
}
class Producer implements Runnable {
private Vector obj;
public Producer(Vector v) {
this.obj = v;
}
public void run() {
while (true) {
synchronized (this.obj) {
try {
if (this.obj.size() > 4) {
System.out.println("Producter:the basked has full!");
this.obj.wait();
}
this.obj.add(new String("apples"));
System.out.println("Producter:I have produced one");
this.obj.notify();
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
class Consumer implements Runnable {
private Vector obj;
public Consumer(Vector v) {
this.obj = v;
}
public void run() {
while (true) {
synchronized (this.obj) {
try {
if (this.obj.size() == 0) {
System.out.println("Consumer:the basked is null!");
this.obj.wait();
}
this.obj.remove(0);
System.out.println("Consumer:I have taken one");
System.out.println("obj size: " + this.obj.size());
this.obj.notify();
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
Result:
Consumer:the basked is null!
Producter:I have produced one
Producter:I have produced one
Consumer:I have taken one
obj size: 1
Consumer:I have taken one
obj size: 0
Consumer:the basked is null!
Producter:I have produced one
Producter:I have produced one
Producter:I have produced one
Producter:I have produced one
Producter:I have produced one
Consumer:I have taken one
obj size: 4
Consumer:I have taken one
obj size: 3
Consumer:I have taken one
obj size: 2
Consumer:I have taken one
obj size: 1
Consumer:I have taken one
obj size: 0
Consumer:the basked is null!
Producter:I have produced one
Producter:I have produced one
6.线程组
把多个线程组合到一起。它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
构造方法
private ThreadGroup();
public ThreadGroup(String name);
public ThreadGroup(ThreadGroup parent, String name);
成员方法
public final void setDaemon(boolean daemon) ;
public final void interrupt();
void add(Thread t);
...
Demo:
public class ThreadGroupDemo {
public static void main(String[] args) {
method1();
// method2();
}
private static void method2() {
// ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("这是一个新的组");
MyRunnable my = new MyRunnable();
// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, my, "林青霞");
Thread t2 = new Thread(tg, my, "刘意");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
t1.start();
t2.start();
// 通过组名称设置后台线程,表示该组的线程都是后台线程
tg.setDaemon(true);
}
private static void method1() {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "林青霞");
Thread t2 = new Thread(my, "刘意");
// 我不知道他们属于那个线程组,我想知道,怎么办
// 线程类里面的方法:public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
// 线程组里面的方法:public final String getName()
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
// 通过结果我们知道了:线程默认情况下属于main线程组
// 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
System.out.println(Thread.currentThread().getThreadGroup().getName());
t1.start();
t2.start();
// 通过组名称设置后台线程,表示该组的线程都是后台线程
Thread.currentThread().getThreadGroup().setDaemon(true);
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
7.线程池
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。
Demo:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
*
* 如何实现线程池的代码呢?
* A:创建一个线程池对象,控制要创建几个线程对象。
* public static ExecutorService newFixedThreadPool(int nThreads)
* B:这种线程池的线程可以执行:
* 可以执行Runnable对象或者Callable对象代表的线程
* 做一个类实现Runnable接口。
* C:调用如下方法即可
* Future<?> submit(Runnable task)
* <T> Future<T> submit(Callable<T> task)
* D:我就要结束,可以吗?
* 可以。
*/
public class ExecutorsDemo {
public static void main(String[] args) {
// 创建一个线程池对象,控制要创建几个线程对象。
// public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
// 结束线程池
pool.shutdown();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
8.定时器
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。
在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
//Timer
public Timer()
//在指定延迟(delay)后执行任务
public void schedule(TimerTask task, long delay)
//在指定延迟(delay)后间隔(period)执行任务
public void schedule(TimerTask task,long delay,long period)
//在指定时间(time)执行任务
public void schedule(TimerTask task, Date time)
//在指定时间( firstTime)开始间隔(period)执行任务
public void schedule(TimerTask task, Date firstTime, long period)
//TimerTask
public abstract void run()
public boolean cancel()
Demo1:
import java.util.Timer;
import java.util.TimerTask;
public class TimerDemo2 {
public static void main(String[] args) {
// 创建定时器对象
Timer t = new Timer();
// 3秒后执行爆炸任务第一次,如果不成功,每隔2秒再继续炸
t.schedule(new MyTask2(), 3000, 2000);
}
}
// 做一个任务
class MyTask2 extends TimerTask {
@Override
public void run() {
System.out.println("beng,爆炸了");
}
}
Demo2:
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/*
* 需求:在指定的时间删除我们的指定目录(你可以指定c盘,但是我不建议,我使用项目路径下的demo)
*/
public class TimerTest {
public static void main(String[] args) throws ParseException {
Timer t = new Timer();
String s = "2014-11-27 15:45:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sdf.parse(s);
t.schedule(new DeleteFolder(), d);
}
}
class DeleteFolder extends TimerTask {
@Override
public void run() {
File srcFolder = new File("demo");
deleteFolder(srcFolder);
}
// 递归删除目录
public void deleteFolder(File srcFolder) {
File[] fileArray = srcFolder.listFiles();
if (fileArray != null) {
for (File file : fileArray) {
if (file.isDirectory()) {
deleteFolder(file);
} else {
System.out.println(file.getName() + ":" + file.delete());
}
}
System.out.println(srcFolder.getName() + ":" + srcFolder.delete());
}
}
}