Java基础系列:(4)多线程的一些用法示例

一 Java中线程的实现

(1)通过继承Thread类

package javase.thread;class MyThread extends Thread{

	public void run(){
		for(int i=0;i<10;i++)
			System.out.println("正在运行线程:" + currentThread().getName());
	}}public class ThreadDemo {

	public static void main(String[] args) {
		MyThread thread1 = new MyThread();
		MyThread thread2 = new MyThread();
		thread1.start();
		thread2.start();

	}}

(2)通过实现Runnable接口

package javase.thread;class MyRunnable implements Runnable{

	public void run() {
		for(int i=0;i<10;i++)
			System.out.println("正在运行线程: " + Thread.currentThread().getName());
	}
	}public class RunnableDemo {
	public static void main(String[] args) {
		MyRunnable runnable1 = new MyRunnable();
		MyRunnable runnable2 = new MyRunnable();
		new Thread(runnable1).start();
		new Thread(runnable2).start();
	}}

二 线程操作的一些方法
(1)线程的强制启动

在线程操作中,可以使用join()方法让一个线程强制启动。该线程强制启动期间,一直到该线程运行结束其他线程都不能运行,必须等待该线程结束之后才能继续运行

package javase.thread;class MyThread2 implements Runnable{

	public void run(){
		for(int i=0;i<6;i++)
			System.out.println("正在运行线程:" + Thread.currentThread().getName() + " --> " + i);
	}}public class ThreadJoinDemo {
	public static void main(String[] args) {
		MyThread2 demo = new MyThread2();
		Thread thread = new Thread(demo, "线程");
		thread.start();
		for(int i=0;i<6;i++){
			System.out.println("Main线程运行 --> " + i);
			if(i > 2){
				try {
					/**
					 * 线程强制运行
					 * 线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行
					 * */
					thread.join();  
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

输出:

Main线程运行 --> 0
正在运行线程:线程 --> 0
正在运行线程:线程 --> 1
正在运行线程:线程 --> 2
正在运行线程:线程 --> 3
正在运行线程:线程 --> 4
正在运行线程:线程 --> 5
Main线程运行 --> 1
Main线程运行 --> 2
Main线程运行 --> 3
Main线程运行 --> 4
Main线程运行 --> 5

注:结果不唯一
(2)中断线程

当一个线程运行时,在其他线程中可以中断该线程的运行状态。使用方法:interrupt()

package javase.thread;

class MyThread3 implements Runnable{

	public void run(){
		System.out.println("1 run方法开始执行");
		try {
			Thread.sleep(10000);
			System.out.println("2 休眠结束");
		} catch (InterruptedException e) {
			System.out.println("3 休眠被终止");
			return;
		}
		System.out.println("4 run方法正常结束");
	}
}

public class ThreadInterruptDemo {

	public static void main(String[] args) {
		Thread thread = new Thread(new MyThread3());
		thread.start();  //线程启动

		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread.interrupt();  //主线程暂停2s之后将其中断
	}

}

输出:

1 run方法开始执行
3 休眠被终止

可以看出,在主线程休眠2s之后就将线程类强制中断了,使其run方法并没有正常运行结束
(3)后台线程

前台线程结束了,后台线程仍然可以继续运行。使用方法:setDaemon(true)

package javase.thread;

class MyThread4 implements Runnable{
	public void run() {
		while(true)
			System.out.println(Thread.currentThread().getName() + "在运行");
	}

}

public class ThreadDaemonDemo {
	public static void main(String[] args) {
		Thread t = new Thread(new MyThread4(),"线程");
		t.setDaemon(true);  //此方法需要放在start方法之前才会生效
		t.start();
	}

}

看起来是一个不会结束的死循环,但是由于有了后台进程的缘故,程序仍然可以正常结束
(4)线程的优先级
在Java中,线程一共有3种优先级,分别是:

定义 描述 表示的常量
public static final int MIN_PRIORITY 线程可以具有的最低优先级 1
public static final int NORM_PRIORITY 分配给线程的默认优先级 5
public static final int MAX_PRIORITY 线程可以具有的最高优先级 10
package javase.thread;

class MyThread5 implements Runnable{

	public void run(){
		for(int i=0;i<3;i++){
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("正在运行线程:" + Thread.currentThread().getName() + " --> " + i);
		}
	}
}

public class ThreadPriorityDemo {

	public static void main(String[] args) {
		System.out.println("主方法的线程优先级: " + Thread.currentThread().getPriority());

		Thread t1 = new Thread(new MyThread5(), "线程1");
		Thread t2 = new Thread(new MyThread5(),"线程2");
		Thread t3 = new Thread(new MyThread5(),"线程3");
		t1.setPriority(Thread.MIN_PRIORITY);  // 1
		t2.setPriority(Thread.NORM_PRIORITY);  // 5
		t3.setPriority(Thread.MAX_PRIORITY);  // 10
		t1.start();
		t2.start();
		t3.start();
	}

}

输出:

主方法的线程优先级: 5
正在运行线程:线程1 --> 0
正在运行线程:线程2 --> 0
正在运行线程:线程3 --> 0
正在运行线程:线程1 --> 1
正在运行线程:线程2 --> 1
正在运行线程:线程3 --> 1
正在运行线程:线程2 --> 2
正在运行线程:线程1 --> 2
正在运行线程:线程3 --> 2

PS:从上面的示例可以看出,主线程的优先级是5,也就是默认的优先级。题外话:Java程序每次运行至少需要启动几个线程?答案是2,一个是main(主)线程,另一个是垃圾回收线程
(5)线程的礼让

在线程操作中,可以使用yield()方法让一个线程将执行权暂时让给另一个线程

package javase.thread;

class MyThread6 implements Runnable{

	public void run(){
		for(int i=0;i<6;i++){
			System.out.println("正在运行线程:" + Thread.currentThread().getName() + " --> " + i);

			if(i == 3){
				System.out.print(Thread.currentThread().getName() + "进行礼让: ");
				Thread.currentThread().yield();
			}
		}
	}
}

public class ThreadYieldDemo {
	public static void main(String[] args) {
		MyThread6 mThread6 = new MyThread6();
		Thread t1 = new Thread(mThread6, "线程A");
		Thread t2 = new Thread(mThread6, "线程B");

		t1.start();
		t2.start();
	}

}

输出:

正在运行线程:线程A --> 0
正在运行线程:线程B --> 0
正在运行线程:线程A --> 1
正在运行线程:线程B --> 1
正在运行线程:线程A --> 2
正在运行线程:线程A --> 3
正在运行线程:线程B --> 2
线程A进行礼让: 正在运行线程:线程B --> 3
正在运行线程:线程A --> 4
线程B进行礼让: 正在运行线程:线程A --> 5
正在运行线程:线程B --> 4
正在运行线程:线程B --> 5

三 线程的同步与死锁
(1)问题引出:

我这里以多个线程共同卖票来举例说明。通过Runnable接口实现了多线程,然后实例化3个线程对象共同卖10张票:

package javase.thread;

class MyThread7 implements Runnable{
	private int ticket = 10;  //多个线程共享
	public void run(){
		while(ticket > 0){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("卖票,剩余票数: ticket = " + --ticket);
		}
	}
}

public class SyncDemo1 {
	public static void main(String[] args) {
		MyThread7 mThread7 = new MyThread7();
		Thread t1 = new Thread(mThread7,"售票员A");
		Thread t2 = new Thread(mThread7,"售票员B");
		Thread t3 = new Thread(mThread7,"售票员C");

		t1.start();
		t2.start();
		t3.start();

	}

}

输出:

卖票,剩余票数: ticket = 9
卖票,剩余票数: ticket = 9
卖票,剩余票数: ticket = 8
卖票,剩余票数: ticket = 7
卖票,剩余票数: ticket = 7
卖票,剩余票数: ticket = 6
卖票,剩余票数: ticket = 4
卖票,剩余票数: ticket = 5
卖票,剩余票数: ticket = 3
卖票,剩余票数: ticket = 2
卖票,剩余票数: ticket = 2
卖票,剩余票数: ticket = 1
卖票,剩余票数: ticket = 0
卖票,剩余票数: ticket = 0
卖票,剩余票数: ticket = -1

可以看出,最后的结果竟然出现了负数。这就是因为在线程的执行过程中由于没有把需要一起执行的命令部分加锁,导致了本来应该一起执行的几条命令在执行了一部分之后被另一个线程抢过了执行权,因而最后的数据出错

(2)通过添加同步代码块的方式解决:

格式:

synchronized (同步对象) {

需要同步的代码

}

package javase.thread;

class MyThread8 implements Runnable{
	private int ticket = 10;
	public void run(){
		//添加同步代码块
		synchronized (this) {
			while(ticket > 0){
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("卖票,剩余票数: ticket = " + --ticket);
			}
		}

	}
}

public class SyncDemo2 {
	public static void main(String[] args) {
		MyThread8 mThread8 = new MyThread8();
		Thread t1 = new Thread(mThread8,"售票员A");
		Thread t2 = new Thread(mThread8,"售票员B");
		Thread t3 = new Thread(mThread8,"售票员C");

		t1.start();
		t2.start();
		t3.start();

	}
}

输出:

卖票,剩余票数: ticket = 9
卖票,剩余票数: ticket = 8
卖票,剩余票数: ticket = 7
卖票,剩余票数: ticket = 6
卖票,剩余票数: ticket = 5
卖票,剩余票数: ticket = 4
卖票,剩余票数: ticket = 3
卖票,剩余票数: ticket = 2
卖票,剩余票数: ticket = 1
卖票,剩余票数: ticket = 0

可以看出这下结果就很正确了

(3)通过添加同步方法的方式解决:

格式:

访问权限{public|default|protected|private} synchronized 方法返回值 方法名称(参数列表){

}

package javase.thread;

class MyThread9 implements Runnable {
	private int ticket = 10;

	public void run() {
		sale();
	}
	/**
	 * 同步方法
	 * */
	public synchronized void sale() {
		while (ticket > 0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("卖票,剩余票数: ticket = " + --ticket);
		}

	}
}

public class SyncDemo3 {
	public static void main(String[] args) {
		MyThread9 mThread9 = new MyThread9();
		Thread t1 = new Thread(mThread9, "售票员A");
		Thread t2 = new Thread(mThread9, "售票员B");
		Thread t3 = new Thread(mThread9, "售票员C");

		t1.start();
		t2.start();
		t3.start();

	}

}

输出:略

四 线程操作经典实例——生产者与消费者

要求是:生产者生产出一个产品后消费者才能消费,否则等待,一直到生产者生产出产品后将消费者唤醒;反之亦然。关于等待和唤醒分别是这两个方法:wait()和notify()。代码如下:

package javase.thread;

/**
 * 表示一个网站的基本信息
 * */
class Info {
	private String name;
	private String url;
	private boolean flag = false; // 标记赋值状态,false表示还未赋值

	/**
	 * 赋值
	 * */
	public synchronized void setValues(String name, String url) {
		// 如果已被赋值,等待
		if (flag) {
			try {
				this.wait(); // 已经赋值而且值未被使用,则一直等待被使用
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		this.name = name;
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.url = url;

		flag = true; // 赋值之后,更新标记状态
		this.notify(); // 赋值之后,唤醒等待取值的线程

	}

	/**
	 * 取值
	 * */
	public synchronized void getValues() {
		// 如果未被赋值,等待
		if (!flag) {
			try {
				this.wait(); // 上一次赋的值已经使用过,下一次的还未生产
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("站点:" + name + " --> URL:" + url);
		flag = false;
		this.notify(); // 唤醒等待赋值的线程

	}
}

/**
 * 生产者,负责给参数赋值
 * */
class Producer implements Runnable {
	private Info info = null;

	public Producer(Info info) {
		this.info = info;
	}

	public void run() {
		for (int i = 0; i < 10; i++) {
			// 交替赋值
			if (i % 2 == 0)
				info.setValues("zifangsky的个人博客", "http://www.zifangsky.cn");
			else
				info.setValues("51CTO博客", "http://blog.51cto.com");
		}
	}
}

/**
 * 消费者,负责取值
 * */
class Consumer implements Runnable {
	private Info info = null;

	public Consumer(Info info) {
		this.info = info;
	}

	public void run() {
		for (int i = 0; i < 10; i++)
			info.getValues();
	}
}

public class Demo {
	public static void main(String[] args) {
		Info info = new Info();
		Producer producer = new Producer(info);
		Consumer consumer = new Consumer(info);

		new Thread(producer).start();
		new Thread(consumer).start();

	}

}

输出:

站点:zifangsky的个人博客 --> URL:http://www.zifangsky.cn
站点:51CTO博客 --> URL:http://blog.51cto.com
站点:zifangsky的个人博客 --> URL:http://www.zifangsky.cn
站点:51CTO博客 --> URL:http://blog.51cto.com
站点:zifangsky的个人博客 --> URL:http://www.zifangsky.cn
站点:51CTO博客 --> URL:http://blog.51cto.com
站点:zifangsky的个人博客 --> URL:http://www.zifangsky.cn
站点:51CTO博客 --> URL:http://blog.51cto.com
站点:zifangsky的个人博客 --> URL:http://www.zifangsky.cn
站点:51CTO博客 --> URL:http://blog.51cto.com

附:另一个类似实例,关于生产电脑和卖电脑:

package javase.thread;

class Computer{
	private String name;
	private boolean status = false;  //标记computer状态,false表示还未生产
	private int sum = 0;

	public synchronized String getName() {
		if(!status){
			//未生产,等待电脑生产
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		status = false;
		notify();
		return name;
	}

	public synchronized void setName(String name) {
		if(status){
			/**
			 * 如果生产的电脑没有卖出,则要等待电脑卖出之后再生产,
			 * 并统计出生产的电脑数量
			 * */
			System.out.println("到目前为止,一共生产出了 " + sum + " 台电脑");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		this.name = name;
		sum++;
		status = true;
		notify();
	}

}
/**
 * 生产电脑
 * */
class ComputerProducer implements Runnable{
	Computer computer = null;

	public ComputerProducer(Computer computer) {
		this.computer = computer;
	}

	public void run() {
		for(int i=0;i<10;i++)
			computer.setName("zifangsky00" + i);
	}

}
/**
 * 卖电脑
 * */
class ComputerConsumer implements Runnable{
	Computer computer = null;

	public ComputerConsumer(Computer computer) {
		this.computer = computer;
	}

	public void run() {
		for(int i=0;i<10;i++)
			System.out.println("卖出电脑,编号是: " + computer.getName());
	}

}

public class Demo2 {

	public static void main(String[] args) {
		Computer computer = new Computer();
		ComputerProducer producer = new ComputerProducer(computer);
		ComputerConsumer consumer = new ComputerConsumer(computer);

		new Thread(producer).start();
		new Thread(consumer).start();

	}

}

输出:

到目前为止,一共生产出了 1 台电脑
卖出电脑,编号是: zifangsky000
到目前为止,一共生产出了 2 台电脑
卖出电脑,编号是: zifangsky001
到目前为止,一共生产出了 3 台电脑
卖出电脑,编号是: zifangsky002
到目前为止,一共生产出了 4 台电脑
卖出电脑,编号是: zifangsky003
到目前为止,一共生产出了 5 台电脑
卖出电脑,编号是: zifangsky004
到目前为止,一共生产出了 6 台电脑
卖出电脑,编号是: zifangsky005
到目前为止,一共生产出了 7 台电脑
卖出电脑,编号是: zifangsky006
到目前为止,一共生产出了 8 台电脑
卖出电脑,编号是: zifangsky007
到目前为止,一共生产出了 9 台电脑
卖出电脑,编号是: zifangsky008
卖出电脑,编号是: zifangsky009
时间: 2024-10-07 23:50:15

Java基础系列:(4)多线程的一些用法示例的相关文章

夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理

目录 final使用 final变量 final修饰基本数据类型变量和引用 final类 final关键字的知识点 final关键字的最佳实践 final的用法 关于空白final final内存分配 使用final修饰方法会提高速度和效率吗 使用final修饰变量会让变量的值不能被改变吗: 如何保证数组内部不被修改 final方法的三条规则 final 和 jvm的关系 写 final 域的重排序规则 读 final 域的重排序规则 如果 final 域是引用类型 参考文章 微信公众号 Jav

夯实Java基础系列9:深入理解Class类和Object类

目录 Java中Class类及用法 Class类原理 如何获得一个Class类对象 使用Class类的对象来生成目标类的实例 Object类 类构造器public Object(); registerNatives()方法; Clone()方法实现浅拷贝 getClass()方法 equals()方法 hashCode()方法; toString()方法 wait() notify() notifAll() finalize()方法 CLass类和Object类的关系 参考文章 微信公众号 Ja

夯实Java基础系列13:深入理解Java中的泛型

目录 泛型概述 一个栗子 特性 泛型的使用方式 泛型类 泛型接口 泛型通配符 泛型方法 泛型方法的基本用法 类中的泛型方法 泛型方法与可变参数 静态方法与泛型 泛型方法总结 泛型上下边界 泛型常见面试题 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下Star.Fork.Watch三连哈,感谢你的

Spring基础系列5 -- bean的基本用法

Spring基础系列5 -- bean的基本用法 转载:http://www.cnblogs.com/leiOOlei/p/3532604.html 本篇讲述了Bean的基本配置方法,以及Spring中怎样运用Bean. 主要内容如下: 一.      Spring中Bean的相互引用 二.      Spring中给Bean属性注入value 三.      Spring Inner Bean—内部嵌套的Bean 四.      Spring Bean Scopes—Bean的作用域 五.  

夯实Java基础系列6:一文搞懂抽象类和接口,从基础到面试题,揭秘其本质区别!

目录 抽象类介绍 为什么要用抽象类 一个抽象类小故事 一个抽象类小游戏 接口介绍 接口与类相似点: 接口与类的区别: 接口特性 抽象类和接口的区别 接口的使用: 接口最佳实践:设计模式中的工厂模式 接口与抽象类的本质区别是什么? 基本语法区别 设计思想区别 如何回答面试题:接口和抽象类的区别? 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl

夯实Java基础系列7:一文读懂Java 代码块和执行顺序

目录 Java中的构造方法 构造方法简介 构造方法实例 例 1 例 2 Java中的几种构造方法详解 普通构造方法 默认构造方法 重载构造方法 java子类构造方法调用父类构造方法 Java中的代码块简介 Java代码块使用 局部代码块 构造代码块 静态代码块 Java代码块.构造方法(包含继承关系)的执行顺序 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github

夯实Java基础系列10:深入理解Java中的异常体系

目录 为什么要使用异常 异常基本定义 异常体系 初识异常 异常和错误 异常的处理方式 "不负责任"的throws 纠结的finally throw : JRE也使用的关键字 异常调用链 自定义异常 异常的注意事项 当finally遇上return JAVA异常常见面试题 参考文章 微信公众号 Java技术江湖 个人公众号:黄小斜 - Java异常 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.c

Java基础】并发 - 多线程

Java基础]并发 - 多线程 分类: Java2014-05-03 23:56 275人阅读 评论(0) 收藏 举报 Java 目录(?)[+] 介绍 Java多线程 多线程任务执行 大多数并发应用程序时围绕执行任务(task)进行管理的:所谓任务就是抽象的,离散的工作单元. 围绕执行任务来管理应用程序时,第一步是要指明一个清晰的任务边界.大多数应用服务器程序都选择了下面这个自然的任务辩解:单独的客户请求: 任务时逻辑上的单元: 任务 Runnable 表示一个任务单元(java.lang)

黑马程序员——Java基础知识之多线程协同

多线程协同 线程间的通讯:对资源的操作动作不同,比如说两个卡车一个拉煤一个装煤,但是他们共享了一个资源. 怎么样把这个资源拿出来?怎样把车装满?这个资源当然是一个类,他里面的组成元素就是对象!!现在我们就要有操作对象的思想了,用对象把这车装满,现在一车装一个对象. 等待唤醒机制: 用的不是sleep是wait.flag标记,这是两人沟通的方式.其实每个标记就要做一次等待或者notify,判断wait,改值notify.线程池.notify唤醒里面的线程,按顺序唤醒.wait和notify必须用在

【小白的java成长系列】——多线程初识(多人买票问题)

本来这节内容是要到后面来说的,因为最近在弄并发的问题,推荐一本书<java并发编程实战>,深入的讲解了多线程问题的.本人最近也刚好在看这本书,还不错的~ 多线程的相关概念,就不用说了的,自己可以去网上查找,有一大堆关于它的讲解~ 先来看看买票的程序: package me.javen.thread.one; public class TicketDemo { public static void main(String[] args) { // 使用Thread类的方式 // TicketTh