目录
一、线程入门
1.进程:每一个软件都是一个进程。
2.线程:进程是由多个线程组成的。
3.进程和线程的关系:一个进程是对应一个或者是多个线程的。
4.我们所有的操作都是由CPU来执行的,但是CPU一次只能处理一个操作,所以我们在打开了多个程序后,只是看似是一起运行的,实际上还是在单个运行:
(1)那么多个程序为什么可以看似是一起运行的,这是因为CUP把执行的时间分成了时间片段;
(2)比如:把1秒分成了多个时间片段,其中一部分时间片段是执行一个程序,而另一部分时间片段又去执行另一个程序,因为时间片段很快,我们是很难用肉眼去分辨的,所以在1秒内,多个程序是可以看作是一起运行的;
- 秒后面的单位:微秒 -> 纳秒
5.线程类:Thread类
(1)创建线程:new一个线程类,但是一般情况下,我们是不会这么写的,我们会通过匿名内部类的方式来写;
java
Thread t = new Thread(); // 创建线程 -- 初始化
(2)使用匿名内部类的方法来创建线程;
java
// 创建线程
Thread t = new Thread(){//运行状态 -->> 抢到了CPU资源
// 重写run方法
public void run(){
}
};
(3)子线程;
- 在一个线程中创建了另一个线程,那么被创建出来的那个线程就是那个已有线程的子线程;
(4)线程类中的常用方法;
- 获取当前线程的名字:Thread.currentThread().getName();
java
System.out.println(Thread.currentThread().getName());
-
启动线程:线程类的对象名.start();
- 注意线程只有在启动后才会执行里面的代码;
java// 就绪状态 -->> 可以开始去抢占CPU的资源了 t.start(); // 启动线程
(5)设置线程的优先级:线程类的对象名.setPriority(Thread的静态常量);
java
t.setPriority(Thread.MAX_PRIORITY);
-
Thread.MAX_PRIORITY --> 10;
-
Thread.MIN_PRIORITY --> 1;
-
Thread.NORM_PRIORITY --> 5;
-
设置线程为精灵线程,也可以称为守护线程或者后台线程:线程类的对象名.setDaemon(true);
- 作用:如果将一个子线程设置为精灵线程,那么这个子线程会在主线程停止执行后一起停止,就算是子线程还没有执行完也会被强制停止;
javat.setDaemon(true);
6.线程的状态:
(1)初始化状态:创建线程对象;
java
// 创建一个线程类 -- 初始化
Thread t = new Thread();
(2)就绪状态:调用start()方法;
- 就绪状态代表线程可以开始去抢占CPU资源了;
java
// 启动线程 -- 就绪状态
t.start();
(3)运行状态:执行run()方法;
- 代表是抢到了CPU资源,开始执行run()方法;
java
// 创建线程
Thread t = new Thread(){ // 运行状态 -->> 抢到了CPU资源
// 执行run方法
public void run(){
}
};
(4)死亡状态:run()方法运行结束;
(5)阻塞状态:调用sleep()方法的时候;
- sleep()方法的括号中的参数是以毫秒为单位的;
java
Thread.sleep(1);
7.实现线程有三种方法:
(1)实现接口:要将一个普通类变成线程类需要实现Runnable接口;
- 使用实现接口的方法,那么这个普通类就变成了一个可以被线程类操作的类,然后使用线程类去完成一些操作;
- 在实现接口后,这个普通类只是可以被线程类操作了,并没有变成一个线程类;
java
public class Thread2 implements Runnable {
@Override
public void run() {
}
}
(2)继承类:要将一个普通类变成线程类需要继承Thread类并重写run()方法;
- 使用继承类的方法,那么将一个普通类变成线程类之后,这个普通类就是一个线程类,它和线程类的用法没有区别;
java
public class Thread1 extends Thread {
@Override
public void run() {
}
}
(3)线程池:线程池用来管理线程,下面会介绍;
8.实现线程的方法中,实现接口和继承类这个两个方法推荐使用实现接口的方法,因为java只能单继承,但是可以多实现。
二、线程同步
1.同步代码块:
(1)格式:synchronized(锁旗标){};
(2)含义:在同步块中的代码,同时只能有一个线程去执行;
java
public class Ticket implements Runnable {
int number = 100;
@Override
public void run() {
while (true) {
// 同步代码块
synchronized ("") {
if (number > 0) {// 还有票
try {
Thread.sleep(50);// 模拟卖票时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("同步代码块:" + Thread.currentThread().getName() + "卖了" + number-- + "张票");
}
}
}
}
}
(3)注意:synchronized括号中可以放字符串或对象,只有括号中是同一个字符串或同一个对象时才可以达到同步的效果,一般括号中放的是空字符串或者this对象;
2.同步方法:
(1)同步方法锁住的是当前对象;
java
public class Ticket implements Runnable {
int number = 100;
@Override
public void run() {
while(true){
sale();
}
}
// 卖票的方法
// 同步方法 -->> 锁住的是当前对象
public synchronized void sale(){//this
if (number > 0) {// 还有票
try {
Thread.sleep(50);// 模拟卖票时间
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("同步方法:" + Thread.currentThread().getName() + "卖了" + number-- + "张票");
}
}
}
3.总结:
(1)每个对象身上都有一把锁,它的状态不是1就是0;
(2)我们锁住的东西称为锁旗标,如:this,空字符串都可以称为锁旗标;
(3)想要线程同步,只需要让多个线程拥有同一个锁旗标;
三、死锁
1.互相拿着对方的锁,但是需要对方来解锁;
java
public class Ticket implements Runnable {
int number = 100;
String s = "a";
@Override
public void run() {
while (true) {
if ("a".equals(s)) {
while (true) {
sale();
}
} else {
while (true) {
synchronized ("") {
if (number > 0) {// 还有票
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("同步代码块:" + Thread.currentThread().getName() + "卖了" + number-- + "张票");
// 会造成死锁
synchronized (this){
}
}
}
}
}
}
}
//卖票的方法
//同步方法 -->> 锁住的是当前对象
public synchronized void sale(){//this
if (number > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("同步方法:" + Thread.currentThread().getName() + "卖了" + number-- + "张票");
//会造成死锁
synchronized (""){
}
}
}
}
public class Demo {
public static void main(String[] args) throws InterruptedException {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
Thread t4 = new Thread(ticket);
t1.start();
t2.start();
Thread.sleep(1);
t.s = "b";
t3.start();
t4.start();
}
}
四、线程的方法
1.挂起:线程对象名.suspend();
(1)此方法已过时,作为了解。
java
Thread t1 = new Thread();
t1.suspend();
2.取消挂起:线程对象名.resume();
(1)此方法已过时,作为了解。
java
Thread t1 = new Thread();
t1.resume();
3.礼让:线程对象名.yield();
(1)线程在抢到CPU的资源后,会让出来,但是在让出来后,还会和其他线程去抢CPU的资源(只让一次)。
java
Thread t1 = new Thread();
t1.yield();
4.加入:线程对象名.join();
(1)在一个线程执行的途中,可以加入另一个正在执行的线程,然后正在执行的线程就会停下来让新加入的线程先执行完,等到新加入的线程执行完毕后,原来的线程才会继续执行。
java
public class ThreadB extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===" + i);
}
}
public static void main(String[] args) {
ThreadB b = new ThreadB();
ThreadA a = new ThreadA(b);
a.start();
b.start();
}
}
public class ThreadA extends Thread {
private ThreadB b;
public ThreadA(ThreadB b){
this.b = b;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i == 10){
try {
//当A线程执行到10的时候,将B线程加入进来
b.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "===" + i);
}
}
}
5.休息/等待:线程对象名.wait()或者this.wait();
(1)让一个正在执行的线程停止执行,进入休息状态。
6.唤醒当前线程:线程对象名.notify()或者this.notify();
(1)如果当前线程是休息状态,则唤醒当前线程。
7.唤醒所有线程:线程对象名.notifyAll()或者this.notifyAll();
(1)唤醒全部已经进入休息状态的线程。
8.使用生产者和消费者案例来解释wait和notifyAll方法,notify方法和notifyAll方法使用方法差不多,一个是唤醒当前线程,一个是唤醒全部线程;
java
/**
* 产品
*/
public class Product {
int number = 0;
int max = 100;
public synchronized void add(){
if(number < max){
number++;
System.out.println("生产了一件产品,现在的数量是:" + number);
//当生产了一件产品的时候,唤醒全部线程开始消费
this.notifyAll();
}else {
try {
//当现有的产品数量大于等于最大的库存容量时,休息一下
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void remove(){
if(number > 0){
number--;
System.out.println("消费了一件产品,现在的数量是:" + number);
//当消费了一件产品的时候,唤醒全部线程开始生产
this.notifyAll();
}else {
try {
//当没有产品时,先休息一下
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 生产者
*/
public class Produce extends Thread {
Product pro = null;
public Produce(Product pro){
this.pro = pro;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
pro.add();
}
}
}
/**
* 消费者
*/
public class Saler extends Thread {
Product pro = null;
public Saler(Product pro){
this.pro = pro;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
pro.remove();
}
}
}
public class Test {
public static void main(String[] args) {
Product pro = new Product();
new Produce(pro).start();
new Produce(pro).start();
new Saler(pro).start();
}
}
五、线程的流程图
六、线程池
1.线程池的概念:
(1)将多个线程放入到一个类似池子的空间中,然后如果要使用某个线程,就从线程池中取出使用,使用完成后再将线程放入到线程池中。
2.Java通过Executors提供了四种线程池:
(1)newCacheThreadPool:创建一个可缓存的线程池,如果线程池长度超过处理需要,可以灵活的回收空闲线程,若没有可回收的线程,则会新创建一个;
- 可缓存的线程池为无限大,当执行第二个任务时第一个任务已经完成,第二个任务会复用第一个任务的线程,而不用每次都新创建一个线程;
- 使用execute方法来执行线程;
java
// 可缓存的线程池,线程池为无限大,当执行第二个任务时如果第一个任务已经完成,会复用第一个任务的线程,而不用每次新建线程
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(1000 * index);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
(2)newFixedThreadPool:创建一个固定长度的线程池,可控制线程的最大并发数,如果超出,线程会在队列中等待;
- 创建一个固定长度的线程池,这个线程池的长度是固定的,每次执行线程的时候,会按最大长度去限制执要行线程的个数;
- 如果最大长度为3,则一次性最多只能执行3个线程,如果为5,则一次性最多只能执行5个线程;
- 使用execute方法来执行线程;
java
// 创建固定长度的线程池,因为线程池的大小为3,每个任务休息3秒之后,会打印三个数字,所以是每三秒打印三个数字
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(index);
}
});
}
(3)newScheduledThreadPool:创建一个定长的线程池,支持定时及周期任务;
- 使用schedule方法来定时执行线程,就是让一个线程在多长时间后执行;
java
// 创建一个定长的线程池,支持定时及周期任务执行
// 定时:表示延迟3秒后执行一次
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
},3, TimeUnit.SECONDS);
- 使用scheduleAtFixedRate方法来定时及定周期执行线程,就是让一个线程,在多长时间后执行,每多长时间执行一次;
java
// 创建一个定长的线程池,支持定时及周期任务执行
// 延迟1秒之后,每隔3秒执行一次
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
scheduledThreadPool.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
System.out.println("delay 1 seconds, and execute every 3 seconds");
}
},1,3,TimeUnit.SECONDS);
(4)newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,可以保证所有的任务按照指定的顺序来执行;
- 创建一个单线程化的线程池,也就是说这个线程池只会创建一个线程,然后使用这个线程去执行各个任务,相当于顺序执行各个任务;
- 使用execute方法来执行线程;
java
// 创建一个单一的线程池,结果按照执行顺序依次输出,相当于顺序执行各个任务
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}