采用Runable接口实现并发与同步、线程休眠与同步锁、线程间的通信(协调同步线程)、线程的优先级、加入线程、等待线程、守护线程 Java第十八天(三)

这篇博客深入探讨了Java中的多线程概念,包括通过实现Runable接口创建线程,线程同步避免并发异常,线程休眠与同步锁的使用,线程间的通信策略,线程优先级的设置和获取,join方法用于线程加入,yield方法用于线程等待,以及如何将线程设置为守护线程。通过实例代码详细阐述了每个主题的应用。

线程

 并发(线程同步)

  采用实现Runable接口的方式实现

  由于是使用实现Runnable接口的方式实现,所以在获取线程名时不能直接获取,需使用Thread类的静态方法。

   当只有一个代码块使用一把锁时

 线程类 

public class SealTicketsThread implements Runnable{
//	由于使用实现Runnable接口的方式,以至于票数这个数据被线程共享,所以这里是对象的属性(成员变量)
	private int ticketsCount = 50;
	
//	由于使用实现Runable接口的方式,以至于同步锁对象被线程共享,所以这里是对象的属性(成员变量)
//	创建同步锁锁的对象
	private  Object obj = new Object();
	/**
	 * 重写run方法进行售票
	 */
	@Override
	public void run() {
			while(true) {
//				这里使用同步锁将并发代码锁起来
				synchronized (obj) {
				if(ticketsCount > 0) {
					System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
				}else {
					System.out.println(Thread.currentThread().getName()+"票已售完!");
					break;
				}
			}
		}
	}
}

 测试类 

public class SealTicketsTest {
	public static void main(String[] args) {
		/**
		 * 创建售票类的对象,相当于是票
		 */
		SealTicketsThread sth1 = new SealTicketsThread();
//		这里使用支持自定义线程名的Thread构造器,相当于是窗口
		Thread th1 = new Thread(sth1,"窗口1");
		Thread th2 = new Thread(sth1,"窗口2");
		Thread th3 = new Thread(sth1,"窗口3");
		Thread th4 = new Thread(sth1,"窗口4");
		
//		启动线程
		th1.start();
		th2.start();
		th3.start(); 
		th4.start();
	}
}
   当不同的代码块使用同一把同步锁时,不会发生并发异常。

 线程类 

/**
 * 当同步锁为任意类对象时,让不同的代码块使用同一把同步锁
 */
public class SealTicketsThreadSameLock implements Runnable{
//	由于使用实现Runnable接口的方式,以至于票数这个数据被线程共享,所以这里是对象的属性(成员变量)
	private int ticketsCount = 500;
	
//	由于使用实现Runable接口的方式,以至于同步锁对象被线程共享,所以这里是对象的属性(成员变量)
//	创建同步锁锁的对象
	private Test2 test2 = new Test2();
	
	/**
	 * 重写run方法进行售票
	 */
	@Override
	public void run() {
			while(true) {
				int i = 0;
//				考虑奇偶情况来分别执行不同的代码块
				if(i%2 == 0) {
//					这里使用同步锁将并发代码锁起来
					synchronized (test2) {
					if(ticketsCount > 0) {
						System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
					}else {
						System.out.println(Thread.currentThread().getName()+"票已售完!");
						break;
					}
				}
			}else {
//				这里使用相同的同步锁将并发代码锁起来
				synchronized (test2) {
				if(ticketsCount > 0) {
					System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
				}else {
					System.out.println(Thread.currentThread().getName()+"票已售完!");
				}
			}
		}
			i++;
		}
	}
}
class Test2{
}

 测试类 

public class SealTicketsTest {
	public static void main(String[] args) {
		/**
		 * 创建售票类的对象,相当于是票
		 */
		SealTicketsThreadSameLock sth = new SealTicketsThreadSameLock();
//		这里使用支持自定义线程名的Thread构造器,相当于是窗口
		Thread th1 = new Thread(sth,"窗口1");
		Thread th2 = new Thread(sth,"窗口2");
		Thread th3 = new Thread(sth,"窗口3");
		Thread th4 = new Thread(sth,"窗口4");
		
//		启动线程
		th1.start();
		th2.start();
		th3.start(); 
		th4.start();
	}
}
   针对上面的代码我们可以将第二个代码块使用方法封装起来。

注意:当同步锁不是静态成员变量时,将不能被所有对象共享。票数同理。

package com.threadExampleImplementsRunnable;
/**
 * 当同步锁为任意类对象时,让不同的代码块使用同一把同步锁
 */
