前言
线程是Java基础中的重要一章,必须要熟练掌握基本概念和使用方法。
目录
1. 基本概念
1.1 线程
操作系统中对线程的定义是程序执行的最小单元;实际上我们可以理解为一个进程里面的不同的执行路径。
1.2 进程
进程是一个静态的概念,可以理解为一个可执行的java文件。
2. 创建和启动
线程的创建和启动有两种方式:
(1)继承Thread类
public class ThreadStart {
public static void main(String[] args) {
Thread thread = new Thread1();
thread.start();
}
}
class Thread1 extends Thread {
@Override
public void run() {
}
}
(2)实现Runnable接口
public class TestThread1 {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable1());
thread1.start();
for (int i = 0; i < 100; i ++ ) {
System.out.println("main: " + i);
}
}
}
class Runnable1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i ++ ) {
System.out.println("thread: " + i);
}
}
}
(3)两种方法的对比
实现Runnable的接口要比继承Thread类要好,因为继承只能有一个父类,比较单一且不好控制。
3. 调度和优先级
线程的优先级基础的分为1-10级,默认值为5,优先级越高,获得的CPU的执行时间片越长:
Thread thread1 = new Thread(new Runnable1());
thread1.setPriority(Thread.MAX_PRIORITY);
thread1.start();
4. 状态控制
4.1 线程状态
4.2 执行方法
(1)sleep()
该方法是Thread类的静态方法,使线程处于睡眠等待的状态,让出cpu的执行权。
(2)interrupt()
需要捕获InterruptException的异常,当调用线程的该方法时,停止线程的执行,但是会先执行完InterruptException的异常处理之后,再结束线程。
(3)stop()
停止线程,该方法已经废弃,一旦调用会立即结束线程。
(4)join()
合并两个线程的执行。当在线程A中执行调用线程B的join方法时,线程B合并到A中执行,B结束后再接着执行线程A。
(5)yield()
当调用线程的该方法时,该线程立刻释放CPU的占用,重新进入就绪等待的状态。
(6)isAlive()
判断当前线程是否还存活:
Thread.currentThread().isAlive();
4.3 结束线程的方法
对比:
a. stop方法比interrupt粗暴,直接结束线程;
b. 结束一个线程的最好方法是,结束run()方法的执行。run()方法结束,那么线程也就结束了。
5. 线程同步
线程同步可以直接理解为资源独占,也就是同一时期的某个资源只能被一个线程独占。
5.1 互斥锁
资源独占可以用synchronized关键字进行修饰。
synchronized:
a. 修饰方法:当synchronized修饰一个方法时,该线程拥有该对象的锁(Object本身就是一个对象锁),则其他线程会阻塞 无法访问该方法,直到独占的线程释放该对象锁。
这种情况下,其他线程可以访问该对象的其他方法。
public synchronized void printDataA() {
try {
System.out.println("Current: " + new Date() + " === " + Thread.currentThread().getName());
Thread.sleep(5000);
flag++;
System.out.println("Current: " + new Date() + " === " + Thread.currentThread().getName());
} catch (InterruptedException e) {
}
System.out.println("Thread : " + Thread.currentThread().getName() + " , flag = " + flag);
}
b. 修饰代码块:使用关键字时需要指明具体的锁
public void printDataB() {
flag++;
System.out.println("Current: " + new Date() + " === " + Thread.currentThread().getName());
synchronized (this) {
try {
flag++;
System.out.println("Thread : " + Thread.currentThread().getName() + " , flag = " + flag);
//System.out.println("Current: " + new Date() + " === " + Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println("Current: " + new Date() + " === " + Thread.currentThread().getName());
} catch (InterruptedException e) {
}
}
}
5.2 死锁
死锁是指两个线程或多个线程之间相互等待需要的资源对象的现象,由于线程之间的相互等待,导致线程永远处于阻塞状态无法继续执行。经典的问题如哲学家吃饭。
针对死锁的问题,可以通过添加同步锁的方式(资源独占)进行解决。但是如果要保护好被访问的对象,需要仔细考虑所有操作该对象的方法是否被加了同步锁。
5.3 生产者/消费者 问题
生产者/消费者模型,是典型的容易发生死锁现象的问题。
5.3.1 wait和notify
该模型的具体代码见下面,这里使用到了线程控制的另一种方法wait(),该方法是Object对象的方法,与其他线程控制的方法不同。
wait():当调用某个对象的wait()方法时,说明锁定在该对象上的线程需要等待,让出cpu执行权。
当调用wait方法时,该线程处于睡眠状态,那么如何唤醒该线程呢?
notify()/notifyAll():叫醒wait在当前对象上的线程。如果某个线程处于wait状态时,Object不发通知也会产生死锁的现象。
5.3.2 wait和sleep的区别
wait属于Object的方法,sleep是Thread类的方法;
wait时会释放线程所占用的锁,sleep不会释放线程占用的锁;
5.3.3 生产者/消费者模型:
public class ProducerCunsumer {
public static void main(String[] args) {
Basket basket = new Basket();
new Thread(new Producer(basket)).start();
new Thread(new Consumer(basket)).start();
}
}
class Wotou {
public Wotou(){}
}
class Basket{
int index = 0;
int length = 5;
Wotou[] array = new Wotou[length];
public synchronized void push(Wotou m) {
while (index == length) {
try{
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
notify();
System.out.println("set wotou: " + index);
array[index] = m;
index++;
}
public Wotou pop() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(this) {
while (index == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
notify();
index--;
Wotou result = array[index];
System.out.println("get wotou: " + index);
return result;
}
}
}
class Producer implements Runnable {
Basket basket;
Producer(Basket basket) {
this.basket = basket;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
basket.push(new Wotou());
}
}
}
class Consumer implements Runnable {
Basket basket;
Consumer(Basket basket) {
this.basket = basket;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
basket.pop();
}
}
}
6. 总结
线程的基本概念和使用方法基本上已经介绍清楚,相关的测试代码会上传到github上。