05_多线程
来源:互联网 发布:php 表单验证 数据库 编辑:程序博客网 时间:2024/05/16 18:53
一、多线程简介
(1)、进程:是一个正在执行中的程序
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元
(2)、线程:就是进程中一个独立的控制单元
线程在控制着进程的执行。
一个进程中至少要有一个线程
开启多个线程是为了同时运行多部分代码
每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务
线程运行的程序在main方法中,该线程就是主线程。
(3)、多线程的利与弊:
多线程的好处:解决了多部分同时运行的问题
多线程的弊端:线程太多回到的效率降低
(4)、多线程的随机性:
其实应用程序的执行都是cpu在做着快速的切换完成的 ,这个切换是随机的,我们可以形象把多线程的运行行为在互相抢夺cpu执行权。这就是多线程的随机性。
jvm启动时就启动了多个线程,至少有两个线程可以分析出来
1、执行main函数的线程
该线程的任务代码都定义在main函数中
2、负责垃圾回收的线程
二、线程的创建
(1)、方式一: 继承Thread类
步骤:
1、定义类继承Thread.
2、复写Thread类中的run方法。
目的:将自定义代码存储在run方法。让线程运行。
3、调用线程的start方法。
该方法有两个作用 :启动线程,调用run方法。
为什么要覆盖run方法呢?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。
也即是说run方法是用于存储线程要运行的代码。
- public class Demo {
- public static void main(String[] args) {
- /*
- * 创建线程的目的就是为了开启一条执行路径,去运行的代码和其他代码实现同时运行
- *
- * 而运行的代码就是这个执行路径的任务
- *
- * jvm创建的主线程的任务都定义在了主函数中。
- *
- * 而自定义的线程它的任务在哪呢? Thread类用于描述线程,线程是需要任务的,所以Thread类也对任务的描述
- * 这个任务就是通过Thread类的run方法来体现的, 也就是说,run方法封装自定义线程运行任务的函数
- *
- * run方法中定义就是线程要运行的任务代码
- *
- * 开启线程就是运行指定代码,所以只有继承Thread类,并复写run方法。 将运行的代码定义在run方法中即可
- */
- Demo4 d4 = new Demo4("李四");
- Demo4 d5 = new Demo4("张三");
- //<strong><span style="color:#ff0000;"> d4.run();//仅仅是对象调用方法。而线程创建了,并没有执行</span></strong>
- <span style="color:#ff0000;">d4.start();// 开启线程调用run方法</span>
- d5.start();
- System.out.println("结束了这个线程。。" + Thread.currentThread().getName());
- }
- }
- /**
- * 继承方式
- */
- class Demo4 extends Thread {
- private String name;
- Demo4(String name) {
- <span style="color:#ff0000;">super(name);// 给定义的线程命名</span>
- }
- // 重写run方法
- public void run() {
- // 循环测试
- for (int x = 0; x < 10; x++) {
- System.out.println(name + ".." + x + "..."
- + Thread.currentThread().getName());
- }
- }
- }
新建状态(New):新创建了一个线程对象。
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
没有执行资格的情况下是冻结状态。sleep(), 时间到 wait(),notify()
有执行资格的状态叫做临时状态。
既有资格又有执行权运行状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程对象以及名称:
原来线程都有自己默认的名称。
Thread-编号 该编号从0开始。
Thread 对象的setName() getName();方法
线程初始化名称:
构造方法 super(name);
Thread currentThread():获取当前正在执行的线程对象的引用。
(2)、方式二:实现Runnable接口
1、步骤:
a、定义类实现Runnable接口
b、覆盖接口Runnable中的run方法,将线程的任务代码封装到run方法中
c、通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递
为什么呢?因为线程的任务都封装在Runnable接口子类对象的run方法中。 所以要线程对象的start方法开启线程
d、调用Thread类中的start方法,开启线程并调用Runnnable接口。
2、实现Runnable接口的好处:
a、将线程的任务从线程的子类中分离出来,进行单独的封装
按照面向对象的思想将任务封装成对象
b、避免了java单继承的局限性
所以创建线程的第二种方式较为常用
- public class Test9 {
- public static void main(String[] args) {
- TextThread t = new TextThread();
- Thread t1 = new Thread(t);//线程1
- Thread t2 = new Thread(t);//线程2
- t1.start();
- t2.start();
- }
- }
- /**
- * 继承方式,实现Runnable接口
- * @author Administrator
- *
- */
- class TextThread implements Runnable {
- //重写run方法
- @Override
- public void run() {
- // TODO Auto-generated method stub
- show();
- }
- //测试方法
- public void show() {
- for (int i = 0; i < 20; i++) {
- System.out.println(Thread.currentThread().getName() + "..." + i);
- }
- }
- }
继承Thread:线程代码存放在Thread子类run方法中
实现Runnnable:线程代码存在接口子类的run方法。
在定义线程时,建议使用实现方式。
三、线程的安全问题
(1)、发现问题
- public class Test9 {
- public static void main(String[] args) {
- Ticket1 t = new Ticket1();
- // 开启四个线程
- Thread t1 = new Thread(t);
- Thread t2 = new Thread(t);
- Thread t3 = new Thread(t);
- Thread t4 = new Thread(t);
- t1.start();
- t2.start();
- t3.start();
- t4.start();
- }
- }
- class Ticket1 implements Runnable {
- private int ticket = 10;// 共享数据
- public void run() {
- while (ticket > 0) {
- if (ticket > 0) {
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "。。sale。。"
- + ticket--);// 有负数的存在,安全隐患
- }
- }
- }
- }
通过分析,发现,打印出0,-1,-2等错票
(2)、解决办法:1、问题原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。
导致共享数据的错我。
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式。
这种专业的解决方式就是:同步代码快
哪些代码需要同步,就看哪些语句在操作共享数据。
synchronized(对象){
需要被同步的代码
}
程序示例:
- public class Test9 {
- public static void main(String[] args) {
- //开启4个线程
- Ticket1 t = new Ticket1();
- Thread t1 = new Thread(t);
- Thread t2 = new Thread(t);
- Thread t3 = new Thread(t);
- Thread t4 = new Thread(t);
- t1.start();
- t2.start();
- t3.start();
- t4.start();
- }
- }
- class Ticket1 implements Runnable {
- private int ticket = 100;
- Object obj=new Object();
- public void run() {
- while (true) {
- //同步代码块,加锁,对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
- synchronized (this) {
- if (ticket > 0) {
- try {
- Thread.sleep(10);//让线程休眠
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()
- + "。。sale。。" + ticket--);//没有出现负数票
- }
- }
- }
- }
- }
1、同步代码快的理解:
对象如同锁,持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
举例:火车上的卫生间,里面有人时,门就会锁住,别人就进不去。只有人出来了,把门打开,其他人才能进去。
2、同步的前提:
a、必须要有两个或者两个以上的线程。
b、必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
3、同步的利与弊:
好处:解决多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源。允许消耗范围内的。
(3)、同步的两种表现形式
1、是同步代码块(如上程序段所示)
2、是同步函数。把synchronized作为修饰符放在函数上。
程序示例:
- public class Test10 {
- public static void main(String[] args) {
- Tickets t = new Tickets();
- // 创建4个线程卖票,并开启
- Thread t1 = new Thread(t);
- Thread t2 = new Thread(t);
- Thread t3 = new Thread(t);
- Thread t4 = new Thread(t);
- t1.start();
- t2.start();
- t3.start();
- t4.start();
- }
- }
- class Tickets implements Runnable {
- private int ticket = 1000;// 票数
- // 复写run方法调用show
- public void run() {
- while (ticket > 0) {
- this.show();
- }
- }
- // 同步函数所持有的锁是this
- public synchronized void show() {
- if (ticket > 0) {
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "...sale..."
- + ticket--);
- }
- }
- }
(4)、同步函数和同步代码块:
1、同步函数使用的锁是this
验证:使用两个线程来买票。
一个线程在同步代码块中。
一个线程在同步函数中。
都在执行买票动作。
2、同步代码块使用的锁是任意对象。
(5)、静态同步函数的锁
静态的同步函数使用的锁是:该函数所属字节码文件对象
可以使用getClass方法获取,也可以用“当前类名.class“表示
静态的同步方法使用的锁是该方法所在类的字节码对象。
验证方法如下:
- public class Test10 {
- public static void main(String[] args) {
- Tickets t = new Tickets();
- // 创建4个线程卖票,并开启
- Thread t1 = new Thread(t);
- Thread t2 = new Thread(t);
- /*
- * Thread t3=new Thread(t); Thread t4=new Thread(t);
- */
- t1.start();// t1一开启跑到同步代码块中。,开启这个线程不一定立即执行。处于临时状态,有可能执行下面一句
- t.flag = false;// 在t2开启之前,把标识变为false;
- try {
- Thread.sleep(10);// 主线程停止10毫秒,只能是t1在运行。过了时间段,可能执行下面的语句
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- t2.start();// t2一开启跑到同步函数中。
- /*
- * t3.start(); t4.start();
- */
- }
- }
- class Tickets implements Runnable {
- private static int tick = 1000;// 票数
- // 复写run方法调用show
- Object obj = new Object();
- boolean flag = true;
- @Override
- public void run() {
- // TODO Auto-generated method stub
- if (flag) {
- while (true) {
- // 同步代码块
- // synchronized(obj),存在安全问题
- synchronized (Test10.class) {
- if (tick > 0) {
- try {
- Thread.sleep(10);
- System.out.println("同步代码块");
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- }
- } else {
- while (true) {
- show();
- }
- }
- }
- // 同步函数
- public synchronized void show() {
- if (tick > 0) {
- try {
- Thread.sleep(10);
- System.out.println("同步函数");
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- }
(6)、懒汉式单利模式:
加入同步为了解决线程安全问题
加入双重判断是为了解决效率问题
此处是懒汉式示例:
- public class Test10 {
- public static void main(String[] args) {
- System.out.println("hello");
- }
- }
- class SingleDemo {
- private static SingleDemo s = null;// 共享数据,多个线程并发访问getInstance(),有可能存在安全问题,多条语句操作
- private SingleDemo() {// 私有构造函数
- }
- public static SingleDemo getInstance() {
- if (s == null) {
- synchronized (SingleDemo.class) {// 锁是字节码文件对象
- if (s == null) {
- s = new SingleDemo();// 对象延迟加载
- }
- }
- }
- return s;
- }
- }
(7)、线程死锁
两个对象互相依赖,所以死锁!示例代码如下:
- public class Test10 implements Runnable {
- public int flag = 1;
- static Object o1 = new Object(), o2 = new Object();//两个锁
- public void run() {
- System.out.println("flag=" + flag);
- //两个锁相持不下
- if (flag == 1) {
- synchronized (o1) {
- try {
- Thread.sleep(500);
- } catch (Exception e) {
- e.printStackTrace();
- }
- synchronized (o2) {
- System.out.println("1");
- }
- }
- }
- if (flag == 0) {
- synchronized (o2) {
- try {
- Thread.sleep(500);
- } catch (Exception e) {
- e.printStackTrace();
- }
- synchronized (o1) {
- System.out.println("0");
- }
- }
- }
- }
- public static void main(String[] args) {
- Test10 td1 = new Test10();
- Test10 td2 = new Test10();
- //定义标识
- td1.flag = 1;
- td2.flag = 0;
- //两个线程开启
- Thread t1 = new Thread(td1);
- Thread t2 = new Thread(td2);
- t1.start();
- t2.start();
- }
- }
四、线程间的通讯
(1)、概述:其实就是多个线程在操作同一个资源,但是操作的动作不同
程序示例:
- class Res {
- String name;
- String sex;
- boolean flag = false;// 标记是否有 资源
- }
- // 添加类,实现Runnable接口
- class Input implements Runnable {
- private Res r;// 资源对象
- public Input(Res r) {// 关联资源对象
- this.r = r;
- }
- // 重写run方法
- public void run() {
- // TODO Auto-generated method stub
- int x = 0;// 这里也必须要加线程,操作共享数据,同一个锁,可以是资源对象
- while (true) {
- synchronized (r) {
- if (r.flag) {
- try {
- r.wait();// 等待
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- if (x == 0) {
- r.name = "丽丽";
- r.sex = "女";
- } else {
- r.name = "mike";
- r.sex = "man";// 线程结束后,有可能还能抢到cpu执行权
- }
- x = (x + 1) % 2;
- r.flag = true;
- r.notify();// 唤醒线程池中的最早wait的线程。
- }
- }
- }
- }
- // 输出类实现Runnable接口
- class Output implements Runnable {
- private Res r;
- public Output(Res r) {// 关联资源对象
- this.r = r;
- }
- // 重写run方法
- public void run() {
- // TODO Auto-generated method stub
- while (true) {
- synchronized (r) {
- if (!r.flag) {
- try {
- r.wait();// 等待,取消了执行资格
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- System.out.println(r.name + "..." + r.sex);
- r.flag = false;
- r.notify();// 叫醒线程池中的最早线程
- }
- }
- }
- }
- public class Test {
- public static void main(String[] args) {
- Res r = new Res();
- // 形象的比喻 有一堆煤,有两个大卡车,一个进的,一个出的,把煤放到大卡车上,把卡车放到高速公路上。
- Input in = new Input(r);
- Output out = new Output(r);
- // 开启两个线程
- Thread t1 = new Thread(in);
- Thread t2 = new Thread(out);
- t1.start();
- t2.start();
- }
- }
(2)、分析问题:
a、以上的代码还是有问题的,按理说应该是存一个打印一个这样是比较靠谱的。上面的情况是一大片一大片的男,或者女。为什么出现这种情况?
输入的线程如果获得了cpu执行权,它存了一个值后,其他线程进不来,这个时候出了同步,output,input都有可能抢到cpu执行权。所以输入有可能还会抢到,前面的值就回被覆盖掉了。当某一时刻,执行权被抢走了,输出被抢到了,他也可能把一个值打印多遍,所以造成了上面的情况。cpu切换造成的。现在需求是这样的,添加一个,取出一个,这样才是最靠谱的。为了满足条件需求,我们要做的是,在资源中加入一个标记,默认false;输入线程在往里面添加数据时,判断标记,false则存入,存完后,输入线程可能还持有执行权,将标记改为真,代表里面有数据了。为true时,不能在存入了,这个时候,让输入线程等着不动,wait()放弃了执行资格;当取走了之后,才能醒,notify()。
当output具备执行权的时候,开始输出,之前也要进行判断,如果true,取出,打印,变为false还持有执行权,回来之后,为false,wati(),叫醒 input,input等的时候,再把output叫醒。等待唤醒机制。
wait();
notity();
notityAll();
都是用在同步中。因为要对持有监视器(锁)的线程操作。
所以要使用同步中,因为只有同步才具有锁。
b、为什么这些操作线程的方法要定义在Object中呢?
因为这些方法在操作同步线程时,都必须要标识它们所操作线程只有的锁。
只有同一个锁上的被等待线程,可以被同一个锁上notity唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
优化后的代码:
- class Res {
- private String name;
- private String sex;
- boolean flag = false;
- //设置添加方法
- public synchronized void set(String name, String sex) {
- if (flag) {
- try {
- this.wait();//线程等待,没有执行资格
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- this.name = name;
- this.sex = sex;
- flag = true;//有了数据,标识变为true;
- this.notify();//唤醒线程池中的最早wait的线程
- }
- //输出方法
- public synchronized void out() {
- if (!flag) {
- try {
- this.wait();//线程等待,没有执行资格
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- System.out.println(name + ".." + sex);//打印
- flag = false;//取走了数据,变为false;
- this.notify();//唤醒线程池中的最早wait的线程
- }
- }
- //添加类,input
- class Input implements Runnable {
- private Res r;
- public Input(Res r) {//关联资源
- this.r = r;
- }
- //重写run方法
- public void run() {
- // TODO Auto-generated method stub
- int x = 0;
- while (true) {
- if (x == 0) {
- r.set("mike", "man");
- } else {
- r.set("丽丽", "女");
- }
- x = (x + 1) % 2;
- }
- }
- }
- //输出类
- class Output implements Runnable {
- private Res r;
- public Output(Res r) {//关联资源
- this.r = r;
- }
- public void run() {
- // TODO Auto-generated method stub
- while (true) {
- r.out();
- }
- }
- }
- public class Test {
- public static void main(String[] args) {
- Res r = new Res();//资源对象
- //创建两个线程,并开启
- new Thread(new Input(r)).start();
- new Thread(new Output(r)).start();
- }
- }
五、Lock接口
解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。
到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。
在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。
所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。
在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。
而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。
这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。
< java.util.concurrent.locks > Condition接口:await()、signal()、signalAll();
成功的 lock 操作与成功的 Lock 操作具有同样的内存同步效应。
成功的 unlock 操作与成功的 Unlock 操作具有同样的内存同步效应
- 05_多线程
- 05_多线程
- _多线程
- 多线程_多线程理论
- 多线程_多线程总结
- C#_多线程_ 整理
- 04_多线程_死锁
- 第九章 Java多线程机制 05_线程同步_ 12_总结
- 【多线程】_认识多线程笔记
- 多线程(四)_多线程互动
- 【多线程】_认识多线程笔记
- Java基础_多线程
- 黑马程序员_多线程
- 黑马程序员_多线程
- 黑马程序员_多线程
- 黑马程序员_多线程
- 黑马程序员_多线程
- 黑马程序员_多线程
- 女程序媛与男程序猿的一天
- tomcat内存溢出
- mysql数据库的安装与使用
- 如何参看Mali系列GPU的使用率
- android 屏幕适配的方案
- 05_多线程
- eclipse从mybatis接口直接跳转到xml的插件
- 关于检查对象数组的值与属性判断
- Facets和Artifacts的区别
- laravel错误页面设置(推介自主上传项目报错页面)
- 1.基本概念
- 过滤器中注入spring中的bean
- 【广告】前端架构师独家揭秘:月薪30k难吗?
- C#反射