public class SealTicketsThreadSameLock implements Runnable{
//	由于使用实现Runnable接口的方式,以至于票数这个数据被线程共享
//  但不被对象共享,所以我们要将它声明为静态的,这样我们声明的方法才能使用这个数据
	private static int ticketsCount = 500;
	
//	由于使用实现Runable接口的方式,以至于同步锁对象被线程共享
//  但不被对象共享,所以我们要将它声明为静态的,这样我们声明的方法才能使用这个数据
//	创建同步锁锁的对象
	private static Test2 test2 = new Test2();
	
	/**
	 * 重写run方法进行售票
	 */
	@Override
	public void run() {
			while(true) {
				int i = 0;
//				考虑奇偶情况来分别执行不同的代码块
				if(i%2 == 0) {
//					这里使用同步锁将并发代码锁起来
					synchronized (test2) {
					if(ticketsCount > 0) {
						System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
					}else {
						System.out.println(Thread.currentThread().getName()+"票已售完!");
						break;
					}
				}
			}else {
//				调用使用方法封装的代码块
				saleTickets();
		}
			i++;
		}
	}
//	声明一个方法,将第二个代码块封装起来
	public static void saleTickets() {
//		这里使用相同的同步锁将并发代码锁起来
		synchronized (test2) {
		if(ticketsCount > 0) {
			System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");
		}else {
			System.out.println(Thread.currentThread().getName()+"票已售完!");
		}
	}
	}
}
class Test2{
}
   当synchronized修饰非静态方法时,虽然我们不能指定锁对象,但是其同步锁对象为当前对象。

 线程类 


public class SealTicketsThread implements Runnable{
//	由于使用实现Runnable接口的方式,以至于票数这个数据被线程共享,所以这里是对象的属性(成员变量)
	private int ticketsCount = 200;
	
//	由于使用实现Runable接口的方式,以至于同步锁对象被线程共享,所以这里是对象的属性(成员变量)
//	创建同步锁锁的对象
	private  Object obj = new Object();
	/**
	 * 重写run方法进行售票
	 */
	@Override
	public void run() {
			while(true) {
//				这里使用同步锁将并发代码锁起来
				synchronized (obj) {
				if(ticketsCount > 0) {
					System.out.println(Thread.currentThread().getName()+"正在出售第"+ ticketsCount-- +"张票");`在这里插入代码片`
				}else {
					System.out.println(Thread.currentThread().getName()+"票已售完!");
					break;
				}
			}
		}
	}
}

 测试类 

public class SealTicketsTest {
	public static void main(String[] args) {
		/**
		 * 创建售票类的对象,相当于是票
		 */
		SealTicketsThreadStaticMethod sth = new SealTicketsThreadStaticMethod();
//		这里使用支持自定义线程名的Thread构造器,相当于是窗口
		Thread th1 = new Thread(sth,"窗口1");
		Thread th2 = new Thread(sth,"窗口2");
		Thread th3 = new Thread(sth,"窗口3");
		Thread th4 = new Thread(sth,"窗口4");
		
//		启动线程
		th1.start();
		th2.start();
		th3.start(); 
		th4.start();
	}
}

 线程的休眠(sleep方法)与同步锁

  线程的休眠是很必要的,当代码在服务器端运行时,为了减少服务器的压力,我们需要去依照运行场景进行适当的休眠,如果休眠是在带有同步锁代码块中执行,那么线程在休眠时不会让出锁对象(带着同步锁进行休眠)

static void 		sleep(long millis) 
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的
精度和准确性。
(当线程执行了该方法就会立刻让出CPU的使用权,使当前线程处于中断状态。经过指定的时间后,该
线程就重新进到线程队列中等待CPU资源,以便从中断处继续运行。)

 线程类 

public class ThreadSleepDemo implements Runnable{
	@Override
	public void run() {
		while(true) {
			try {
//				这里单位是毫秒,此句代码将产生异常,并且我们必须处理
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(new Date());
		}
	}
}

 测试类 

public class ThreadSleepTest {
	public static void main(String[] args) {
		ThreadSleepDemo threadSleepDemo = new ThreadSleepDemo();
		Thread t = new Thread(threadSleepDemo);
		t.start();
	}
}

