Java多线程

多线程

应用程序的执行都是cpu在做着快速的切换完成的。这个切换是随机的

1、进程

直译:正在进行中的程序

一个程序就是一个进程,而一个程序中的多个任务则被称为线程,进程其实就是一个静态的概念

2、线程(控制单元/执行路径)

  • 就是进程中一个负责程序执行的控制单元(执行路径)
  • 一个线程中可以执行多个路径,称之为多线程
  • \color{blue}{一个进程中至少有一个线程}
  • 开启多线程是为了同时运行多部分代码,每一个线程都有自己运行的内容。这个内容可以称为线程要执行的任务

3、多线程存在的利弊

  • 多线程的好处:解决了多部分同时运行的问题
  • 多线程的弊端:线程太多会使运行效率的降低

4、JVM中的多线程解析

JVM虚拟机的启动时本身就是多线程,至少有两个可以分析出来

  1. 执行main函数的线程

    该线程的任务代码都定义在main函数中

  2. 负责垃圾回收的线程

    该线程的任务代码都在垃圾回收器中

垃圾回收器实际上就是垃圾回收程序,可以通过系统System类中中的gc()方法唤醒调用

class Demo extends Object{
    public void finalize(){
        System.out.println("demo ok");
    }
}
class ThreadDemo{
    public static void main(){
        new Demo();
        new Demo();
        System.gc();
        new Demo();
        System.out.println("hello zimo!");
      }
}
//  >: hello zimo!
//  >: demo ok
//  >: demo ok

5、创建线程

创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行,运行的指定代码就是这个执行路径的任务。

所以开启线程是为了运行指定代码,只有继承Thread类,并复写run方法,将运行的代码定义在run方法中即可。

jvm创建的主线程的任务都定义在了主函数中,而自定义的线程任务运行在哪?

    Thread类用于描述线程,线程是需要任务的,所以Thread类也有对任务的描述。这个任务就是通过Thread类中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数。

run方法就是定义在线程要运行的任务代码。

1、创建线程方式一:继承Thread类

  1. 定义一个类继承Thread类
  2. 覆盖Thread类中的run方法
  3. 直接创建Thread的子类对象创建线程
  4. 调用start方法开启线程并调用线程的任务run方法执行

多线程实现两个对象同时运行实例:

class Demo extends Thread{
    private String name;
    Demo(String name){
        // super(name);   // 给线程起个名
        this.name = name;
    }
    public run(){
        show();
    }
    public void show(){
        for(int i = 0; i < 20; i++){
      System.out.println(name + "....." + i + getName());
        }
    }
}
class ThreadDemo{
    public static void main(){
        Demo d1 = new Demo("zimo");
        Demo d2 = new Demo("mozi");
        // d1.run();    
        // d2.run();
        d1.start();   // 开启线程,调用run方法
        d2.start();
        System.out.println("hello zimo!");
      }
}
  • 可以通过Thread的getName()获取线程的名称 Thread - 编号(从0开始)

  • 获取当前运行线程名称 Thread.currentThread().getName() 获取线程名称

    主线程的名称:main

2、创建线程方式二:实现Runnable接口

  1. 定义类实现Runnable接口

  2. 覆盖接口中的run()方法,将线程的任务代码封装到run()方法中

  3. 通过Thread类创建线程对象,并将Runnable接口的子类对象作为构造函数的参数进行传递

    因为线程的任务都封装在Runnable接口子类对象的run()方法中,所以要在线程对象创建时就必须明确要运行的任务

  4. 调用线程对象的start()方法开启线程

如果该类已经继承了一个父类,想扩展功能为多线程,可以通过接口的形式完成

它的出现仅仅是将线程的任务进行了对象的封装

class Demo extends FuDemo implements Runnable{
    private String name;
    Demo(String name){
        // super(name);   // 给线程起个名
        this.name = name;
    }
    // 覆盖接口中的run方法
    public void run(){
        show();
    }
    public void show(){
        for(int i = 0; i < 20; i++){
      System.out.println(name + "....." + i + getName());
        }
    }
}
class ThreadDemo{
    public static void main(){
        Demo d = new Demo();
        Thread t1 = new Thread(d);
        Thread t2 = new Thread(d);
        t1.start();   // 开启线程,调用run方法
        t2.start();
        System.out.println("hello zimo!");
      }
}

