目录
1. 基本知识概述
2. 多线程概述
2.1 优点
2.2 使用场景
3. 创建线程
3.1 继承 Thread 类
3.2 实现 Runnable 接口
3.3 比较
3.4 创建 Callable 接口
3.5 使用线程池
4. Thread 类常用方法
5. 线程生命周期
6. 线程安全机制
6.1 同步代码块
6.2 同步方法
6.3 Lock 锁
7. 死锁
1. 基本知识概述
程序:一段静态的代码;
进程:正在运行的一个程序,或程序的一次运行过程。
是一个动态的过程:有产生,存在,消亡的过程,即生命周期;
线程:一个程序内部的一条执行路径;
单核 cpu :在一个时间单元内,只能执行一个线程的任务;
如高速公路的收费站只有一个工作人员,只能一个一个的收费,没有收费的需要排队等待,但 cpu 时间单元很短,我们感觉不出来;
多核 cpu:相当于老板请了工人,现在的服务器都是多核的;
并行:多个 cpu 同时执行多个任务,如:多人同时做不同的事情;
并发:一个 cpu 同时执行多个任务,如:秒杀:多人同时做一件事情;
2. 多线程概述
2.1 优点
提高应用程序的响应;
提高计算机系统 cpu 的利用率;
改善程序结构。将长而复杂的进程分为多个线程,,独立运行,利于理解和修改;
2.2 使用场景
程序需要同时执行两个或多个任务时;
程序需要实现一些需要等待的任务时;
需要一些后台运行的程序时;
3. 创建线程
3.1 继承 Thread 类
1.创建一个继承于 Thread 类的子类
2.重写 Thread 类的 run() ---> 将此线程执行的操作声明在 run() 中
3.创建 Thread 类子类的对象
4.通过此对象调用 start()
①启动当前线程
②调用当前线程的 run()
思考一:调用 run()方法行不行?调用后是单线程
思考二:再启动一个线程 * 需要重新创建一个对象
/***多线程的创建:* 方式一:继承于 Thread 类* 1.创建一个继承于 Thread 类的子类* 2.重写 Thread 类的 run() ---> 将此线程执行的操作声明在 run() 中* 3.创建 Thread 类子类的对象* 4.通过此对象调用 start()*//*
主线程创建Thread()子类,并创建对象mythread;
mythread 线程调用 start() 方法,
所以共有两个线程;
*///1.创建一个继承于 Thread 类的子类
public class Mythread extends Thread {public static void main(String[] args) {
//3.创建 Thread 类子类的对象Mythread mythread = new Mythread();
//4.通过此对象调用 start()mythread.start();System.out.println("=====================");for (int i = 0; i < 11; i++) {if (i % 2 == 0){System.out.print(i + " ");}}}
//2.重写 Thread 类的 run()@Overridepublic void run() {//将此线程执行的操作声明在 run() 中for (int i = 0; i < 101; i++) {if (i % 2 == 0){System.out.println(i);}}}
}
3.2 实现 Runnable 接口
1.创建一个实现了 Runnable 接口的类
2.实现类实现 Runnable 中的抽象方法:run ( )
3.创建实现类的对象
4.将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象
5.通过 Thread 类的对象调用 start()
public class MyThread2 {public static void main(String[] args) {// 3.创建实现类的对象Mythread02 mythread02 = new Mythread02();// 4.将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象Thread thread = new Thread(mythread02);// 5.通过 Thread 类的对象调用 start()thread.start();//Thread 类的对象调用 start() 才会启动分支线程
// start() 作用:①:启动线程 ②:调用当前线程的 run()//再创建一个线程Thread thread2 = new Thread(mythread02);thread2.start();}
}//多线程的创建方式二// 1.创建了一个实现了 Runnable 接口的类
class Mythread02 implements Runnable{// 2.实现类实现 Runnable 中的抽象方法:run()@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + " " + i);}}
}
3.3 比较
优先选择实现 Runnable 接口的方式
1.实现的方式相比类的单继承的局限性要好
2.实现的方式更适合处理多个线程有共享数据的情况
相同点:
两种方式都要重写 run(),且线程执行的操作写在其方法体中;
案例比较:三个窗口买票:
继承 Thread 类的方式:
public class BuyTickets {public static void main(String[] args) {
// 有三个窗口Test test1 = new Test();Test test2 = new Test();Test test3 = new Test();test1.start();test2.start();test3.start();}
}class Test extends Thread{private static int ticket = 100;//static:每个线程共享同一静态变量@Overridepublic void run(){while (true){if(ticket >= 0) {System.out.println(getName() + "票号:" + ticket);ticket --;}else break;}}
}
使用实现 Runnable 接口的方式
//解决三个窗口卖票:使用实现 Runnable 接口的方式
class TicketTest{public static void main(String[] args) {SaleTicket saleTicket = new SaleTicket();
// 相当于三个线程共用一个对象 saleTicketThread thread1 = new Thread(saleTicket);Thread thread2 = new Thread(saleTicket);Thread thread3 = new Thread(saleTicket);thread1.start();thread2.start();thread3.start();}
}class SaleTicket implements Runnable{private int ticket = 100;@Overridepublic void run() {while (true){if(ticket > 0)System.out.println(Thread.currentThread().getName() + "票号:" + ticket);else break;ticket --;}}
}
3.4 创建 Callable 接口
与实现 Runnable 接口相比,Callable 功能更强大些
如何理解:
1.call() 有返回值;
2.call() 可以抛出异常,被外面打操作捕获,获取异常信息;
3.Callable 支持泛型
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** 与实现 Runnable 接口相比,Callable 功能更强大些* 如何理解:* 1.call() 有返回值;* 2.call() 可以抛出异常,被外面打操作捕获,获取异常信息;* 3.Callable 支持泛型** 创建线程的方式三:实现 Callable 接口 ---JDK 5.0 新增** 步骤:①创建一个实现 Runnable 接口的实现类* ②实现 call() 方法,将需要执行的操作声明在该方法中* ③创建 Callable 实现类的对象* ④将此Callable实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象* ⑤将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()* ⑥获取Callable中call方法的返回值*/public class CreateCallum {public static void main(String[] args) {// ③创建 Callable 实现类的对象CreateThread createThread = new CreateThread();//④将此Callable实现类的对象作为参数传递到FutureTask构造器中// ,创建FutureTask的对象FutureTask futureTask = new FutureTask(createThread);//⑤将FutureTask对象作为参数传递到Thread类的构造器中,// 创建Thread对象,并调用start()new Thread(futureTask).start();try {//⑥获取Callable中call方法的返回值//get()返回值即为futureTask构造器参数Callable实现类重写的call()的返回值Object sum = futureTask.get();System.out.println(sum);} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}}
}//①创建一个实现 Runnable 接口的实现类
class CreateThread implements Callable {//②实现 call() 方法,将需要执行的操作声明在该方法中@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 1; i <= 100; i++) {if(i % 2 == 0){System.out.println(i);sum += i;}}return sum;}
}
3.5 使用线程池
创建线程的方式四:使用线程池
好处:
1.提高响应速度(减少创新新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
初学者简单理解一下线程池的基本概念即可;
4. Thread 类常用方法
/***测试 Thread 类中常用方法:* 1.start():①启动当前线程;②调用当前线程的 run()* 2.run():(需要重写 Thread类中 run()),将创建线程执行的操作声明在此方法中* 3.currentThread():静态方法,Thread类直接调用。返回执行当前代码的线程* 4.getName():获取当前线程的名字* 5.setName():设置当前线程的名字* 6.yield():释放当前 cpu 的执行权,暂停当前执行的线程,把执行机会让给优先级相同或更高的线程。类似“礼让”* 7.join():在线程 a 中调用线程 b 的 join(),此时线程 a 就进入阻塞状态,直到线程 b* 完全执行完以后,线程 a 才结束阻塞。类似插队,加塞* 8.stop():已过时。强制生命周期结束,不推荐使用* 9.sleep(long millitime):休眠,单位:毫秒* 10.isAlive():判断当前线程是否存活*** 1.线程优先级:* MAX_PRIORITY:10* NORM_PRIORITY:5 默认优先级* MIN_PRIORITY:1* 2.如何获取与设置当前线程的优先级:* getPriority():获取线程的优先级* setPriority():设置线程的优先级* 说明:高优先级的线程要抢占低优先级 cpu 的执行权,* 但只是从概率上讲,高优先级的线程大概率会被执行;*/public class ThreadMethodTest {public static void main(String[] args) {ThreadMethod threadMethod = new ThreadMethod();threadMethod.setName("分线程");//返回线程名字之前修改名称threadMethod.setPriority(Thread.MAX_PRIORITY);//执行分线程前设置优先级threadMethod.start();
// 以上代码(12-14行)均是主线程执行的,只有 start()执行后才是分线程//给主线程命名Thread.currentThread().setName("主线程");Thread.currentThread().setPriority(Thread.MIN_PRIORITY);for (int i = 1; i < 6; i++) {
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }System.out.println(ThreadMethod.currentThread().getName()+":"+i);if(i == 3){try {threadMethod.join();//执行分线程} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
}class ThreadMethod extends Thread{@Overridepublic void run(){isAlive();for (int i = 0; i < 10; i++) {System.out.print(currentThread()+" ");//在 Thread 类里面,“前缀”可以省略System.out.print(getName()+" ");//在 Thread 类里面,“前缀”可以省略System.out.println(i+" ");if( i % 2 == 0) this.yield();}}
}
5. 线程生命周期
6. 线程安全机制
解决线程安全的方式有两种:同步代码块,同步方法
6.1 同步代码块
实现 Runnable 接口创建线程的解决线程安全方式;
/*** 举例:卖票过程中,创建三个窗口卖票,总票数为 100,使用实现 Runnable 接口的方式使用线程* 1.问题:卖票过程中出现重票,错票 ——> 线程的安全问题* 2.原因:当某个线程操作车票的过程中,操作尚未完成时,其他线程也参与进来* 3.解决方法:当一个线程 A 在操作 ticket 的时候,其他线程不能参与进来,* 直到线程 A 操作完 ticket 时,其他线程才可以开始操作 ticket;* 即使线程 A 出现了阻塞,也不能被改变;** 有共享数据,线程才会存在安全问题;* (实现类中天然是一个共享的数据,因为只造了一个对象)* (继承的方式中早了多个对象,类有时需要 static 修饰)** 在 Java 中,通过同步机制,解决线程的安全问题。** 方法一:同步代码块:* synchronized(同步监视器){* //需要被同步的代码* }* 说明:1.操作共享数据的代码,即为需要被同步的代码;* 共享数据:多个线程共同操作的变量,比如:ticket 就是共享数据* 2.同步监视器:俗称,锁* 任何一个类的对象,都可以充当锁* 注意创建对象的位置* 3.多个线程必须要公用同一把锁** 4.同步的方式,解决了线程的安全问题;* 操作同步代码时,只能有一个线程参与,其他线程等待,相当于是* 一个单线程的过程,效率低;**/public class SafeThread {public static void main(String[] args) {Window window = new Window();Thread thread1 = new Thread(window);Thread thread2 = new Thread(window);Thread thread3 = new Thread(window);thread1.start();thread2.start();thread3.start();}
}
class Window implements Runnable{private int ticket = 100;
// Object object = new Object();@Overridepublic void run() {while (true){//操作共享数据的代码,即为需要被同步的代码;//this指当前对象,调用当前方法的就是当前对象 windowsynchronized (this){//任何一个类的对象,都可以充当锁if(ticket > 0){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "票号" + ticket);}else break;ticket --;}}}
}
继承 Thread 类创建线程解决线程安全的方式;
//使用同步代码块解决继承 Thread 类的线程安全问题;//在继承 Thread 类创建多线程的方式中,慎用 this 充当同步监视器,一般使用当前类class Test2{public static void main(String[] args) {Window2 window21 = new Window2();Window2 window22 = new Window2();Window2 window23 = new Window2();window21.start();window22.start();window23.start();}
}
class Window2 extends Thread{private int ticket = 100;static Object object = new Object();//需要 static 修饰@Overridepublic void run(){while (true){//不可以用this,当前对象有window21/22/23三个对象,对象不唯一,锁不唯一synchronized(object) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "票号:" + ticket);ticket --;}else break;}}}}
6.2 同步方法
实现 Runnable 接口创建线程的解决线程安全方式;
public class SafeThread2 {public static void main(String[] args) {Window1 window1 = new Window1();Thread thread1 = new Thread(window1);Thread thread2 = new Thread(window1);Thread thread3 = new Thread(window1);thread1.start();thread2.start();thread3.start();}
}class Window1 implements Runnable{private int ticket = 100;static Object object = new Object();@Overridepublic synchronized void run() {while (true){show();}}private synchronized void show(){//非静态同步方法if(ticket > 0){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName()+"票号:"+ticket);ticket --;}}
}
继承 Thread 类创建线程的解决线程安全的方式;
/*** 使用同步代码块解决 继承 Thread 类的线程安全问题:*/
class SafeWindow1{public static void main(String[] args) {Test02 t1 = new Test02();Test02 t2 = new Test02();Test02 t3 = new Test02();t1.start();t2.start();t3.start();}
}class Test02<object> extends Thread{private static int ticket = 100;@Overridepublic void run(){while (true){Show();}}private static synchronized void Show( ){//静态同步方法try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}if(ticket > 0){System.out.println(Thread.currentThread().getName()+"票号:"+ticket);ticket --;}}}
6.3 Lock 锁
Lock(手动控制)> 同步代码块(已进入方法体,分配资源)> 同步方法(方法体制之外)
synchronized 与 lock 的异同
相同:二者都可以解决线程安全问题
不同:synchronized 机制在执行完相应的同步代码以后,自动释放同步监视器
Lock 需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock)
import java.util.concurrent.locks.ReentrantLock;/*** Lock 锁,JDK5.0新增,解决线程安全问题 ----JDK5.0新增* 1.面试题: synchronized 与 lock 的异同* 相同:二者都可以解决线程安全问题* 不同:synchronized 机制在执行完相应的同步代码以后,自动释放同步监视器* Lock 需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())** 2.优先使用顺序:* Lock(手动控制)>同步代码块(已进入方法体,分配资源)> 同步方法(方法体制之外)* 3. 如何解决线程安全问题?**/public class Lock {public static void main(String[] args) {Window01 window01 = new Window01();Thread thread1 = new Thread(window01);Thread thread2 = new Thread(window01);Thread thread3 = new Thread(window01);thread1.start();thread2.start();thread3.start();}
}class Window01 implements Runnable{private int ticket = 100;//1. 实例化 ReentrantLockprivate ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true){try{//没有异常不用抛//2.调用 lock()lock.lock();if(ticket > 0){try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "票号:" + ticket);ticket --;}else break;}finally{//3.调用解锁方法:unlock()lock.unlock();}}}
}
7. 死锁
死锁:
1.理解: 不同的线程分别占用对方需要的同步资源不“松手”
都在等待对方放弃自己需要的同步资源,造成一种 * 程序相持的局面;
2.说明:
(1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态; * (2)使用同步时,要避免出现死锁
3.解决办法:
(1)专门的算法、原则
(2)尽量减少同步资源的定义
(3)尽量避免嵌套同步