 线程间的通信(协调同步的线程)

在这里插入图片描述
  前提:厂家生产货物,经销商出售货物,消费者购买货物,经销商不得囤积货物,一旦经销商有货物消费者就会购买。
  当经销商有货物时,厂家则不生产并处于等待生产状态,消费者则开始购买。
  当经销商没有货物时,经销商会给厂家发送需求信息,厂家在收到信息后则开始生产货物,并供给经销商。
注意:wait()方法与notify()方法成对出现!
 货物类 

/**
 *货物类
 */
public class Goods {
//	声明货物名
	private String goodsName;
//	声明货物状态
	private boolean isExsit;
	
	/**
	 * 对成员变量进行封装
	 */
	public String getGoodsName() {
		return goodsName;
	}
	public void setGoodsName(String goodsName) {
		this.goodsName = goodsName;
	}
	public boolean isExsit() {
		return isExsit;
	}
	public void setExsit(boolean isExsit) {
		this.isExsit = isExsit;
	}
}

 厂家线程类 

/**
 *厂家线程类
 */
public class Factory implements Runnable{
//	由于货物是厂家线程与消费者的共享数据,所以要传递过来
	private Goods goods;
	
	public Factory(Goods goods) {
		super();
		this.goods = goods;
	}

	@Override
	public void run() {
//		由于消费者实时消费,所以厂家也实时生产
		while(true) {
//			当多个线程操作共享数据时,此块操作共享数据的代码必须使用同步锁
//			由于货物是厂家线程与消费者线程的共享数据,所以要使用同步锁
			synchronized (goods) {
//				这里需要判断经销商是否有库存货物
				if(goods.isExsit()) {
//					如果经销商有库存货物,厂家则不生产,并处于等待生产状态
//					wait()方法属于Object类
					try {
//						此时厂家的线程被挂起,变为阻塞状态
						goods.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
//				如果经销商没有货物,厂家则开始生产货物
				System.out.println("厂家正在生产 "+goods.getGoodsName()+" 并供给经销商中.....");
//				此时货物已被生产并供给经销商完毕,所以更改货物状态并通知消费者开始购买
				goods.setExsit(true);
				goods.notify();
			}
		}
	}
}

 消费者线程类 

/**
 *消费者线程类
 */
public class Customer implements Runnable{
//	由于货物是厂家线程与消费者的共享数据,所以要传递过来
	private Goods goods;
	
	public Customer(Goods goods) {
		super();
		this.goods = goods;
	}

	@Override
	public void run() {
//		消费者实时消费
		while(true) {
//			当多个线程操作共享数据时,此块操作共享数据的代码必须使用同步锁
//			由于货物是厂家线程与消费者线程的共享数据,所以要使用同步锁
			synchronized (goods) {
//				这里需要判断经销商是否有库存货物
				if(!goods.isExsit()) {
//					如果经销商没有库存货物,消费者则处于等待购买状态
//					wait()方法属于Object类
					try {
//						此时消费者的线程被挂起,变为阻塞状态
						goods.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
//				如果经销商有货物,消费者则开始购买
				System.out.println("消费者购买了 "+goods.getGoodsName());
//				由于货物被消费者购买,所以货物状态变为无,并通知厂家开始生产
				goods.setExsit(false);
				goods.notify();
			}
		}
	}
}

 测试类 

public class Test {
	public static void main(String[] args) {
		Goods goods = new Goods();
		goods.setGoodsName("货物");
		goods.setExsit(false);
		
		Factory factory = new Factory(goods);
		Customer customer = new Customer(goods);
		
		Thread t = new Thread(factory,"厂家");
		Thread t2 = new Thread(customer,"消费者");
		
		t.start();
		t2.start();
	}
}

 线程的优先级(set、getPriority()方法)

返回值类型		方法名		参数列表
void 			setPriority(int newPriority) 
更改此线程的优先级。  
---------------------------------------------------------------------------------
int 			getPriority() 
返回此线程的优先级。  
---------------------------------------------------------------------------------
Modifier and Type(修饰符和类型)Constant Field(常数域)Value(值)
public static final intMAX_PRIORITY(最大优先级)10
public static final intMIN_PRIORITY(最小优先级)1
public static final intNORM_PRIORITY(默认优先级)5

  由上面的表格我们可以得出:我们在创建线程后若未设置优先级,那么该线程的优先级为默认优先级,也就是5;线程的最大优先级为10,最小的优先级为1。
  线程的优先级不是绝对的,只是相对来说能够比其他低等级优先级的线程得到CPU的资源机会更多一些。

 线程类 

public class ThreadPriority extends Thread{
	@Override
	public void run() {
		for(int i = 1;i<30;i++) {
			System.out.println(this.getName()+"线程的第 "+i+" 次输出");
		}
	}
}

 测试类 

public class TestThreadPriority {
	public static void main(String[] args) {
		ThreadPriority tp = new ThreadPriority();
		ThreadPriority tp2 = new ThreadPriority();
		ThreadPriority tp3 = new ThreadPriority();
		
//		未设置线程优先级
		System.out.println("未设置线程优先级:");
		System.out.println(tp.getPriority());
		System.out.println(tp2.getPriority());
		System.out.println(tp3.getPriority());
		
		tp.setPriority(10);
		tp2.setPriority(2);
		tp3.setPriority(3);
				
//		设置线程优先级后
		System.out.println("设置线程优先级后:");
		System.out.println(tp.getPriority());
		System.out.println(tp2.getPriority());
		System.out.println(tp3.getPriority());
		
		tp.start();
		tp2.start();
		tp3.start();
	}
}

 加入线程(join方法)

void join() 
等待这个线程死亡。  
---------------------------------------------------------------------------------

  使用join()方法的线程会被优先分配到CPU资源并开始执行线程,然后其他线程依次被分到CPU资源进行执行。
注意:只有在线程启动后才可以被分配到CPU资源。
 线程类 

public class ThreadJoin extends Thread{
	@Override
	public void run() {
		for(int i = 1;i<30;i++) {
			System.out.println(this.getName()+"线程的第 "+i+" 次输出");
		}
	}
}

 测试类 

public class TestThreadJoin {
	public static void main(String[] args) {
		ThreadJoin tp0 = new ThreadJoin();
		ThreadJoin tp1 = new ThreadJoin();
		ThreadJoin tp2 = new ThreadJoin();
		
//		这里要先启动线程
		tp2.start();
		tp0.start();
		tp1.start();
		
//		设置线程tp2优先被分配到CPU资源,并执行其线程
		try {
			tp2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

 等待线程(yield()方法)

  使当前线程从运行阶段回到就绪阶段,目的是为了将CPU的资源让给其他线程,使线程执行得更均匀一些。

static void 			yield() 
暂停当前正在执行的线程对象,并执行其他线程。
---------------------------------------------------------------------------------

 线程类 

public class ThreadYield extends Thread{
	@Override
	public void run() {
		for(int i = 1;i<30;i++) {
			System.out.println(this.getName()+"线程的第 "+i+" 次输出");
//			暂停当前正在执行的线程对象,并执行其他线程。
			Thread.yield();
		}
	}
}

 测试类 

public class TestThreadYield {
	public static void main(String[] args) {
		ThreadYield tp0 = new ThreadYield();
		ThreadYield tp1 = new ThreadYield();
		ThreadYield tp2 = new ThreadYield();
		
//		这里要先启动线程
		tp0.start();
		tp1.start();
		tp2.start();
	}
}

 守护线程(setDaemon()方法)

void 			setDaemon(boolean on) 
将此线程标记为 daemon线程或用户线程。  
---------------------------------------------------------------------------------

  线程默认是非守护线程,非守护线程也称作用户(user)线程,一个线程调用void setDaemon(boolean on)方法可以将自己设置成一个守护(Daemon)线程。
  当程序中的所有用户线程都已结束运行时,即使守护线程的run方法中还有需要执行的语句,守护线程也会立刻结束运行。即守护线程会随着主线程消亡而消亡。
  我们可以使用守护线程做一些不是很严格的工作,线程的随时结束不会产生什么不良的后果。一个线程必须在运行之前自己是否是守护线程。
 线程类 

public class ThreadDaemon extends Thread{
	@Override
	public void run() {
		for(int i = 1;i<30;i++) {
			System.out.println(this.getName()+"线程的第 "+i+" 次输出");
		}
	}
}

 测试类 

public class TestThreadDaemon {
//	主方法也就是主线程
	public static void main(String[] args) {
		ThreadDaemon tp0 = new ThreadDaemon();
		ThreadDaemon tp1 = new ThreadDaemon();
		ThreadDaemon tp2 = new ThreadDaemon();
		
//		在启动线程前设置守护线程
		tp0.setDaemon(true);
		tp1.setDaemon(true);
		tp2.setDaemon(true);
		
//		这里要先启动线程
		tp0.start();
		tp1.start();
		tp2.start();
		
//		设置主线程在循环打印十次就结束运行
		for(int i = 1;i<=3;i++) {
			System.out.println(Thread.currentThread().getName()+"主线程运行的第 " + i+ " 次");
		}
	}
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值