以前一听到多线程操作就感到好腻害好腻害的,如果你现在也是这种情况或许这篇文章能够帮助到你。
1、什么是多线程?
先了解两个概念
进程:正在运行的程序,是系统进行资源分配和调用的独立单位,有自己的内存空间和系统资源。
线程:是进程中的单个顺序控制流,是一条执行路径,线程是应用程序中执行的基本单元。
某位大神的总结:进程就相当于工厂,线程就是工厂里的流水线,线程不能独立存在,必须存在于进程中。
多进程:系统中同时存在多个并行的进程,则称为多进程。可通过电脑任务管理器查看正在运行的进程,比如用电脑聊QQ的同时看电影,就是多进程的体现。
多线程:线程是进程中的单个顺序控制流,是一条执行路径,一个进程如果有多条执行路径,则称为多线程。比如给某人聊QQ的同时还可以接收到其他人的消息。
2、多线程实现方式
1.继承Thread类
Java提供了Thread类,让我们对线程进行操作。
class MyThread extends Thread {
@Override
public void run() {
/* 多线程执行的逻辑 */
super.run();
}
}
//开启线程
new MyThread().start();
2.实现Runable接口
class MyRunnable implements Runnable {
@Override
public void run() {
/* 多线程执行的逻辑 */
}
}
//将实现了Runnable接口的类,以参数的形式传递给Thread类
new Thread(new MyRunnable()).start();
3.实现Callable接口
这是一种有返回值的线程,但是必须通过线程池使用。
public class Test {
public static void main(String[] args) {
// 生成线程池对象
ExecutorService pool = Executors.newCachedThreadPool();
// 提交Callable线程
pool.submit(new MyCallable());
}
}
class MyCallable implements Callable<String> {
// 通过指定范型 可以通过线程方法返回该类型的数据 而其他两种实现方式都没有返回值
@Override
public String call() throws Exception {
/* 多线程执行的逻辑 */
return "hello";
}
}
3、线程调度
分时调度模型 :所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
抢占式调度模型:抢占CPU使用权,其中优先级高的线程抢到CPU执行权的概率会越大,存在很大随机性。Java采用的是抢占式调度模型。
查看抢占式调度效果:
public class Test {
public static void main(String[] args) throws InterruptedException {
// 生成线程对象
MyThread thread = new MyThread();
MyThread thread2 = new MyThread();
// 启动线程
thread.start();
thread2.start();
//第一次输出 //第二次输出
//Thread-0:0 //Thread-1:0
//Thread-1:0 //Thread-0:0
//Thread-1:1 //Thread-1:1
//Thread-1:2 //Thread-0:1
//Thread-0:1 //Thread-0:2
//Thread-0:2 //Thread-1:2
}
}
// 线程类
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i <3; i++) {
// 输出线程名和i的值
System.out.println(this.getName() + ":" + i);
}
super.run();
}
}
通过输出可以看出线程抢占是随机的无规律可言(不设置优先级的情况下)。
如何设置线程优先级呢?
//Thread提供了相关方法
// 设置线程的优先级
void setPriority(int newPriority)
// 返回线程的优先级
int getPriority()
// 生成线程对象
MyThread thread = new MyThread();
// 获取默认线程优先级
int priority = thread.getPriority();
System.out.println(priority);// 5
// 设置线程优先级
thread.setPriority(6);
// 输出最小线程优先级
System.out.println(thread.MIN_PRIORITY);// 1
// 输出最大线程优先级
System.out.println(thread.MAX_PRIORITY);// 10
可以看出线程的优先级的是1-10
4、线程控制
Thread类提供了相关方法对线程进行控制。
1.线程休眠sleep
// 生成线程对象
MyThread thread = new MyThread();
//线程休眠3000毫秒
thread.sleep(3000);
//获取当前线程对象并设置其休眠200毫秒2000纳秒
Thread.currentThread().sleep(200, 2000);
2.线程加入join
public static void main(String[] args) throws InterruptedException {
// 生成线程对象
MyThread thread = new MyThread("线程一");
MyThread thread2 = new MyThread("线程二");
// 启动线程
thread.start();
// 线程加入 当调用了线程加入了之后在该放后启动的线程会等这个线程执行完 在抢占CPU执行权
thread.join();
thread2.start();
//未添加前输出
// 线程一:0
// 线程一:1
// 线程二:0
// 线程一:2
// 线程二:1
// 线程二:2
//添加后输出
// 线程一:0
// 线程一:1
// 线程一:2
// 线程二:0
// 线程二:1
// 线程二:2
}
}
// 线程类
class MyThread extends Thread {
public MyThread(String name) {
// 设置线程名
this.setName(name);
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
// 输出线程名和i的值
System.out.println(this.getName() + ":" + i);
}
super.run();
}
}
3.线程礼让yield
// 生成线程对象
MyThread thread = new MyThread("线程一");
MyThread thread2 = new MyThread("线程二");
//暂停当前正在执行的线程对象,并执行其他线程,让线程执行更和谐
thread.yield();
// 启动线程
thread.start();
thread2.start();
//输出
// 线程一:0
// 线程二:0
// 线程一:1
// 线程二:1
// 线程二:2
// 线程一:2
4.线程守护setDaemon
// 生成线程对象
MyThread thread = new MyThread("线程一");
MyThread thread2 = new MyThread("线程二");
//将该线程标记为守护线程或用户线程 在启动线程前调用
//设置thread为守护线程 即当线程thread2执行完成后,如果thread未执行完则不再继续执行
//因为线程具有随机性,所以存在thread在thread2执行完的情况
thread.setDaemon(true);
// 启动线程
thread.start();
thread2.start();
5.线程中断stop、interrupt
// 生成线程对象
MyThread thread = new MyThread("线程一");
MyThread thread2 = new MyThread("线程二");
// 启动线程
thread.start();
thread2.start();
// 立即终止某个线程,没有任何反馈,存在不安全性
//thread.stop();
// 中断线程
thread2.interrupt();
//输出:
//线程二线程终止!!!
//线程二------------
// 线程类
class MyThread extends Thread {
public MyThread(String name) {
// 设置线程名
this.setName(name);
}
@SuppressWarnings("static-access")
@Override
public void run() {
try {
//当前线程休眠3秒
this.sleep(3000);
} catch (InterruptedException e) {
System.out.println(this.getName()+"线程终止!!!");
}
System.out.println(this.getName()+"------------");
super.run();
}
}
stop()方法会立刻终止线程并没有任何反馈,而interrupt()会有后续的输出。
5、线程生命周期
图示:
6、线程安全问题
1.线程安全问题
模拟卖票情况:两个线程对同一数据进行操作
public class Test {
public static void main(String[] args) throws InterruptedException {
MyRunnable runnable = new MyRunnable();
new Thread(runnable, "卖票口一:").start();
new Thread(runnable, "卖票口二:").start();
}
}
// 线程类
class MyRunnable implements Runnable {
private int ticket = 100;
@Override
public void run() {
// 多次执行
while (true) {
if (ticket > 0) {
try {
// 线程休眠模拟网络延迟
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticket-- + "张票");
}
}
}
}
部分输出情况:
1.出现同票情况?根据输出可以推断,当ticket为40的时候,卖票窗口一线程抢到CPU执行权,并执行,但是执行run方法输出语句的时候, ticket–只执行完赋值操作并未执行–操作的时候,CPU执行权被卖票窗口二抢到,输出卖票窗口二:正在出售第40张票,然后卖票窗口一又抢到了CPU执行权输出卖票窗口一:正在出售第40张票。
2.出现0票情况?根据输出可以推断,当ticket为1的时候,卖票窗口一线程抢到CPU执行权,并执行,但是执行run方法未执行到输出语句的时候,CPU执行权并执行完run方法输出卖票窗口二:正在出售第1张票,然后卖票窗口一又抢到CPU执行权,此刻ticket已经为0了, 所以输出卖票窗口一:正在出售第0张票。
注:一般多线程环境、存在共享数据、多条语句操作共享数据的情况下会出现线程安全问题。那么如何解决呢?就用到了线程同步。
2.2.线程同步
1.同步代码块
class MyRunnable implements Runnable {
private int ticket = 100;
@Override
public void run() {
// 多次执行
while (true) {
// 同步代码块(添加任意对象)
synchronized (this) {
if (ticket > 0) {
try {
// 线程休眠模拟网络延迟
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + ticket-- + "张票");
}
}
}
}
}
注:同步代码块格式synchronized(对象){同步代码;};同步可以解决安全问题的根本原因就在那个对象上,该对象就是锁,多个线程操作同一代码块,一定要保证那个锁对象相同。
###2.同步方法
就是在方法上同步关键字。
class MyRunnable implements Runnable {
private int ticket = 100;
@Override
public void run() {
// 多次执行
while (true) {
sellTicket();
}
}
/**
* 同步方法
*/
private synchronized void sellTicket() {
if (ticket > 0) {
try {
// 线程休眠模拟网络延迟
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ ticket-- + "张票");
}
}
/**
* 同步静态方法
*/
private static synchronized void sellTicket() {
if (ticket > 0) {
try {
// 线程休眠模拟网络延迟
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticket-- + "张票");
}
}
注:同步方法的锁对象其实是当前对象this,同步静态方法的锁对象是当前类的字节码文件。
3.Lock锁的使用
JDK5提供了Lock锁接口,提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。
// 获取锁
void lock()
// 释放锁
void unlock()
class MyRunnable implements Runnable {
private int ticket = 100;
//实现类对象实例化锁接口
Lock lock = new ReentrantLock();
@Override
public void run() {
// 多次执行
while (true) {
// 获取锁
lock.lock();
if (ticket > 0) {
try {
// 线程休眠模拟网络延迟
Thread.sleep(000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ ticket-- + "张票");
}
// 释放锁
lock.unlock();
}
}
线程同步总结:
7、线程死锁问题
死锁就是线程间因相互等待对方资源,而不能继续执行的情况。线程同步嵌套就非常容易产生死锁问题。
修改之前的代码:
class MyRunnable implements Runnable {
private int ticket = 100;
// 锁对象1
private Object object1 = new Object();
// 锁对象2
private Object object2 = new Object();
@Override
public void run() {
// 多次执行
while (true) {
if (ticket > 0) {
if (ticket % 2 == 0) {
synchronized (object1) {// 这里出现线程死锁
synchronized (object2) {
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + ticket-- + "张票");
}
}
} else {
synchronized (object2) {// 这里出现线程死锁
synchronized (object1) {
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + ticket-- + "张票");
}
}
}
}
}
}
该程序因为进行了同步嵌套所以会线程死锁问题,会出现输出不全的情况。
注:那么如何解决死锁问题呢?给资源排序,在所有的线程中,决定次序并始终遵照这个次序获取锁。
8、线程间通信问题
1.生产消费模式
多线程间除了线程安全问题外,还存在线程间通信问题。比如线程A执行完,操作了某些数据,而线程B需要这些数据做某些操作,这时线程A需要通知线程B已经有数据了,然后线程B做某些操作,当没有数据时线程B需要反馈给线程A。线程A和线程B之间就冥冥中存在了联系,这就是生成消费模式。
这个类似于Android中的Work线程和UI线程通过Handler通信一样,而java则提供了等待唤醒机制进行线程通信。
2.等待唤醒机制
Object类提供了notify、wait方法,对线程进行唤醒和等待。
// 唤醒在此对象监视器上等待的单个线程
void notify()
// 唤醒在此对象监视器上等待的所有线程
void notifyAll()
// 在其他线程调用此对象的notify()方法或 notifyAll()方法前,导致当前线程等待
void wait()
// 在其他线程调用此对象的notify()方法或 notifyAll()方法,或者超过指定的时间量前,导致当前线程等待
void wait(long timeout)
public class User {
String name;
int age;
boolean b = false;// 判断是否有数据
// 设置数据
public synchronized void setUser(String name, int age)
throws InterruptedException {
if (this.b) {
//如果存在数据就等待
// 线程等待
this.wait();
} else {
//没有数据则设置数据并唤醒某个线程
setName(name);
setAge(age);
this.b = true;
// 唤醒等待的单个线程
this.notify();
}
}
// 获取数据
public synchronized void getUser() throws InterruptedException {
if (this.b) {
// 相当于消费数据
System.out.println(getName() + ":" + getAge());
// 消费后相当资源消失
this.b = false;
// 唤醒线程
this.notify();
} else {
// 线程等待
this.wait();
}
}
/** 实现get set方法 **/
}
public class Test {
public static void main(String[] args) throws InterruptedException {
User user = new User();
SetUserThread setUserThread = new SetUserThread(user);
GetUserThread getUserThread = new GetUserThread(user);
getUserThread.start();
setUserThread.start();
}
}
class GetUserThread extends Thread {
private User user;
public GetUserThread(User user) {
super();
this.user = user;
}
@Override
public void run() {
while (true) {
try {
user.getUser();
} catch (InterruptedException e) {
e.printStackTrace();
}
super.run();
}
}
}
class SetUserThread extends Thread {
private User user;
public SetUserThread(User user) {
super();
this.user = user;
}
@Override
public void run() {
while (true) {
try {
user.setUser("Hello", 18);
} catch (InterruptedException e) {
e.printStackTrace();
}
super.run();
}
}
}
9、线程组的使用
Java提供了线程组ThreadGroup对线程进行统一管理和调度。
ThreadGroup:线程组就是一个线程的集合。
// 生成线程组对象,并设置名字
ThreadGroup threadGroup = new ThreadGroup("线程组一");
// 使用Thread的构造设置线程归宿的线程组
new Thread(threadGroup, new MyRunnable(), "线程一").start();
new Thread(threadGroup, new MyRunnable(), "线程二").start();
new Thread(threadGroup, new MyRunnable(), "线程三");
// 获取线程组名字
System.out.println(threadGroup.getName());// 线程组一
// 获取线程组活动线程的估计数
Thread[] threads = new Thread[threadGroup.activeCount()];
// 线程组中的所有活动线程复制到指定数组中
threadGroup.enumerate(threads);
System.out.println(threads.length);//运行多次输出 0 1 2
//通过输出可以看到 threadGroup.activeCount()结果所固有的不精确特性
// 中断线程组中所有线程
threadGroup.interrupt();
10、线程池的使用
线程池简述:开启一条线程是非常浪费资源的,因为它涉及到要与操作系统进行交互;因此JDK5之后Java提供了线程池让我们提高性能,线程池里的线程执行完后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
相关对象
Executors:创建线程池的工厂类。
创建方法:
// 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们
static ExecutorService newCachedThreadPool()
// 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程
static ExecutorService newFixedThreadPool(int nThreads)
// 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们,并在需要时使用提供的ThreadFactory创建新线程
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
// 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的ThreadFactory创建新线程
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
// 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
// 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
//创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程
public static ExecutorService newSingleThreadExecutor()
ExecutorService:线程池管理接口,提供了线程的操作方法。
// 启动一次顺序关闭,执行以前提交的任务,但不接受新任务
void shutdown()
// 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表
List<Runnable> shutdownNow()
// 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future
<T> Future<T> submit(Callable<T> task)
// 提交一个 Runnable任务用于执行,并返回一个表示该任务的 Future
Future<?> submit(Runnable task)
// 提交一个 Runnable任务用于执行,并返回一个表示该任务的 Future
<T> Future<T> submit(Runnable task, T result)
ExecutorService threadpool= Executors.newCachedThreadPool();
//提交任务
threadpool.submit(new Runnable() {
@Override
public void run() {
/**线程中执行逻辑**/
}
});
//这是实现线程的第三种方式
threadpool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
/**线程中执行逻辑**/
return null;
}
});
//停止任务
threadpool.shutdown();
注:线程池可以很大程度上提高性能,如存在多线程环境建议使用 。
更多方法查看API