实现Runnable接口的好处:

  1. 将线程的任务从线程的子类中分离出来,进行了单独的封装

    按照面向对象的思想将任务封装成了对象

  2. 避免Java单继承的局限性,所以第二种常用

6、线程的四种状态

  • CPU的执行资格:可以被CPU处理到,在处理的队列中排队
  • CPU的执行权:正在被CPU进行处理
线程的四种状态.png

sleep方法需要指定睡眠时间,单位是毫秒。

一个特殊的状态:就绪。具备了执行资格,但是还没有获取资源

多线程示例:卖票

class Ticket extends Thread{
    private static int num = 100; // 如果不用静态的 他就会每个线程有独立的100张票
    public void run(){
        while(num > 0){
            System.out.println(Thread.currentThread().getName() + "..sale.."+num--);
        }
    }
}
class Ticket1 implements Runnable{
    private int num = 100;  
    public void run(){
        while(num > 0){
            System.out.println(Thread.currentThread().getName() + "..sale.."+num--);
        }
    }
}
class TicketDemo{
    public static void main(String[] args){
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        Ticket t3 = new Ticket();
        Ticket t4 = new Ticket();
        t1.start();
        // t1.start();    // 多次启动会抛出异常
        t2.start();
        t3.start();
        t4.start();
        
        Ticket1 t = new Ticket1();    // 创建一个线程任务对象
        Ticket1 tt1 = new Ticket1(t);
        Ticket1 tt2 = new Ticket1(t);
        Ticket1 tt3 = new Ticket1(t);
        Ticket1 tt4 = new Ticket1(t);
        tt1.start();
        tt2.start();
        tt3.start();
        tt4.start();
    }
}
多线程卖票内存分析图.png

7、线程安全问题

  • 导致产生线程安全的原因:
    1. 多个线程在操作共享的数据
    2. 操作共享数据的线程代码有多条
    3. 当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算
  • 解决方法:
    • Java中使用同步代码块:synchronized(对象){需要被同步的代码块}
    • 同步函数:pubilc synchronized void add(int num){}
  • 同步锁使用前提:同步中必须有多个线程,并且使用同一个锁
  • 同步的好处:\color{blue}{解决了安全问题}>;
  • 同步的弊端:\color{red}{相对降低了效利,因为同步外的线程都会判断同步锁}
class Ticket implements Runnable{
    private int num = 100;
    Object obj = new Object();
    public void run(){
        // Object obj = new Object(); //假设所在方法里,每个线程都有自己单独的锁,那么还是存在问题
        while(num > 0){
            synchronized(obj){
            // synchronized(new Object()){  // err 相当于一个线程有一个独立的对象    
              System.out.println(Thread.currentThread().getName() + "..sale.."+num--);
            }
        }
    }
}

class TicketDemo{
    public static void main(String[] args){
        Ticket t = new Ticket();    // 创建一个线程任务对象
        Ticket tt1 = new Ticket(t);
        Ticket tt2 = new Ticket(t);
        Ticket tt3 = new Ticket(t);
        Ticket tt4 = new Ticket(t);
        tt1.start();
        tt2.start();
        tt3.start();
        tt4.start();
    }
}

8、线程同步函数示例

// 需求:两个储户,每个都到同一家银行,每次100,共三次
class Bank{
    private int sumMoney;
    // private Object obj = new Object();
    public synchronized void add(int money){  // 同步函数
        // synchronized(obj){
            sum += sum;
            try {Thread.sleep(10);}catch(InterruptedException e){}
            System.out.println("sum = " + sum);
        // }
    }
}
class Cus implements Runnable{
    private Bank b = new Bank();
    for(int i = 0; i < 3; i++){
        b.add(100);
    }
}
class CusBankDemo{
    public static void main(String[] args){
        Cus c = new Cus();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        t1.start()
    }
}
  • 验证同步函数的锁

    同步函数锁使用的是this对象

