多线程是java中比较重要的一部分内容,使用多线程有许多的优点: - 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。 - 程序需要实现一些需要等待的任务时,可以提高计算机系统CPU的利用率 - 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
本文就多线程来做一个总结,希望可以给予大家一些帮助。
在java中实现多线程可以采用两种方式:继承Thread类、实现Runnable接口
class MyThread extends Thread { // 继承Thread类,作为线程的实现类
private String name; // 表示线程的名称
public MyThread(String name) {
this.name = name; // 通过构造方法配置name属性
}
public void run() { // 覆写run()方法,作为线程 的操作主体
for (int i = 0; i < 10; i++) {
System.out.println(name + "运行,i = " + i);
}
}
};
public class ThreadDemo02 {
public static void main(String args[]) {
MyThread mt1 = new MyThread("线程A "); // 实例化对象
MyThread mt2 = new MyThread("线程B "); // 实例化对象
mt1.start(); // 调用线程主体
mt2.start(); // 调用线程主体
}
};
Output: 线程A 运行,i = 0 线程B 运行,i = 0 线程A 运行,i = 1 线程B 运行,i = 1 线程A 运行,i = 2 线程B 运行,i = 2 线程B 运行,i = 3 线程B 运行,i = 4 线程A 运行,i = 3 线程B 运行,i = 5 线程A 运行,i = 4 线程B 运行,i = 6 线程A 运行,i = 5 线程B 运行,i = 7 线程A 运行,i = 6 线程B 运行,i = 8 线程A 运行,i = 7 线程B 运行,i = 9 线程A 运行,i = 8 线程A 运行,i = 9
从例子来看, 确实是并发执行的, 哪个线程先抢到cpu资源, 哪个线程就先执行。
问题:为什么不直接调用run()
方法, 而是通过start()
方法来调用(或者说start()
方法是怎么实现能启动多线程这个功能的)?-批注1
class MyThread implements Runnable { // 实现Runnable接口,作为线程的实现类
private String name; // 表示线程的名称
public MyThread(String name) {
this.name = name; // 通过构造方法配置name属性
}
public void run() { // 覆写run()方法,作为线程 的操作主体
for (int i = 0; i < 10; i++) {
System.out.println(name + "运行,i = " + i);
}
}
};
public class RunnableDemo01 {
public static void main(String args[]) {
MyThread mt1 = new MyThread("线程A "); // 实例化对象
MyThread mt2 = new MyThread("线程B "); // 实例化对象
Thread t1 = new Thread(mt1); // 实例化Thread类对象
Thread t2 = new Thread(mt2); // 实例化Thread类对象
t1.start(); // 启动多线程
t2.start(); // 启动多线程
}
};
OUtput: 线程B 运行,i = 0 线程A 运行,i = 0 线程B 运行,i = 1 线程A 运行,i = 1 线程B 运行,i = 2 线程A 运行,i = 2 线程A 运行,i = 3 线程A 运行,i = 4 线程A 运行,i = 5 线程A 运行,i = 6 线程A 运行,i = 7 线程A 运行,i = 8 线程A 运行,i = 9 线程B 运行,i = 3 线程B 运行,i = 4 线程B 运行,i = 5 线程B 运行,i = 6 线程B 运行,i = 7 线程B 运行,i = 8 线程B 运行,i = 9
实现Runnable接口比继承Thread类有如下明显优点
综合以上来看,开发中使用Runnable接口是最合适的,在以后的文章中,使用多线程时都将以Runnable接口的实现为操作的重点。
调度策略 - 时间片
- 抢占式:高优先级的线程抢占CPU
java的调度方法 - 同优先级线程组成先进先出队列(先到先服务),使用时间片策略 - 对高优先级,使用优先调度的抢占式策略
以卖火车票为例,如果现在要想买火车票的话可以去火车站或者去各个售票点,但是不管有多少个地方可以买火车票,最终一趟列车的火车票数量是固定的,如果把各个售票点理解为各个线程的话,则所有线程应该共同拥有同一份的票数。
class MyThread implements Runnable {
private int ticket = 5; // 假设一共有5张票
public void run() {
for (int i = 0; i < 100; i++) {
if (ticket > 0) { // 还有票
try {
Thread.sleep(300); // 加入延迟,可以使问题暴露的更加明显(会让线程执行到这里的时候必须切换出去, 不加切换出去的几率小)
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket = " + ticket--);
}
}
}
};
public class SyncDemo01 {
public static void main(String args[]) {
MyThread mt = new MyThread(); // 定义线程对象
Thread t1 = new Thread(mt); // 定义Thread对象
Thread t2 = new Thread(mt); // 定义Thread对象
Thread t3 = new Thread(mt); // 定义Thread对象
t1.start();
t2.start();
t3.start();
}
};
Output: 卖票:ticket = 5 卖票:ticket = 3 卖票:ticket = 4 卖票:ticket = 2 卖票:ticket = 1 卖票:ticket = 0 卖票:ticket = -1
从执行的结果来看, 卖出的票数成负数,程序代码出现了问题。
程序分析:
如图所示,线程当ticket等于1的时候,假设线程t1先进入if语句,紧接着执行睡眠, 线程被挂起,此时ticket还没有被减1。同理t2、t3线程就会在t1睡眠时候进入if语句,所以在最后,t1、t2、t3都执行了-1,ticket最后就变成了-1。 如果出现重票,原因也是类似的情况。
sysnchronized(同步监视器){
//需要被同步的代码(即为操作共享数据的代码)
}
注:在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this!
public synchronized void show (String name){
//代码
}
注:同步方法里面的同步监视器是this
//使用同步方法实现
class MyThread1 implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
this.sale() ; // 调用同步方法
}
}
public synchronized void sale(){ // 声明同步方法
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println("卖票:ticket = " + ticket-- );
}
}
};
//使用同步代码块
class MyThread implements Runnable {
private int ticket = 5; // 假设一共有5张票
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (this) { // 要对当前对象进行同步
if (ticket > 0) { // 还有票
try {
Thread.sleep(300); // 加入延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("卖票:ticket = " + ticket--);
}
}
}
}
};
public class SyncDemo02 {
public static void main(String args[]) {
//分别测试同步代码块和同步方法
//测试同步代码块
/* MyThread mt = new MyThread(); // 定义线程对象
Thread t1 = new Thread(mt); // 定义Thread对象
Thread t2 = new Thread(mt); // 定义Thread对象
Thread t3 = new Thread(mt); // 定义Thread对象
System.out.println("使用同步代码块:");
t1.start();
t2.start();
t3.start();*/
//测试同步方法
MyThread1 mt1 = new MyThread1(); // 定义线程对象
Thread t11 = new Thread(mt1); // 定义Thread对象
Thread t21 = new Thread(mt1); // 定义Thread对象
Thread t31 = new Thread(mt1); // 定义Thread对象
System.out.println("使用同步方法:");
t11.start();
t21.start();
t31.start();
}
};
Output: 卖票:ticket = 5 卖票:ticket = 3 卖票:ticket = 4 卖票:ticket = 2 卖票:ticket = 1 卖票:ticket = 0
程序分析:
如图所示,图中的②就是同步监视器,也就是俗称的锁。当没有线程进入时,②是绿色的,表示可以进入。当t1线程进入到run()方法时, ②将变成红色,阻止t2、t3线程的进入。直到t1线程的run()方法执行完毕,释放了②的3(也就是②变成绿色)。这样就保证了共享数据只会被一个线程操作,程序就不会出现重票、错票的情况了。
产生原因:过多的同步可能导致死锁,不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
//死锁的问题:处理线程同步时容易出现。
//不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
public class DeadLock {
static StringBuffer sb1 = new StringBuffer();
static StringBuffer sb2 = new StringBuffer();
public static void main(String[] args) {
//线程1
new Thread() {
public void run() {
synchronized (sb1) {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("A");
synchronized (sb2) {
sb2.append("B");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
//线程2
new Thread() {
public void run() {
synchronized (sb2) {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("C");
synchronized (sb1) {
sb2.append("D");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
}
}
Output: ……
唤醒和等待:notify、notifyAll、wait Object是所有类的父类,在此类中有以下几个方法是对线程操作有所支持的
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public final void wait() throws InterruptedException | 普通 | 线程等待 |
2 | public final void wait(long timeout,int nanos) throws InterruptedException | 普通 | 线程等待,并指定等待的最长时间,以毫秒为单位 |
3 | public final void wait(long timeout) throws InterruptedException | 普通 | 线程等待,并制定等待的最长毫秒及纳秒 |
4 | public final void notifyAll() | 普通 | 唤醒第一个等待的线程 |
5 | public final void notify() | 普通 | 唤醒全部等待的线程 |
wait():
令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
notify():
唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll ():
唤醒正在排队等待资源的所有线程结束等待. Java.lang.Object
提供的这三个方法只有在synchronized
方法或synchronized
代码块中才能使用,
否则会报java.lang.IllegalMonitorStateException
异常//使用同步代码块
class PrintNum1 implements Runnable {
int num = 1;
Object obj = new Object();
public void run() {
while (true) {
//synchronized()里面可以填写任意可以唯一确定的对象,也可以直接写this
synchronized (obj) {
obj.notify();
if (num <= 100) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出num的值并且让num加一
System.out.println(Thread.currentThread().getName() + ":" + num++);
} else
break;
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//使用同步方法
class PrintNum2 implements Runnable {
private int num = 1;
@Override
public void run() {
while (true) {
print();
//1.为什么在这里使用break不能终止线程?
if(num == 101)
break;
System.out.println(num);
}
}
private synchronized void print() {
notify();
if(num <= 100){
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + num++);
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestCommunication {
public static void main(String[] args) {
//测试同步代码块
// PrintNum1 p = new PrintNum1();
// Thread t1 = new Thread(p, "甲");
// Thread t2 = new Thread(p, "乙");
//测试同步方法
PrintNum2 p = new PrintNum2();
Thread t1 = new Thread(p, "甲");
Thread t2 = new Thread(p, "乙");
t1.start();
t2.start();
}
}
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。 这里可能出现两个问题: 生产者比消费者快时,消费者会漏掉一些数据没有取到。 消费者比生产者快时,消费者会取相同的数据。
/*
分析:
1.是否涉及到多线程的问题?是!生产者、消费者
2.是否涉及到共享数据?有!考虑线程的安全
3.此共享数据是谁?即为产品的数量
4.是否涉及到线程的通信呢?存在这生产者与消费者的通信
*/
class Clerk{//店员
int product;
public synchronized void addProduct(){//生产产品
if(product >= 20){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
product++;
System.out.println(Thread.currentThread().getName() + ":生产了第" + product + "个产品");
notifyAll();
}
}
public synchronized void consumeProduct(){//消费产品
if(product <= 0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println(Thread.currentThread().getName() + ":消费了第" + product + "个产品");
product--;
notifyAll();
}
}
}
class Producer implements Runnable{//生产者
Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
public void run(){
System.out.println("生产者开始生产产品");
while(true){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
class Consumer implements Runnable{//消费者
Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
public void run(){
System.out.println("消费者消费产品");
while(true){
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class TestProduceConsume {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
Consumer c1 = new Consumer(clerk);
Thread t1 = new Thread(p1);//一个生产者的线程
Thread t3 = new Thread(p1);
Thread t2 = new Thread(c1);//一个消费者的线程
t1.setName("生产者1");
t2.setName("消费者1");
t3.setName("生产者2");
t1.start();
t2.start();
t3.start();
}
}
JDK中用Thread.State
枚举表示了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有