多线程
程序(**program**):为了完成特定任务、用某种语言编写的一组指令集合。指一段静态的代码,静态队形
进程 (process):程序的一次执行过程,或是正在运行的一个程序。 是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期。
线程 (thread):进程可进一步细化为线程,是一个程序内部的一条执 行路径。 ·
-
并行:多个 CPU 同时执行多个任务。比如:多个人同时做不同的事。
-
并发:一个 CPU(采用时间片)同时执行多个任务。比如:秒杀、多 个人做同一件事
优点
-
提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
-
提高计算机系统 CPU 的利用率。
-
改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利 于理解和修改
-
Thread 类的特性:
每个线程都是通过某个特定 Thread 对象的 run() 方法来完成操作的, 经常把 run() 方法的主体称为线程体。 通过该 Thread 对象的 start() 方法来启动这个线程,而非直接调用 run()
常用方法:
-
Thread():创建新的 Thread 对象。
-
Thread(String threadname):创建线程并指定线程实例名。 Thread(Runnabletarget):指定创建线程的目标对象,它实现了 Runnable 接口中的 run 方法。
-
Thread(Runnable target, String name):创建新的 Thread 对象。
线程创建的两种方式
继承 Thread 类的方式。
实现 Runnable 接口的方式。
-
继承Thread类的方式
-
. 创建一个继承于 Thread 类的子类;
-
重写 Thread 类的 run() 方法 ---> 将此线程的方法声明在 run() 中;
-
创建 Thread 类的子对象;
-
通过此对象调用 start()。
-
// 例子 : 遍历 100 以内的所有的偶数 // 1. 创建一个继承于 Thread 类的子类 class MyThread extends Thread{ // 2. 重写 Thread 类的 run() @Override public void run() { for(int i = 1;i < 100;i++){ if(i % 2 == 0){ System.out.println(i); } } } } public class ThreadTest { public static void main(String[] args) { // 3. 创建 Thread 类的子对象 MyThread t1 = new MyThread(); // 4. 通过此对象调用 start(): // ①启动当前线程 ②调用当前线程的 run() t1.start(); // 如下操作仍在 main 线程中执行的 for(int i = 1;i < 100;i++){ if(i % 2 == 0){ System.out.println(i + "***main()***"); } }
测试 Thread 类有关方法:
start():启动当前线程,执行当前线程的 run()。
run():通常需要重写 Thread 类中的此方法,将创建的线程要执行的 操作声明在此方法中。
currentThread():静态方法,返回当前代码执行的线程。
getName():获取当前线程的名字。
setName():设置当前线程的名字。
yield():释放当前 CPU 的执行权。
join():在线程 a 中调用线程 b 的 join(), 此时线程 a 就进入阻塞状态, 直到线程 b 完全执行完以后,线程 a 才结束阻塞状态。
stop():已过时。当执行此方法时,强制结束当前线程。
sleep(long millitime):让当前线程“睡眠”指定时间的 millitime 毫秒。 在指定的 millitime 毫秒时间内,当前线程是阻塞状态的。
isAlive():返回 boolean,判断线程是否还活着
线程的调度
-
线程的优先级 调度策略: 时间片。
-
抢占式:高优先级的线程抢占 CPU。
Java 的调度方法: 同优先级线程组成先进先出队列(先到先服务),使用时间片策略; 对高优先级,使用优先调度的抢占式策略
线程的优先级
线程的优先级等级: - MAX_PRIORITY:10
MIN _PRIORITY:1 - NORM_PRIORITY:5 --->
默认优先级 涉及的方法: - getPriority() :返回线程优先值。
setPriority(intnewPriority) :改变线程的优先级。
说明 : 高优先级的线程要抢占低优先级线程 cpu 的执行权。但是只是从 概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优 先级的线程执行完以后,低优先级的线程才会被执行
实现 Runnable 接口
创建多线程的方式二:实现 Runnable 接口 1. 创建一个实现了 Runnable 接口的类
.实现类去实现 Runnable 中的抽象方法 :run();
创建实现类的对象;
将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对 象;
通过 Thread 类的对象调用 start()
继承方式和实现方式的联系
比较创建线程的两种方式。 开发中:优先选择:实现 Runnable 接口的方式。
原因:
实现的方式没有类的单继承性的局限性。
实现的方式更适合来处理多个线程有共享数据的情况。 联系:public class Thread implements Runnable。
相同点:两种方式都需要重写 run(), 将线程要执行的逻辑声明在 run() 中。
线程的生命周期
新建---就绪---运行---阻塞---死亡
阻塞:没有抢夺到cup资源,或者被人零时挂起,终止自己的操作
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
线程同步:
解决多个线程执行的不确定性引起执行结果的不稳定。 多个线程对账本的共享,会造成操作的不完整性,会 破坏数据
同步的方式
-
使用synchronized修饰同步监视器
-
操作共享数据的代码,即为需要同步的代码---》不能包含代码多了,也不能少
-
共享数据:多个线程共同操作的变量
-
同步监视器:俗称:锁。任意一个类的对象都可以来充当锁
-
多个线程必须要共用一把锁
-
synchronized( 同步监视器 ){ // 需要被同步的代码 }
-
声明在一个方法上(同步方法)
-
好处:解决了线程的安全问题
-
操作代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低---局限
-
synchronized (dog)
补充:
实现Runnable接口创建多线程的方式中,我们可以使用this充当同步监视器
class Windows1 implements Runnable { private int ticket = 100; // Object obj = new Object(); // Dog dog = new Dog(); 282 283 @Override public void run() { while (true) { synchronized (this) { // 此时的 this: 唯一的 windows1 的对象 // 方式二 :synchronized (dog) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread(). getName() + ": 卖票,票号为 : " + ticket); ticket--; } else { break; } } } } } public class WindowsTest1 { public static void main(String[] args) { Windows1 w = new Windows1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName(" 窗口 1"); t2.setName(" 窗口 2"); t3.setName(" 窗口 3"); t1.start(); t2.start(); t3.start(); } } class Dog { }
同步代码块处理继承 Thread 类 的线程安全问题
class Windows extends Thread { private static int ticket = 100; private static Object obj = new Object(); @Override public void run() { while (true) { // 正确的 // synchronized (obj) { synchronized (Windows.class) { // Class clazz = Windows.class // 错误的,因为此时 this 表示的是 t1,t2,t3 三个对象 // synchronized (this) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + ": 卖 票, 票 号为 : " + ticket); ticket--; } else { break; } } } } } 同步代码块处理继承 Thread 类 的线程安全问题 使用同步代码块解决继承 Thread 类的方式的线程安全问题。 例子:创建三个 c 窗口卖票,总票数为 100 张。 284 285 public class WindowsTest2 { public static void main(String[] args) { Windows t1 = new Windows(); Windows t2 = new Windows(); Windows t3 = new Windows(); t1.setName(" 窗口 1"); t2.setName(" 窗口 2"); t3.setName(" 窗口 3"); t1.start(); t2.start(); t3.start(); }
同步方法处理实现Runnable的线程安全问题
-
同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
-
非静态的同步方法,同步监视器是:this; 静态的同步方法,同步监视器是:当前类本身。
class Windows3 implements Runnable { private int ticket = 100; @Override public void run() { while (true) { show(); } } public synchronized void show() { // 同步监视器:this // synchronized (this){ if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ": 卖票,票号为 : " + ticket); ticket--; } // }} } public class WindowsTest3 { public static void main(String[] args) { Windows3 w3 = new Windows3(); 同步方法处理实现 Runnable 的线程安全问题 使用同步方法解决实现 Runnable 接口的线程安全问题 关于同步方法的总结: 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。 2. 非静态的同步方法,同步监视器是:this; 静态的同步方法,同步监视器是:当前类本身。 286 287 Thread t1 = new Thread(w3); Thread t2 = new Thread(w3); Thread t3 = new Thread(w3); t1.setName(" 窗口 1"); t2.setName(" 窗口 2"); t3.setName(" 窗口 3"); t1.start(); t2.start(); t3.start(); }
线程安全的单例模式之懒汉式
使用同步机制将单例模式中的懒汉式改写为线程安全的。
public class BankTest { } class Bank { private Bank() { } private static Bank instance = null; public static Bank getInstance() { // 方式一:效率稍差 // 快捷键 :Alt+Shift+Z // synchronized (Bank.class) { // if(instance == null){ // instance = new Bank(); / } // return instance; // } // 方式二:效率较高 if (instance == null) { synchronized (Bank.class) { if (instance == null) { instance = new Bank(); } } } return instance; } }
死锁问题
死锁的理解:
不同的线程分别占用对方需要同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁
说明:
死锁形成后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,我们使用同步时,要避免出现死锁
public class ThreadTest { public static void main(String[] args) { StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer(); new Thread() { @Override public void run() { synchronized (s1) { s1.append("a"); s2.append("1"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s2) { s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } }.start(); new Thread(new Runnable() { @Override public void run() { synchronized (s2) { s1.append("c"); s2.append("3"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (s1) { s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }).start(); }
Lock 锁方式解决线程安全问题
java.util.concurrent.locks.Lock 接口是控制多个线程对 共享资源进行访问的工具。锁提供了对共享资源的独占 访问,每次只能有一个线程对 Lock 对象加锁,线程开始 访问共享资源之前应先获得 Lock 对象。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中, 比较常用的是 ReentrantLock,可以显式加锁、释放锁
synchronized 与 Lock 的异同?
-
相同:二者都可以解决线程安全问题;
-
不同:synchronized 机制在执行完相应的同步代码以后,自动的 释放同步监视器。 Lock 需要手动的启动同步(lock()),同时结束同步也需要手动 的实现(unlock())。 2. 优先使用顺序: Lock ·同步代码块(已经进入了方法体,分配了相应资源) ·同步方法(在方法体之外)
线程的通信
涉及到的三个方法:
-
wait():一旦执行此方法,当前线程就进入阻塞状态, 并释放同步监视器。
-
notify():一旦执行此方法,就会唤醒被 wait 的一个 线程。如果有多个线程被 wait,就唤醒优先级高的那个。
-
notifyAll():一旦执行此方法,就会唤醒所有被 wait 的线程。
说明:
wait(),notify(),notifyAll() 三个方法必须使用在同 步代码块或同步方法中。
wait(),notify(),notifyAll() 三个方法的调用者必须 是同步代码块或同步方法中的同步监视器。否则,会出 现 IllegalMonitorStateException 异常。
wait(),notify(),notifyAll() 三个方法是定义在 java. lang.Object 类中。
sleep() 和 wait() 的异同
相同点:
-
一旦执行方法,都可以使得当前的线程进入 阻塞状态。
不同点:
两个方法声明的位置不同: Thread 类中声明 sleep() Object 类中声明 wait()
调用的要求不同: sleep() 可以在任何需要的场景下调用。 wait() 必须使用在同步代码块或同步方法中。
关于是否释放同步监视器:如果两个方法都使用在 同步代码块或同步方法中,sleep() 不会释放锁,wait() 会 释放锁
创建多线程的方式三:实现 Callable 接口
如何理解实现 Callable 接口的方式创建多线程比实现 Runnable 接口创建多线程方式强大?
call() 可以有返回值的。
call() 可以抛出异常,被外面的操作捕获,获取异 常的信息。
Callable 是支持泛型的。
. 需要借助 FutureTask 类,比如获取返回结果
Future 接口
-
可以对具体 Runnable、Callable 任务的执行结果进行 取消、查询是否完成、获取结果等。
-
FutrueTask 是 Futrue 接口的唯一的实现类。
-
-
FutureTask 同时实现了 Runnable, Future 接口。它既 可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值
//1. 创建一个实现 Callable 的实现类 class NumThread implements Callable { // 2. 实现 call 方法,将此线程需要执行的操作声明在 call() 中 @Override public 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; } } public class ThreadNew { public static void main(String[] args) { // 3. 创建 Callable 接口实现类的对象 NumThread numThread = new NumThread(); // 4. 将此 Callable 接口实现类的对象作为传递到 FutureTask 构 造器中,创建 FutureTask 的对象 FutureTask futureTask = new FutureTask(numThread); // 5. 将 FutureTask 的对象作为参数传递到 Thread 类的构造器 中,创建 Thread 对象,并调用 start() new Thread(futureTask).start(); try { // 6. 获取 Callable 中 call 方法的返回值 // get() 返回值即为 FutureTask 构造器参数 Callable 实现 类重写的 call() 的返回值。 Object sum = futureTask.get(); System.out.println(" 总和为 :" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
创建多线程的方式四:使用线程池
经常创建和销毁、使用量特别大的资源,比如并发情 况下的线程,对性能影响很大。
思路: 提前创建好多个线程,放入线程池中,使用时直接获 取,使用完放回池中。可以避免频繁创建销毁、实现重 复利用。类似生活中的公共交通工具
好处:
提高响应速度(减少了创建新线程的时间) 降低资源消耗(重复利用线程池中线程,不需要每次 都创建) 便于线程管理 corePoolSize:核心池的大小 maximumPoolSize:最大线程数 keepAliveTime:线程没有任务时最多保持多长时 间后会终止
class NumberThread implements Runnable { @Override public void run() { for (int i = 0; i <= 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread(). getName() + ":" + i); } } } } class NumberThread1 implements Runnable { @Override public void run() { for (int i = 0; i <= 100; i++) { if (i % 2 != 0) { System.out.println(Thread.currentThread(). getName() + ":" + i); } } } } public class ThreadPool { public static void main(String[] args) { // 1. 提供指定线程数量的线程池 ExecutorService service = Executors. newFixedThreadPool(10); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; // 设置线程池的属性 // System.out.println(service.getClass()); // service1.setCorePoolSize(15); // service1.setKeepAliveTime(); // 2. 执行指定的线程的操作。需要提供实现 Runnable 接口或 Callable 接口实现类的对象 service.execute(new NumberThread()); // 适合适用于 Runable service.execute(new NumberThread1()); // 适合适用于 Runable // service.submit(Callable callable); // 适合适用于 Callable // 3. 关闭连接池 service.shutdown(); } }
线程池相关 API JDK 5.0 起提供了线程池相关 API:ExecutorService 和 Executors。 ExecutorService:真正的线程池接口。
常见子类 ThreadPoolExecutor。
·void execute(Runnable command) :执行任务 / 命令,没有返回值, 一般用来执行 Runnable。
·Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable。 ·void shutdown():关闭连接池。
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线 程池。
·Executors.newCachedThreadPool():创建一个可根据需要创建新线程 的线程池。 ·
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程 池。
·Executors.newSingleThreadExecutor():创建一个只有一个线程的线程 池。
·Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在 给定延迟后运行命令或者定期地执行