class Ticket implements Runnable{
    private int num = 100;
    // Object obj = new Object();
    public void run(){
        if(flag){
            while(true){
                // synchronized(obj){ // 如果是obj,那么线程代码块和线程函数的锁不是同意把,还是存在安全问题
              synchronized(this){
                    System.out.println("this:" + this);
                    if(num > 0){
                        System.out.println(Thread.currentThread().getName() + "..sale.."+num--);
                    }
                }
            }
        }else
            while(true)
                show();
    }
    public synchronized void show(){
    // public static synchronized void show(){    // 静态代码块没有this对象,只有一个getClass获取的当前class字节码所属的对象
                        // 同步代码块可以通过传synchronized(this.getClass()){}来实现同步
                               // 获取字节码文件对象还可以通过类型.class: Ticket.class
        System.out.println(Thread.currentThread().getName() + "..sale.."+num--);
    }
}

class TicketDemo{
    public static void main(String[] args){
        Ticket t = new Ticket();    // 创建一个线程任务对象
        System.out.println("t:" + t);
        Ticket tt1 = new Ticket(t);
        Ticket tt2 = new Ticket(t);
        tt1.start();
        tt2.start();

    }
}
  • 同步函数和同步代码块的区别是:

    • 同步函数的锁是固定的this

    • 同步代码块的锁是任意的对象

    • 静态的同步函数使用的锁是,该函数所属字节码文件,该同步锁对象不是this

      可以通过getClass()方法获取,也可以用当前类名.class表示

  • 建议使用同步代码块

9、多线程下的单例模式

  • 饿汉式(单例模式)
class Single{
    private static final Single s = new Single();     // 1.固定不变,一开始就被创建
    private Single(){}
    public static Single getInstance(){
        return s;           // 2.返回地址,不存在线程安全问题
    }
}
  • 懒汉式(延迟加载单例模式)
class Single{
    private static final Single s = null; // 共享数据
    private Single(){}
    
    public static Single getInstance(){
  // 方法一:虽然解决了问题,但是每次进来都要判断锁,效率低
  // public static synchronized Single getInstance(){   
        
        // 方法二:同步代码块,这样写还是和同步方法没区别,一进来就要判断锁
        synchronized(Single.class){     // 这边不能使用getClass()方法,是非静态的
            
            if(s == null){  
                // 可能存在线程切换同时进入这里
              s = new Single(); // 产生多个对象,不能保证唯一性
          }
        }
        return s;
    }
    // 改良,通过双重判断解决懒汉式的 线程安全问题 和 效率问题
    public static Single getInstance(){
        if(s == null){
            synchronized(Single.class){ 
              if(s == null){  
                s = new Single();
            }
          }
        }
        
        return s;
    }
}

10、死锁示例

死锁:常见的情景之一:同步的嵌套

class Ticket implements Runnable{
    private int num = 100;
    private boolean flag = true;
    Object obj = new Object();
    public void run(){
        if(flag){
            while(true){
                synchronized(obj){  // obj -->> this
                   show();
                }
            }
        }else
            while(true)
                show();
    }
    public synchronized void show(){  // this -->> obj
        synchronized(obj){
            System.out.println(Thread.currentThread().getName() + "..sale.."+num--);
        }
    }
}

class DeadLockDemo{
    public static void main(String[] args){
        Ticket t = new Ticket();    // 创建一个线程任务对象
        Ticket t1 = new Ticket(t);
        Ticket t2 = new Ticket(t);
        t.flag = false;
        t1.start();
        t2.start();

    }
}
  • 手动实现死锁示例:遵循原则--嵌套
class Test implements Runnable{
    private boolean flag;
    Test(boolean flag){
        this.flag = flag;
    }
    public void run{
        if(flag){
            synchronized(MyLock.locka){
                system.out.println("if-locka");
                synchronized(MyLock.lockb){
                    system.out.println("if-lockb");
                }
            }
        }else{
            synchronized(MyLock.lockb){
                system.out.println("else-locka");
                synchronized(MyLock.locka){
                    system.out.println("else-lockb");
                }
            }
        }
    }
}
class MyLock{
    public static final Object locka = new Object();
    public static final Object lockb = new Object();
}
class DeadLockTest{
    public static void main(String[] args){
        Test a = new Test(true);
        Test b = new Test(false);
        Thread t1 = new Thread(a);
        Thread t2 = new Thread(b);
        t1.start();
        t2.start();
        System.out.println("hello zimo!");
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容