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