多线程
Posted lotus-wmm
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程相关的知识,希望对你有一定的参考价值。
一、 概念
多线程: 多条执行路径
程序是指:指令的集合
进程: 程序说在运行中由cpu调度,开辟出来的一条路
通过debug模式查看 main 的主线程
public class Test
public static void main(String[] args)
System.out.println("main---- 开始"); a(); // 执行方法 a() System.out.println(" main---- 结束");
public static void a() System.out.println(" -----> a");
public static void b() System.out.println(" -----> b");
|
运行结果
main---- 开始
-----> a
-----> b
main---- 结束
程序执行路径示意图:
初学一来我们的程序都是只有main 方法去执行------- > 这个就被称之为main线程
查看单线程的程序执行顺序
二、 创建多线程
多线程的创建方式有多种模式
1. Thread 类 继承模式
通过继承thread类的模式,重写 run()方法 , 使用start() 方法启动线程。
示例: (注意事项:多线程启动不要直接调run方法(普通方法调用))
public class Test extends Thread /** * 使用Thread 创建多线程 * * 1. 继承Java.lang.Thread 类型 * 2. 重写 run 方法 * 3. 实例化子对象 * 4. 启动多线程 start()
* @throws InterruptedException */ public static void main(String[] args) throws InterruptedException // 3. 实例化子对象 Test test = new Test(); // 4. 启动 test.start();
for (int i = 0; i < 500; i++)
System.out.println("Thread ---> main " + i); Thread.sleep(10);
/** * * 重写 run 方法 */ @Override public void run() // 线程体 for (int i = 0; i < 500; i++) System.out.println("Thread ---> run " + i);
|
总结:Java 单继承模式,这也是造成继承Thread类这种创建方式不是很适用
2. Runnable 接口实现模式 (推荐)
示例:
public class Test
/** * 使用Runnable 接口实现 创建多线程 * 1. 实现接口 Runnable * 2. 重写 run() * 3. 创建 示例化对象 student * 4. 创建Runnable 的子类对象 Thread 使用代理模式 * 5. 线程启动 */ public static void main(String[] args) throws InterruptedException //创建 示例化对象 student Student student = new Student(); // 创建Runnable 的子类对象 Thread 使用代理模式 Thread thread = new Thread(student); // 线程启动 thread.start();
/** * 创建student 类 */ class Student implements Runnable
@Override public void run()
System.out.println("Runnable ----- > run");
|
总结:
优点:避免了单继承的局限性
缺点: runnable 接口 不能申明异常,并且没有返回值
3. 深度解析Runnable
静态代理模式
结构模式:(前置条件) 切面编程的思想
1.真实角色
2.代理角色 持有真实角色的引用
3.共同接口
public class StaticProxy
public static void main(String[] args)
// 使用代理模式 //1. 创建真实角色 You you = new You (); //2. 创建代理角色 + 真实对象 MarryProxy proxy = new MarryProxy(you);
// 执行 proxy.marry();
//2.代理角色 class MarryProxy implements Marry
private You you;
public MarryProxy()
public MarryProxy(You you)
this.you = you;
private void before () System.out.println("结婚之前----> 搞定");
@Override public void marry() before (); you.marry(); after ();
private void after () System.out.println("结婚之后----> 搞定");
//真实角色 class You implements Marry
@Override public void marry()
System.out.println("you 结婚---->");
//共同接口 结婚方法 interface Marry public abstract void marry(); |
4.实现 Callable 接口 (了解)
来源:java.util.concurrent并发
创建步骤:
- 实现Callable 接口 创建多线程
- 重写call
- 启动
① 创建任务调度
ExecutorService service = Executors.newFixedThreadPool(2);
② 实例化多线程对象
Student student = new Student();
③ 任务提交
Future<String> future = service.submit(student);
④ 解析任务结果
String res = future.get();
⑤ 停止线程
service.shutdown();
示例:
public static void main(String[] args) throws InterruptedException, ExecutionException
// 1.创建任务调度 ExecutorService service = Executors.newFixedThreadPool(2); Student student = new Student(); // 实例化多线程提交 Future<String> future = service.submit(student); // 解析任务结果 String res = future.get();
System.out.println(res);
// 停止线程 service.shutdown();
/** * * 1. 实现 Callable 接口 * 2. 重写 call 方法 */ // 1. 实现 Callable 接口 class Student implements Callable<String> // 2. 重写 call 方法 @Override public String call() throws Exception return "Callable ----> call()";
|
三、 案例示范
1. 12306简单抢票模拟
Web12306对象创建
public class Web12306 implements Runnable
private int num = 50;
@Override public void run()
while (true) if (num <= 0) break; // 跳出循环
System.out.println(Thread.currentThread().getName() + "<--抢票成功-->"+num-- );
|
模拟抢票
/** 启动:使用静态代理 1、创建真实角色 2、创建代理角色 Thread+引用 3、代理角色.start() */ public class Webapp public static void main(String[] args)
Web12306 web = new Web12306 ();
Thread t1 = new Thread(web, "黄牛甲"); Thread t2 = new Thread(web, "黄牛已"); Thread t3 = new Thread(web, "黄牛丙"); Thread t4 = new Thread(web, "路人甲");
t1.start(); t2.start(); t3.start(); t4.start();
|
2. 龟兔赛跑 模拟
代码git账户
四、 线程状态
1. 线程五种状态
1)、新生状态: new (线程在被创建的时候,new)
2)、就绪状态: runnab (线程被start方法调用使用 等待CPU调度)
3)、运行状态: running (CPU执行调度 执行线程)
4)、阻塞状态: blocke (线程阻塞,在解除阻塞时,不是马上执行,而是进入就绪状态)
5)、执行完毕: dead (自然结束或者非正常终止)
五、 线程其他知识点
1. 线程阻塞sleep
线程睡眠状态含义,每一个线程对象都一把排他锁,当该线程处于sleep时候,该线程对所占有的资源,并不会释放锁,其他线程对象无法获取到当前线程对象所占有的共享资源。
Sleep:可以模拟网络延迟
应用场景 : 1. 与时间相关 2. 网络延迟
示例:
倒计时演示
public class SleepDemo public static void main(String[] args) throws InterruptedException /** * 倒计时 10 秒 */ for (int i = 10; i > 0; i--) System.out.println("倒计时:---->" +i ); Thread.sleep(1000);
System.out.println("倒计时结束。。。。");
|
2.终止线程
情况:
- 线程正常执行完毕
- 外部干涉 线程停止
(不要使用过时的方法stop destory 等方法使得线程停止,方法不安全)
操作方法:
- 线程类中加入标示属性
- 线程体加入循环标示 ----- > 循环体
- 对外提供改变标示的的方法 setXXXX terminate() 等
- 外部根据条件 调用方法 进行改变标识
3. 线程的小知识点
Thread.currentThread() 获取当前线程对象
public class ThreadOthers
public static void main(String[] args) // 开启线程 new Thread(new Runnable() @Override public void run() // 返回对当前正在执行的线程对象的引用 System.out.println(Thread.currentThread()); //返回该线程的名称 System.out.println(Thread.currentThread().getName()); // 返回线程的优先级。 System.out.println(Thread.currentThread().getPriority());
).start();
// 获取当前线程 名称 System.out.println(Thread.currentThread().getName()); System.out.println(Thread.currentThread().getPriority()); Thread.currentThread().setPriority(9); System.out.println(Thread.currentThread().getPriority());
// 线程的优先级 只是代表的概率 不代表绝对的先后顺序 //只是代表CPU的调度的概率大一些
|
4.线程优先级:priority
优先级不代表先后执行顺序,代表的是个概率
优先级只是在CPU 在选择线程的是提供参考选择的概率,并不是意味着CPU下次的调度的时候一定会选择选择 优先级高的线程
MIN_PRIORITY : 1
NORM_PRIORITY : 5 默认优先级
MAX_PRIORITY :10
Thread下的方法:getPriority() setPriority()
示例:Thread.currentThread().setPriority(9); // 设置线程的优先级为9
5.线程合并 (join)
含义: 当前线程执行到满意有某一个条件的时候,暂停执行 将其他线程的合并加入到当前线程中,待执行完毕后,再执行当前线程 (通俗来说就是将两个线程合成为一个线程并且 自己等待让加入过来的线程先执行)
示例:
/** * join : 合并线程 * */ public class JoinDemo01 extends Thread
public static void main(String[] args) throws InterruptedException
// 启动多线程 JoinDemo01 JoinDemo01 = new JoinDemo01();// 创建对象 // 新生 Thread tr = new Thread(JoinDemo01);// 存放代理对象 // 就绪 tr.start();// 线程启动 // CPU 调度 即 开始运行
for (int i = 0; i < 100; i++) if (i==50) tr.join(); // 线程合并
Thread.sleep(100); System.out.println("main..." + i);
@Override public void run() // 多线线程run for (int i = 0; i < 100; i++)
System.out.println("Thread..." + i); try Thread.sleep(100); catch (InterruptedException e) e.printStackTrace();
|
六、线程同步问题
----- > 多线程对共享资源的争夺战
多个线程同时访问一个对象,可能造成非线程安全,数据可能错误,所谓同步:就是控制多个线程同时访就是控制多线程操作同一个对象时,注意是同一个对象,数据的准确性,确保数据安全,但是加入同步后因为需要等待,所以效率相对低下。
如:一个苹果,自己一个人去咬怎么都不会出问题,但是多个人同时去咬,这个时候如果控制不好,就可能会咬到对方了。 再强调一下是一个苹果。
12306 中的火车票,为什么抢到票时,需要等待,锁定席位才付款?这就是确保一张票只能一个人去购买
同步: 使用的Java的关键词 synchronized
1.概念
所谓的同步问题就是多个线程访问,也就是多个线程控制操作同一个数据对象(共享资源),但是在同一对象操作的时候,我们需要确保这个这个对象的数据的正确性与安全性,所以我们在操作的操作的时候会加入一个synchronized ,这样则需要线程在进入操作共享资源进行等待,执行的效率下降。
Synchronized : 难点,范围小则 线程不安全,范围大则效率地下
关键点是如何把握synchronized 的范围。
Synchronized 控制结构
1.同步块 (入参数是引用类型)
synchronized (对象|类型.class)
// 同步区
2. 同步方法
在方法的 的关键词中修饰添加 synchronized 关键词,
结构:
权限修饰符
synchronized 返回值类型 方法名称 ([入参])
// 同步区
2. 案例演示 -- 1
在12306简单抢票模拟中 添加线程睡眠 , 模拟网络延迟, 以此来模拟 “超卖”的现象。
解决方案:
添加线程同步 synchronized 来解决问题
public class Web12306 implements Runnable private int num = 50; @Override public void run()
|
public class Webapp public static void main(String[] args)
Web12306 web = new Web12306 ();
Thread t1 = new Thread(web, "黄牛甲"); Thread t2 = new Thread(web, "路人已"); Thread t3 = new Thread(web, "机器人"); Thread t4 = new Thread(web, "路人甲");
t1.start(); t2.start(); t3.start(); t4.start();
|
3. 案例演示 -- 2
单例模式的安全隐患 (懒汉式)
单例模式类型扩展: http://cantellow.iteye.com/blog/838473; (七种模式)
单例设计模式
结构:要求确保一个类只有一个对象
懒汉式步骤:
- 构造器私有化
- 声明一个私有的静态属性
- 创建对外的公共方法去获取对象,如果没有对象则去获取一个对象
class Jvm // 2.声明一个私有的静态变量 private static Jvm jvm = null; // 构造器私有化 避免外界直接创建 private Jvm()
// 3.创建 一个对外的公共方法,如果没有对象则创建对象。
public static Jvm getInstance01(long time) throws InterruptedException if (jvm == null) Thread.currentThread().sleep(time); // 模拟延迟 提升错误的概率 jvm = new Jvm(); // 如果没有对象则创建对象。
return jvm;
|
安全性懒汉单例模式
public class TestSingleModel
public static void main(String[] args)
JvmThread jt1 = new JvmThread(1000);
JvmThread jt2 = new JvmThread(500);
// 启动 jt1.start(); jt2.start();
/** * 创建多线程启动 1. * */ class JvmThread extends Thread
private long time;
public JvmThread(long time) this.time = time;
@Override public void run() try System.out.println(Thread.currentThread().getName() + "---->" + Jvm.getInstance03(time)); catch (InterruptedException e) e.printStackTrace();
/** * 单例模式设计模式 * 确保一个类只有一个对象 * 懒汉式 * 1.构造器私有化 * 2.声明一个私有的静态变量 * 3.创建 一个对外的公共方法,如果没有对象则创建对象。 */
class Jvm
// 2.声明一个私有的静态变量 private static Jvm jvm = null;
// 构造器私有化 避免外界直接创建 private Jvm()
// 3.创建 一个对外的公共方法,如果没有对象则创建对象。
public static Jvm getInstance01(long time) throws InterruptedException if (jvm == null) Thread.currentThread().sleep(time); // 模拟延迟 提升错误的概率 jvm = new Jvm(); // 如果没有对象则创建对象。
return jvm;
// 同步方法 public synchronized static Jvm getInstance02(long time) throws InterruptedException if (jvm == null) Thread.currentThread().sleep(time); // 模拟延迟 提升错误的概率 jvm = new Jvm(); // 如果没有对象则创建对象。
return jvm;
// 同步块 public synchronized static Jvm getInstance03(long time) throws InterruptedException // 双重校验锁 机制 if (jvm == null) synchronized (Jvm.class) // jvm 有一个类模板 classloder机制 if (jvm == null) // 第二次检查 Thread.currentThread().sleep(time); // 模拟延迟 提升错误的概率 jvm = new Jvm(); // 如果没有对象则创建对象。
return jvm;
|
4、死锁问题
原因:过多的同步容易造成死锁
案例示范
package com.shsxt;
/** * 死锁:容易造成死锁 * * @author Administrator * */ public class TestDeadLock
public static void main(String[] args) Object obj1 = new Object(); Object obj2 = new Object(); new A(obj1, obj2).start(); new B(obj1, obj2).start();
class A extends Thread Object obj1; Object obj2;
public A()
public A(Object obj1, Object obj2) super(); this.obj1 = obj1; this.obj2 = obj2;
@Override public void run() synchronized (obj1) try Thread.sleep(200); catch (InterruptedException e) e.printStackTrace();
synchronized (obj2)
System.out.println("给我烟");
class B extends Thread Object obj1; Object obj2;
public B()
public B(Object obj1, Object obj2) super(); this.obj1 = obj1; this.obj2 = obj2;
@Override public void run() synchronized (obj2) try Thread.sleep(100); catch (InterruptedException e) e.printStackTrace();
synchronized (obj1)
System.out.println("给我钱");
|
七、 生产者消费者模式(了解)
注意不是设计模式
线程通信的目标是使线程间能够互相发送信号。另一方面,线程通信使线程能够等待其他线程的信号。
Java 有一个内建的等待机制来允许线程在等待信号的时候变为非运行状态。
java.lang.Object 类定义了三个方法,wait()、notify()和 notifyAll()来实现这个等待
机制。一个线程一旦调用了任意对象的 wait()方法,就会变为非运行状态,直到另一
个线程调用了同一个对象的 notify()方法。为了调用 wait()或者 notify(),线程必须先
获得那个对象的锁。也就是说,线程必须在同步块里调用 wait()或者 notify()。
以下为一个使用了 wait()和 notify()实现的线程间通信的共享对象
public class Street //flag -->true 人走 车等 南北向 //flag -->false 车走人等 东西向 private boolean flag =false;
//南北向 -->人走 车等 ,过去后,变灯 通知车走 public synchronized void north() if(flag==false) //人 等 try this.wait(); catch (InterruptedException e) e.printStackTrace();
//人走 try Thread.sleep(5000); catch (InterruptedException e) e.printStackTrace();
System.out.println("人走南北向....");
//变灯 this.flag =false; //通知车走 this.notify();
//东西向 -->车走人等,过去后,变灯 通知人走 public synchronized void east() if(flag ==true) //车等 try this.wait(); catch (InterruptedException e) e.printStackTrace();
//车走 try Thread.sleep(2000); catch (InterruptedException e) e.printStackTrace();
System.out.println("车走东西向....");
//变灯 this.flag =true; //通知人走 this.notify();
|
person
public class Person implements Runnable private Street street;
public Person(Street street) this.street =street;
@Override public void run() for(int i=0;i<5;i++) street.north();
|
car
public class Car implements Runnable private Street street;
public Car(Street street) this.street =street;
@Override public void run() for(int i=0;i<5;i++) street.east();
|
app
public class App
public static void main(String[] args) Street s =new Street(); //同一份资源 Person p =new Person(s); Car c =new Car(s);
new Thread(p).start(); new Thread(c).start();
|
八 、Java任务调度(了解)
解决定时非人为触发事件或者是程序执行
步骤:
- 创建一个新计时器
- 创建任务
- 安排指定的任务
代码示例
public class TimDemo
public static void main(String[] args) final long currentTimeMillis = System.currentTimeMillis(); System.out.println(new Date(currentTimeMillis));
// 1.创建一个新计时器 Timer timer = new Timer();
// 2.创建任务 TimerTask task = new TimerTask() @Override public void run() System.out.println(new Date(System.currentTimeMillis())); System.out.println("启动任务调度。。。。。。。");
;
// 3.安排指定的任务 timer.schedule(task, new Date(currentTimeMillis + 10000), 2000);
|
|
以上是关于多线程的主要内容,如果未能解决你的问题,请参考以下文章