Java 多线程操作
来源:互联网 发布:cool edit for mac版 编辑:程序博客网 时间:2024/05/20 00:15
以前一听到多线程操作就感到好腻害好腻害的,如果你现在也是这种情况或许这篇文章能够帮助到你。
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
- JAVA多线程数据库操作
- JAVA多线程数据库操作
- JAVA的多线程操作
- Java多线程操作
- Java多线程操作
- java 多线程操作
- Java 多线程操作
- java操作多线程
- Java 多线程操作
- Java Thread 多线程 操作线程
- Java Thread 多线程 操作线程
- Java多线程--线程操作范例
- java 多线程操作同一个变量
- java 多线程+队列 ping 操作
- java 多线程基本操作总结
- JAVA多线程读写文件操作
- Java Thread 多线程 操作线程
- java多线程之批量操作
- 装饰者模式-c++实现
- Java设计模式之构建者模式
- Oracle中对Scott用户解锁
- 【Unity Shader】新书封面 — Low Polygon风格的渲染
- leetcode---Permutations II
- Java 多线程操作
- android的工具类
- 狼爪兔子 gcd
- leetcode---Pow(x, n)---快速幂
- CSS3 animation
- SpringMVC配置<mvc:resources mapping="/resources/**" location="/WEB-INF/resources/" />访问页面404错误
- Mysql查询使用Limit优化
- JSP中的相对路径和绝对路径
- 六爻:占卜记录(By eof)