java multithreading - yaokun123/php-wiki GitHub Wiki

多线程

一、线程概述

进程直译:正在进行中的程序

线程

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

二、多线程好处与弊端

好处:解决了多部分同时运行的问题

弊端:线程太多会导致效率降低

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

三、JVM中多线程的解析

JVM启动时就启动了多个线程,至少有两个线程可以分析的出来。 1、执行main函数的线程 该线程的任务代码都定义在main函数中。 2、负责垃圾回收的线程

四、线程的创建方式-Thread类

创建新执行线程有两种方法:

第一种方法是将类声明为Thread的子类。该子类重写Thread的run方法

class Demo extend Thread{
    private String name;
    Demo(name){
        this.name = name;
    }

    public void run(){
        this.show();
    }
    public void show(){
        for(int x = 0;x<10;x++){
            System.out.println(name+"...x="+x);
        }
    }
}

class ThreadDemo{
    public static void main(){
        //创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行
        //而运行的指定代码就是这个执行路径的任务

        //jvm创建的主线程的任务都定义在了主函数中。
        //而自定义的线程的任务在哪呢?
        //Thread类用于描述线程,线程是需要任务的,所以Thread类也是对任务的描述
        //这恶搞任务就是通过Tnread的run方法体现,也就是说run方法就是封装自定义线程运行任务的函数。
        //run方法中定义的就是线程要运行的任务代码

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

        Demo d1 = new Demo("旺财");
        Demo d2 = new Demo("xiaoqiang");
        d1.start();//开启线程后,jvm会自动调用该线程的run方法
        d2.start();//不用自己手动调用run方法
    }
}

第二种方法是实现Runnable接口

class Demo implements Runnable{//准备扩展Deno类的功能,让其中的内容可以作为线程的任务执行,通过接口的形式完成
    private String name;
    Demo(name){
        this.name = name;
    }

    public void run(){
        this.show();
    }
    public void show(){
        for(int x = 0;x<10;x++){
            System.out.println(name+"...x="+x);
        }
    }
}


class ThreadDemo{
    public static void main(){
        Demo d1 = new Demo();//这时候不是线程对象,不能调用start
       

        Thread t1 = new Thread(d1);//线程对象
        Thread t2 = new Thread(d1);

        t1.start();
        t2.start();
    }
}

第二种方法的好处:

1、将线程的任务从线程的子类中分离出来,进行了单独的封装(按照面向对象的思想将任务封装成对象)

2、避免了java单继承的局限性

所以常见线程的第二种方式比较常用。

五、Thread类中的方法-线程名称

可以通过Thread的getName获取现场的名称 Thread-编号(从0开始)

class Demo extend Thread{
    private String name;
    Demo(name){
        this.name = name;
    }

    public void run(){
        this.show();
    }
    public void show(){
        for(int x = 0;x<10;x++){
            //线程以创建,名字就有了
            System.out.println(name+"...x="+x+"ThreadName="+this.getName());
            
            //获取当前运行线程的名字
            System.out.println(name+"...x="+x+"ThreadName="+Thread.currentThread().getName());
        }
    }
}

六、多线程运行内存图解

七、线程的四种状态

cpu的执行资格:可以背cpu处理,在处理队列中排队。

cpu的执行权:正在被cpu处理。

运行状态的线程具备cpu的执行资格和cpu的执行权

临时阻塞状态的线程具备cpu的执行资格但不具备cpu的执行权

冻结状态的线程不具备cpu的执行资格和cpu的执行权

八、卖票示例

class Ticket implements Runnable{
    private int num = 100;

    public void run(){
        while(true){
            if(num>0){
                System.out.println(Thread.currentThread().getName()+"...sale..."+num--)
            }
        }
    }
}

class TicketDemo{
    public static void main(String[] args){
        Ticket t = new Ticket();

        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 Ticket implements Runnable{
    private int num = 100;

    public void run(){
        while(true){
            if(num>0){
                try{//由于线程的安全问题,可能会导致负数票的出现
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
                }catch{}
            }
        }
    }
}

class TicketDemo{
    public static void main(String[] args){
        Ticket t = new Ticket();

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

线程安全问题产生的原因

1、多个线程在操作共享的数据。

2、操作共享数据的代码有多条。

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

十、解决多线程问题-》同步代码块

解决思路:就是将多条操作共享数据的线程封装起来,当有线程在执行这些代码的时候,其他线程是不可以参与运算的,必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

在java中同步代码块就可以解决这个问题

同步代码块的格式:

synchronized(对象){
    需要被同步的代码;
}

修改代码如下:

class Ticket implements Runnable{
    private int num = 100;
    Object obj = new Object();//创建一个对象为同步代码块使用

    public void run(){
        while(true){
            synchronized(obj){//同步代码块
                if(num>0){
                    try{
                        System.out.println(Thread.currentThread().getName()+"...sale..."+num--);
                    }catch{}
                }
            }
        }
    }
}

class TicketDemo{
    public static void main(String[] args){
        Ticket t = new Ticket();

        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 Back{
    private int sum;
    private Object obj = new Object();

    public void add(int num){
        synchronized(obj){//同步代码块
            sum = sum+num;
            try{Thread.sleep(10)}catch(InterruptedExpection e){}
            System.out.println("sum="+sum);
        }
    }


    //同步的第二种表现形式
    public void synchronized add(int num){//同步函数 (只需要用synchronized关键字修饰即可,不用使用锁)
            sum = sum+num;
            try{Thread.sleep(10)}catch(InterruptedExpection e){}
            System.out.println("sum="+sum);
    }
}

class Cus implements Runnable{
    private Bank b = new Bank();

    public void run(){
        for(int x=0;x<3;x++){
            b.add(100);
        }
    }
}

使用同步代码块时候的锁是自己new的一个对象,那么同步函数的锁是什么呢?(this)

建议使用同步代码块

静态同步函数用的锁就不是this了。当前的字节码对象(this.getClass()/类名.class)

十二、单例模式涉及的多线程问题

//饿汉式(不存在线程安全问题)
class Single{
    private static final Single s = new Single();
    private Single(){}
    public static Single getInstance(){
        return this.s;
    }
}

//懒汉式(存在安全问题需要解决)
class Single{
    private static Single s = null;
    private Single(){}
    public static Single getInstance(){
        if(s==null){
            s = new Single();
        }
        return this.s;
    }

    //解决线程安全

    public static Single getInstance(){
        if(s==null){
            synchronized(Single.class/this.getClass()){
                if(s==null){
                    s = new Single();
                }
            }
        }
        return this.s;
    }
}

十三、死锁

死锁的出现之一:同步的嵌套

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 lockb...");
                synchronized(MyLock.locka){
                    System.out.println("else locka...");
                }
            }
        }
    }
}

class MyLock{
    public static final Object locka = new Object();
    public static final Object lockb = new Object();
}

class DeadLockTest{
    public static void main(){
       Test a = new Test(true);
       Test b = new Test(false);
       
       Thread t1 = new Thread(a);
       Thread t2 = new Thread(b);

       t1.start();
       t2.start();
   }
}