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