Day05-多线程


title: Day05-多线程
date: 2020-05-30 17:12:47
author: 子陌


多线程

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

1、进程

直译:正在进行中的程序

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

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

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

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进行处理
    线程的四种状态

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();
    }
}

多线程卖票内存分析图

7、线程安全问题

  • 导致产生线程安全的原因:
    1. 多个线程在操作共享的数据
    2. 操作共享数据的线程代码有多条
    3. 当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算
  • 解决方法:
    • Java中使用同步代码块:synchronized(对象){需要被同步的代码块}
    • 同步函数:pubilc synchronized void add(int num){}
  • 同步锁使用前提:同步中必须有多个线程,并且使用同一个锁
  • 同步的好处:解决了安全问题>;
  • 同步的弊端:相对降低了效利,因为同步外的线程都会判断同步锁
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!");
    }
}

猜你喜欢

转载自blog.csdn.net/qq_38205875/article/details/107813569