第十五节、轻松学Java_线程与并发

2021/7/22 12:08:22

本文主要是介绍第十五节、轻松学Java_线程与并发,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

      • 线程创建与线程的状态转换
        • 进程与线程
        • 线程的创建
        • 线程的状态与转换
      • 线程同步
      • 死锁
      • 线程交互
      • 线程调度

线程创建与线程的状态转换

进程与线程

线程与进程的主要差别体现在两个方面:
1、同样作为基本的执行单位,线程是划分地更小的执行单位。
2、每个进程都有一段专用的内存区域。与此相反,线程则共享存储单元(包括代码和数据),通过共享的内存单元来实现数据交换,实时通信与必要的同步操作。

线程的创建

在 Java 语言中, 线程也是一种对象, 但并不是任何对象都可以成为线程, 只有实现了 Runnable 接口或者继承了 Thread 类的对象才能成为线程。
线程的创建有两种方式: 一种是继承 Thread 类, 另一种是实现 Runnable 接口。
要启动线程必须调用Thread类之中的start()方法,而调用了start()方法,也就是调用了run()方法。
如果一个类继承了某一个类, 同时又想采用多线程技术, 就不能用 Thread 类产生线程, 因为 Java不允许多继承, 这时要用 Runnable 接口来创建线程。
在 Runnable 接口中并没有start()方法,所以一个类实现了Runnable 接口也必须用Thread类之中的start()方法来启动多线程。

线程的状态与转换

在这里插入图片描述

线程创建后, 调用 start()方法进入就绪状态, 在就绪队列里等待执行;当线程执行 run()方法时, 线程进入运行状态。
当线程调用 Thread 类的 sleep()静态方法时, 线程进入睡眠状态。(注意:调用wait()方法,线程会释放资源;调用sleep()方法,线程不会释放资源)
当线程调用 Thread 类的 join()方法时, 合并某个线程。 即当前线程进入阻塞状态, 被调用的线程执行。
当线程调用 Thread 类的 yield()静态方法时, 线程让出 CPU 资源, 从运行状态进入阻塞状态。

package com.test;

