什么是线程?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。是CPU调度和分派的基本单位,本身不拥有系统资源,但可以访问进程的系统资源。
老生常谈的一个问题:线程和进程之间的关系?
(1)一个线程只属于一个进程,一个进程可以有多个线程,至少有一个线程(一般称为主线程)。
(2)资源分配给进程,同一个进程的所有线程共享该进程的资源。
(3)线程在执行过程中,协作同步。
(4)CPU分配给线程。真正在CPU上运行的是线程。
(5)线程是进程的可执行单元,是进程的可调度实体。
创建线程的方式(Java):
(1)继承Thread
举例:
public class Test extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
public class TestThread {
public static void main(String[] args) {
Test test = new Test();
test.start();
}
}
(2)实现Runnable接口
举例:
public class Test implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
public class TestThread {
public static void main(String[] args) {
Test test = new Test();
Thread thread = new Thread(test);
thread.start();
}
}
两种方式比较:第一种是单继承,该类继承了Thread继承了Thread后,后续无法再继承其他类,只能实现接口,第二种是实现接口,不影响后续的继承与实现。
线程可能会发生的问题:
比如:A和B同时从银行取钱,总额为5000,A取3000,B也取3000,由于线程同步,两者都能取到钱,最后剩余钱为-1000。这显然是不正确的。
这种时候,需要加锁。
举例:吃蛋糕
public class Cake {
// 蛋糕的编号
private String name;
public Cake(String name) {
this.name = name;
}
@Override
public String toString() {
return "Cake{" +
"name='" + name + '\'' +
'}';
}
}
public class Panzi {
// 只在头部移除removeFirst,只在尾部添加addLast
private LinkedList<Cake> list = new LinkedList<>();
public synchronized void putCake(Cake cake) {// 加锁
list.addLast(cake);
notifyAll();// 唤醒全部线程
System.out.println("生产者线程 putCake notifyAll");
}
public synchronized Cake getCake() {// 加锁
if (list.size() <= 0) { // 盘子里面没有蛋糕,所以要wait等待
try {
System.out.println("消费者线程 getCake wait");
wait();// 是java.lang.Object里面的方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return list.removeFirst();
}
}
public class ProducerThread extends Thread{
private Panzi panzi;
public ProducerThread(String name, Panzi panzi) {
super(name);// 给当前线程命名
this.panzi = panzi;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
Cake cake = new Cake("no:" + i);
System.out.println(Thread.currentThread().getName() + " putCake: " + cake);
// 生产一个蛋糕就放到盘子里面
panzi.putCake(cake);
// 1000毫秒就是1秒
// 生成随机等待的时间,模拟生成蛋糕的过程
try {
Thread.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ConsumerThread extends Thread{
private Panzi panzi;
public ConsumerThread(String name, Panzi panzi) {
super(name);
this.panzi = panzi;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
Cake cake = panzi.getCake();
System.out.println(Thread.currentThread().getName() + " getCake : " + cake);
// 生成随机等待的时间,模拟吃蛋糕的过程
try {
Thread.sleep(new Random().nextInt(5000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {// 主函数
public static void main(String[] args) {
// 生产者消费者操作的是同一个盘子
Panzi panzi = new Panzi();
// 启动生产者线程去生产蛋糕
ProducerThread producerThread = new ProducerThread("生产者线程",panzi);
producerThread.start();
// 启动消费者线程去吃蛋糕
ConsumerThread consumerThread = new ConsumerThread("消费者",panzi);
consumerThread.start();
}
}
线程的基本状态:
首先,如图:
注:最上面两个锁池和等待队列也可以归为阻塞状态中。
1)新建状态:创建了一个线程对象
2)可运行状态(就绪状态):线程对象创建后,该对象的start方法已经被其他线程调用了,所以只能在可运行线程池(等待队列)中等待被调度,获得CPU的使用权;原本是运行状态,但是时间片用完了;在运行状态中调用了yield方法,转为可运行状态;原本处于阻塞状态,解决了阻塞的原因,转为可运行状态。
3)运行状态:原本在可运行状态,获得了CPU的时间片,执行程序
4)阻塞状态:由于某种原因,让出了CPU的时间片,暂时停止运行。
5)死亡状态:线程的run或main方法结束,或者出现异常退出。