Java 多线程操作

以前一听到多线程操作就感到好腻害好腻害的,如果你现在也是这种情况或许这篇文章能够帮助到你。
这里写图片描述

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

  • 1
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值