class JoinTest implements Runnable {
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("我是:" + Thread.currentThread().getName());
			try {
				Thread.sleep(1000); // 睡眠1s
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
		JoinTest j = new JoinTest();
		Thread t = new Thread(j);
		t.setName("子线程");
		t.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("我是主线程");
			if (i == 5) {
				try {
					t.join();// 合并子线程
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
		//主线程睡眠结束后,如果子线程没有结束,则中断子线程
		t.interrupt();
	}
}

输出:
我是主线程
我是:子线程
我是主线程
我是主线程
我是主线程
我是主线程
我是主线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是:子线程
我是主线程
我是主线程
我是主线程
我是主线程

package com.test;

class YieldTest implements Runnable {
	public void run() {
		for (int i = 1; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
			if (i % 3 == 0) {
				Thread.yield(); // 让出CPU 资源
			}
		}
	}

	public static void main(String[] args) {
		YieldTest y = new YieldTest();
		Thread t1 = new Thread(y);
		Thread t2 = new Thread(y);
		t1.setName("thread1");
		t2.setName("thread2");
		t1.start();
		t2.start();
	}
}

输出:
thread2:1
thread1:1
thread2:2
thread1:2
thread2:3
thread1:3
thread2:4
thread1:4
thread2:5
thread1:5
thread2:6
thread1:6
thread2:7
thread1:7
thread2:8
thread1:8
thread2:9
thread1:9

线程同步

同步代码块:同步代码块是使用 synchronized 关键字定义的代码块, 但是在同步的时候需要设置一个对象锁, 一般都会给当前对象 this 上锁
如果在一个方法上使用了 synchronized 定义, 那么此方法就称为同步方法。
所谓同步就是指一个线程等待另一个线程操作完再继续的情况。

package com.test;

class MyThread implements Runnable { // 线程主体类
	private int ticket = 6;
	//注释子类重写父类中的方法
	@Override
	public void run() { // 理解为线程的主方法
		for (int x = 0; x < 50; x++) {
			synchronized (this) {
				if (this.ticket > 0) { // 卖票的条件
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
				}
			}
		}
	}
}
package com.test;

public class Test {
	public static void main(String[] args) {
		MyThread mt = new MyThread();
		new Thread(mt, "售票员B").start();
		new Thread(mt, "售票员A").start();
		new Thread(mt, "售票员C").start();
	}
}

输出:
售票员B卖票,ticket = 6
售票员B卖票,ticket = 5
售票员B卖票,ticket = 4
售票员B卖票,ticket = 3
售票员B卖票,ticket = 2
售票员B卖票,ticket = 1

死锁

如果有多个进程, 且它们都要争用对多个锁的独占访问, 那么就有可能发生死锁。最常见的死锁形式是当线程 1 持有对象 A 上的锁, 而且正在等待对象 B 上的锁; 而线程 2 持有对象 B上的锁, 却正在等待对象 A 上的锁。 这两个线程永远都不会获得第二个锁或释放第一个锁, 所以它们只会永远等待下去。

线程交互

当线程调用 Object 类提供的 wait()方法时, 当前线程停止执行, 并释放其占有的资源, 线程从运行状态转换为等待状态。 当线程执行某个对象的 notify()方法时, 会唤醒在此对象等待池中的某个线程, 使该线程从等待状态转换为就绪状态;当线程执行某个对象的 notifyAll()方法时, 会唤醒对象等待池中的所有线程, 使这些线程从等待状态转换为就绪状态

package com.test;

public class ProducerConsumer {
	public static void main(String[] args) {
		Stack s = new Stack(); // 创建栈对象s
		Producer p = new Producer(s); // 创建生产者对象
		Consumer c = new Consumer(s); // 创建消费者对象
		new Thread(p).start(); // 创建生产者线程1
		new Thread(p).start(); // 创建生产者线程2
		new Thread(p).start(); // 创建生产者线程3
		new Thread(c).start(); // 创建消费者线程
	}
}

// Rabbit 类(产品:玩具兔)
class Rabbit {
	int id; // 玩具兔的id

	Rabbit(int id) {
		this.id = id;
	}

	public String toString() {
		return "玩具 : " + id; // 重写toString()方法,打印玩具兔的id
	}
}

// 栈(存放玩具兔的仓库)
class Stack {
	int index = 0;
	Rabbit[] rabbitArray = new Rabbit[6]; // 存放玩具兔的数组

	public synchronized void push(Rabbit wt) { // 玩具免放入数组栈的方法
		while (index == rabbitArray.length) {
			try {
				//释放同步资源,sleep不会,而且这个是等待状态而不是阻塞状态
				this.wait(); // 栈满,等待消费者消费
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notifyAll(); // 唤醒所有生产者进程
		rabbitArray[index] = wt; // 将玩具放入栈
		index++;
	}

	public synchronized Rabbit pop() { // 将玩具兔取走(消费)的方法
		while (index == 0) {
			try {
				this.wait(); // 等待生产玩具兔
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notifyAll(); // 栈不空,唤醒所有消费者线程
		index--; // 消费
		return rabbitArray[index];
	}
}

// 生产者类
class Producer implements Runnable {
	Stack st = null;

	Producer(Stack st) { // 构造方法,为类的成员变量ss 赋值
		this.st = st;
	}

	public void run() { // 线程体
		for (int i = 0; i < 20; i++) { // 循环生产20 个玩具兔
			try {
				Thread.sleep((int) (Math.random() * 200)); // 生产一个玩具兔后睡眠
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			Rabbit r = new Rabbit(i); // 创建玩具兔类
			st.push(r); // 将生产的玩具兔放入栈
			// 输出生产了玩具r,默认调用玩具兔类的toString()
			System.out.println("生产-" + r);
		}
	}
}

// 消费者类
class Consumer implements Runnable {
	Stack st = null;

	Consumer(Stack st) { // 构造方法,为类的成员变量ss 赋值
		this.st = st;
	}

	public void run() {
		for (int i = 0; i < 60; i++) { // 循环消费,即取走20 个玩具兔
			Rabbit r = st.pop(); // 从栈中取走一个玩具兔
			System.out.println("消费-" + r);
			try {
				Thread.sleep((int) (Math.random() * 1000)); // 消费一个玩具兔后睡眠
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

输出:
消费-玩具 : 0
生产-玩具 : 0
生产-玩具 : 0
生产-玩具 : 0
生产-玩具 : 1
生产-玩具 : 1…

注意:必须在同步环境中调用wait(),notify(),notifyAll()方法,线程拥有对象的锁才能调用对象等待或通知方法。

线程调度

线程的优先级:就绪队列中优先级高的线程先获得执行。Java 线程有 10 个优先级, 用数字 1~10 表示, 线程默认的优先级是 5 级。对线程可通过 setPriority(int) 方法设置优先级, 通过 getPriority()方法获知一个线程的优先级。

在 Thread 类中有一个名为 sleep(long millis)的静态方法, 此方法用于线程的休眠。由于sleep()方法会抛出一个InterruptedException,所以在程序中需要用try…catch捕获异常。

线程让步是指暂停当前正在执行的线程对象, 转而执行其他线程。 如果当前运行的线程优先级大于或等于线程池中其他线程的优先级, 当前线程能得到更多的执行时间。 如果某线程想让和它具有相同优先级的其他线程获得运行机会, 使用让步方法 yield()即可。 yield()方法只是令当前线程从运行状态转到可运行状态。

线程联合:Thread 的方法 join()让一个线程 B 与另一个线程 A 联合, 即加到 A 的尾部。 在 A 执行完毕之前 B 不能执行。 A 执行完毕, B 才能重新转为可运行状态。

package com.test;

public class Test {
	public static void main(String args[]) throws Exception {
		Thread sub = new Sub();
		sub.setName("子线程");
		System.out.println("主线程main 开始执行。");
		sub.start();
		System.out.println("主线程main 等待线程sub 执行……");
		sub.join();
		System.out.println("主线程main 结束执行。");
	}
}

class Sub extends Thread {
	public void run() {
		System.out.println(this.getName() + "开始执行。");
		System.out.println(this.getName() + "正在执行……");
		try {
			sleep(3000);
		} catch (InterruptedException e) {
			System.out.println("interrupted!");
		}
		System.out.println(this.getName() + "结束执行。");
	}
}

输出:
主线程main 开始执行。
主线程main 等待线程sub 执行……
子线程开始执行。
子线程正在执行……
子线程结束执行。
主线程main 结束执行。



这篇关于第十五节、轻松学Java_线程与并发的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程