3.1多线程基础

1.进程:正在进行中的 程序(直译)(任务管理器中的都是进程)
分配程序的空间,不执行。

2.线程:就是进程中的一个负责程序执行的控制单元(执行路径)
一个进程中可以有多个执行路径,称之为多线程。
一个进程中至少要有一个线程。

开启多个线程是为了同时运行多个代码。
每一个线程都有自己运行的内容。这个内容称为线程要执行的任务。

其实应用程序的运行都是CPU在做着快速切换完成的。这个切换是随机的。
管理进程的运行:中央处理器CPU,快速切换执行的进程,看起来是同时执行。
(多核(CPU)电脑可以提高多进程运行速度,但是还是得看内存大小。)

2.1
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:开多了效率低。

2.2 JVM中的多线程解析:
①主线程:肯定有一条线程在执行main函数。
该线程的任务代码都定义在main函数中。
(主线程结束时,虚拟机不一定结束,还有其他线程)
②垃圾回收线程:还有一条线程在执行垃圾回收器。(其实每个对象都具备着被回收的方法,在Object.finalize(),这个方法由对象的垃圾回收器调用此方法,垃圾回收器在System.gc()静态方法)
该线程的任务代码定义在垃圾回收器中。
/*//让对象被回收,但是不是立即
class Demo
{
public void finalize() //其实没必要覆写
{
System.out.println(“demo ok”);
}
}

class ThreadDemo
{
public static void main(String[] args) //这是一个线程
{
new Demo();
new Demo();
System.gc(); //不是立即执行,这又是另一个线程,执行具有不确定性。
new Demo();
System.out.println(“HAHA”);
}
}
*/

等等。

2.3主线程运行示例:
主线程运行示例

3.多线程存在的意义

4.线程的创建方式
1)创建方式一:继承Thread类
步骤:

1.定义一个类继承Thread类。
2.覆盖Thead类中的run方法:
    自定义的线程的任务通过Thread类中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数。
    run方法中定义的就是线程要运行的任务代码。

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

3.直接创建Thread类的子类对象。
4.调用start方法开启线程并调用线程的任务run方法执行。

调用run和调用start的区别:

run:调用要在线程中执行的任务(代码)。
start:开启线程并调用线程的任务run方法执行。

代码:

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

    public void run()
    {
        int[] arr = new int[3];
        System.out.println(arr[3]);   发生异常,线程终止

        show();
    }

    public void show()
        {
            for (int x = 0;x < 30 ;x++ )
            {
            //  for (long  y = 0; y <= 1000000000l;y++ ){}   //long类型延迟比int明显
                System.out.println(name+ "........x=" + x +".....name=" + Thread.currentThread().getName());   //getName()获取线程的名字(Thread-数字(从0开始))(但线程可能没有运行)。
    //要获取运行时线程的名字,就得先通过静态方法Thread.currentThread()获得对当前正在执行的线程对象的引用。
            }
    }

}



class ThreadDemo2 
{
    public static void main(String[] args) 
    {
        Demo d1 =new Demo("旺财");   //创建对象的时候就已经完成了线程名称的定义,所以.run()也会输出线程名称。
        /*  源码:  public Thread() {
                  init(null, null, "Thread-" + nextThreadNum(), 0);
              }
           */

        Demo d2 = new Demo("xiaoqiang");

        d1.run();       //调用要在线程中执行的任务(代码),和正常调用没区别。
                       //这时返回的运行时线程名是  main
//          d1.start();    //创建并启动线程。(使该线程开始执行;Java 虚拟机调用该线程的 run 方法。) 
            d2.start();
            System.out.println(4/0);     //发生算数异常,线程终止
            System.out.println("over");   //主线程,这时一共由三个线程,随机执行

    }
}

Thread类中的方法和名称:

方法:
1.getName():获取线程的名字(Thread-数字)(从0开始)(但线程可能没有运行)。
2.Thread.currentThread():静态方法,获得对当前正在执行的线程对象的引用。

线程的名称:
1.主函数的线程名为main
2.线程名自定义:
    在子类的初始化函数中第一行添加super(name),name为自定义线程名。其实就是调用了Thread类的构造方法。

5.多线程运行图解:

1.线程之间独立运行,都有自己的运行空间,相互不影响。
2.线程发生异常立即跳栈,不再执行,不影响其他线程的执行。
主线程挂了,其他线程也不会立即结束,除非虚拟机关闭。

多线程运行图解

6.线程的状态:

CPU的执行资格:可以被CPU处理,在处理队列中排队
CPU的执行权:正在被CPU处理

线程的状态

7.线程创建的第二种方式:实现Runnable接口
步骤:

1.定义类实现Runnable接口。
2.覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为构造函数的参数进行传递。
    为什么:因为线程的任务都封装在Runnable接口的子类对象的run方法中,
    所以要在线程对象创建时就必须明确要运行的任务。

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

原因:

创建方式一,继承Thread类。当需要线程的类中已经继承了其他类,就不能再继承Thread类了。
这个时候还要用到额外的方法创建就需要使用接口。而Thread类已经实现了Runnable接口,它默认run方法就是覆盖的Runnable接口的run抽象方法,不执行任何操作。并且Thread类提供了可以带Runnable接口对象的构造方法,来分配新的 Thread 对象,调用新对象的run方法。

内部实现的思想细节:
class Thread implements Runnable
{
    private Runnable r;
    Thread()
    {
    }
    Thread(Runnable r) //第二种创建方式
    {                //在新的Thread对象中覆盖Runnable的run方法。

        this.r = r;
    }
    public void run()
    {
      if(r!=null)
        r.run();
    }
    public void start()
    {
        run();
    }
}


class SubThread extends Thread
{                       //第一种创建方式
    public void run()    //覆盖Thread中的run()
    {
    }
}
SubThread s =new SubThread();
s.start();   //调用的是父类的start()和子类覆盖的run()



class ThreadImpl implements Runnable
{
    public void run();   //第二种创建方式
}
Thread t = new Thread(new ThreadImpl());
t.start();


API说明:
Thread
public Thread(Runnable target)
分配新的 Thread 对象。这种构造方法与 Thread(null, target,gname) 具有相同的作用,其中的 gname 是一个新生成的名称。自动生成的名称的形式为 “Thread-”+n,其中的 n 为整数。

代码:

class Demo  implements Runnable
{   
        public void run()
    {
            show();
        }
        void show()
    {
                for (int x = 0; x <20 ;x++ )
                {
                    System.out.println(Thread.currentThread().getName() + "......"  + x);
                }
        }

}


class ThreadDemo3 
{
    public static void main(String[] args) 
    {

        Thread t1 = new Thread(new Demo());   //Thread类是在构造方法中定义的是Runable接口中自己覆盖的run方法,
                                                                                            //即类Thread实现了Runable接口
                /*API解释:
                run
                    public void run()
                                如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 
                                Thread 的子类应该重写该方法。
                */

                                                                                        //所以赋值一个实现Runable接口的子类,调用的就是子类覆盖的的run方法。
        Thread t2 = new Thread(new Demo());
        t1.start();    //开启线程并调用线程中的run方法执行
        t2.start();
    }
}

第二种方式的好处:

第一种方式,继承了Thread,会变成Thread体系中的一员,具备了Thread类中的所有方法。但是用类描述事物完了后,如果仅仅只是需要将一部分代码被对线程所操作的话,就没有必要去具备线程对象中的所有方法。做继承的目的仅仅是为了覆盖run方法,建立线程运行的任务。

所以有了第二种方式,实现Runnable接口,它的出现仅仅是将线程的任务进行了对象的封装,不需要线程出现。如果需要有多线程,就去实现Runnable接口,把任务代码封装在run方法中,变成Runnable接口的子类对象,就是线程任务对象。(这是一种思想的变化)
Runnable r = new Student();

要运行多线程时,直接创建Thread对象,在创建对象的同时,明确线程任务对象就行了。
Thread t = new Thread(r);
t.start();

Thread类和其他需要多线程的类中都存在run()方法,向上抽取,而线程是事物的一个额外功能,所以抽取并实现了Runnable接口。

实现Runnable的好处:

1.将线程的任务从线程的子类中分离出来,进行了单独的封装。
    按照面向对象的思想将任务封装成对象。
2.避免了Java单继承的局限性。

所以,创建线程的第二种方式较为常见。

8.多线程小例子:

/*
卖票示例:

需求:四个窗口同时卖票

问题:
1.票数共用  :  就只能使用一个对象,而继承方式需要创建四个对象,所以用接口定义方式创建线程。

2.保证每次买票过程能完成:

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

在Java中,用同步代码块姐可以解决这个问题。

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



多次启用一个线程是非法的。
*/

class Ticket implements Runnable  // extends Thread  //继承无法实现同时卖100张票完成。
{
    private  int num = 100;   //静态化后,对象就没有意义。而且要卖另外不同的100张票呢。
    Object obj = new Object(); //保证用的是同一个锁

    public void run()
    {
    //  Object obj = new Object(); //这样会有4把锁,同步会失效
        sale();
    }

    public void sale()      //不能抛出异常,因为实现的接口没有抛
    {
        while(true)
        {

//同步代码块
            synchronized(obj)
            {
            if(num >0)
                {
/*              try
                {
                        Thread.sleep(10);            //线程安全问题测试
                }
                catch (InterruptedException e)
                {
                }
*/              
                System.out.println(Thread.currentThread().getName() + ":余票:"+ --num);
                }
                }


        }
    }
}

class  TicketDemo
{
    public static void main(String[] args) 
    {
        Ticket t = new Ticket();     //创建一个线程任务对象,火车票
        Ticket tt = new Ticket();     //卖第二种票,动车票
        Thread t1 = new Thread(t,"窗口1(火车票)"); 
        Thread t2 = new Thread(t,"窗口2(火车票)");
        Thread t3 = new Thread(tt,"窗口3(动车票)");  //窗口3,4  买动车票
        Thread t4 = new Thread(tt,"窗口4(动车票)");  

        t1.start();
        t2.start();
        t3.start();
        t4.start();

//      t1.start();
//      t1.start();   //这里会导致 主线程 发生线程状态异常。
//      t1.start();
//      t1.start();

    }
}

问题分析:
线程安全问题的现象:

if(num >0)
System.out.println...

当num = 1 的时候:
第一个线程刚刚进去if循环准备执行输出语句的时候,cpu不调用它了,使其进入临时阻塞状态。
然后cpu调用线程二进来执行输出语句,输出1,再--,0。
这时cpu回来调用线程一,这个时候不需要判断直接执行了输出语句,输出0,再--,num的值为 -1。显然0号票不合理。

产生安全问题的原因:

(因为num是共享的数据,多个线程操作时相互之间可能有影响。)

1.多个线程在操作共享数据。
(如果只有一个语句,不会出事。
两条以上可能导致问题,因为可能在结束一条语句时,其他线程进来了,使当前线程暂停进入临时阻塞状态。而等其他线程操作完,再回来执行该线程的第二条语句时,共享数据可能就已经发生了改变。)
2.操作共享数据的线程代码有多条。

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

解决问题:同步代码块

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

在Java中,用同步代码块姐可以解决这个问题。

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

同步的好处和弊端:

原理:在同步代码块中,当第一个线程进来时,获取obj对象,开始执行代码。这时没有obj对象,其他线程无法进来,只有等到当前进程结束后四方obj对象。obj对象像个锁,就叫对象锁(同步锁)。另外当前线程是不会一直有执行权的,只是切其他线程时它们进不来,只有再次切到当前线程,直到当前线程执行结束。

好处:解决了线程的安全问题。
弊端:相对降低了效率,因为同步外的线程都会判断同步锁。

同步的前提:

同步中必须有多个线程,并使用的是同一个锁。

9.同步函数:同步的第二种表现形式
在函数声明中添加修饰符synchronized即可。

/*
需求:储户,两个,每个都到银行存钱,每次存100,共存三次。

*/

class Bank
{
    private int sum;     //共享数据
//  private Object obj = new Object();  //唯一对象锁

    public synchronized void add(int num)
    {

    //  synchronized(obj){       //这就是函数的全部代码,直接定义同步函数

            sum = sum +num;
            //这里会出现安全隐患。
            System.out.println("sum = " + sum);     

    //}
    }
}

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


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

    }
}

class BankDemo 
{
    public static void main(String[] args) 
    {
        Cus c = new Cus();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);

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

验证同步函数的锁:

验证结果是不是同一个锁,即同步函数锁用的不是obj。

而是调用该方法的对象,this

注意:
但是当同步函数设置成静态的时候,对象锁就是这个方法所属类的字节码对象。getClass()可以获取该对象所属类的的字节码对象,而字节码对象是Class类的对象。
就是同步代码块中写this.getClass()可以获得和静态同步函数一样的同步锁。

另一种表示方式:类名.class;也是返回所属类的字节码对象。class是Class类的一个静态属性,而所有的类在加载时都是以Class类的对象形式加载的。

API中说明:
getClass:
返回的 Class 对象是由所表示类的 static synchronized 方法锁定的对象。 


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

    同步函数的锁是固定的this。
    同步代码块的锁是任意的对象(自定义,也不一定是obj)。

开发建议使用同步代码块。同步函数可以写成同步代码块的简写形式。一简写,就有前提(如果用的锁是this),有好处,有弊端。

验证代码:

/*
同步函数锁验证:
为了验证同步函数和同步代码块的锁,
窗口一设置同步函数,
窗口二设置同步代码块,如果是同一个对象锁,则不会输出负值。  

结果是不是同一个锁,即同步函数锁用的不是obj。

而是调用该方法的对象,this

*/

class Ticket implements Runnable  // extends Thread  //继承无法实现同时卖100张票完成。
{
    private  int num = 100;   //静态化后,对象就没有意义。而且要卖另外不同的100张票呢。
    private Object obj = new Object();         //验证用
     boolean flag = true;

    public void run()
    {
        sale();
    }


//  public synchronized void sale()  
        public void sale()  
    {

                if(flag)
                    {
                        while(true)          //同步函数sale后,第一个线程进来就出不去了,着是死循环。所以不应该同步sale函数。
                        {                                   //解决方法:将死循环里的代码封装成同步函数即可。
                            show();     //窗口一设置同步函数
                        }
                    }


                else                   //窗口二设置同步代码块,如果是同一个对象锁,则不会输出负值。
                {
                    while(true)
                    {
                    synchronized(obj)   //改成synchronized(this),就不会出现错误值
                             //如果同步函数是静态的,则改成synchronized(this.getClass()),通过getClass()获得本类的 static synchronized 方法锁定的对象。 
                                                 //或者synchronized(Ticket.class),等效

                    {
                        if(num >0)
                        {
                            try{Thread.sleep(10); }catch (InterruptedException e){}  //线程安全问题测试

                            System.out.println(Thread.currentThread().getName() + ":余票(obj):"+ --num);
                            }
                        }
                    }
            }       }       



    private synchronized void show()    //同步锁不是obj,是this,即new Ticket()。
                                    //如果改成静态的,则同步锁对象就是所属类的字节码对象。
    {
        if(num >0)
            {
                try{Thread.sleep(10); }catch (InterruptedException e){}  //线程安全问题测试

                System.out.println(Thread.currentThread().getName() + ":余票:(function)"+ --num);
            }
    }
}


class  SynFunctionLockDemo
{
    public static void main(String[] args) 
    {
        Ticket t = new Ticket();     //创建一个线程任务对象,火车票

        Thread t1 = new Thread(t,"窗口1");   //为了验证同步函数和同步代码块的锁。设置同步函数
        Thread t2 = new Thread(t,"窗口2");//设置同步代码块

        t1.start();   
//这里为了让线程一能够有完成 flag =true的赋值动作,让主线程暂停一下。
        try{Thread.sleep(100); }catch (InterruptedException e){}     //线程安全问题测试
        t.flag = false;
        t2.start();

    }
}

10.单例模式涉及的多线程问题。

关于懒汉式单例设计模式在多线程中的
面试问题:
1.安全么?   不安全
2.写个同步函数,效率高么?同步锁是哪一个?怎么解决?
不高,因为每次获取对象都要判断同步锁。
静态同步函数的同步锁是所在类的字节码对象,可以通过类名.class来获取。
解决办法:通过同步代码块把创建对象的代码封装起来,再放到对象是否为空的判断里,这样就只用判断一次同步锁,提高效率。
/*
多线程下的单例:

*/

//饿汉式
class Single
{
        private Single(){}
        private static Single s = new Single();
        public  static void getInstance()
        {
            return s;    //一句话,不存在安全隐患。
        }
}

//懒汉式
class Single
{
    private Single(){}
    private static Single s =null;
    public static/* synchronized*/ getInstance()   //解决办法1:加同步函数,但是每次拿对象都要判断同步锁,效率低。
    {
        if(s==null)    //解决办法2:通过同步代码块把创建对象的代码封装起来,再放到判断里,这样就只用判断一次同步锁。
            {                                                               //加判断是解决效率问题
                    synchronized(Single.class)   //加同步是解决安全问题      
                    {
                    if(s == null)
                              //线程0进来,切换到线程1,线程1暂停,再切到线程0,建立返回一个对象,
                                //然后切换到线程1,不用判断,又建立一个对象。此时无法保证对象的唯一性了。存在安全隐患。
                        s = new Single();

                    }       
            }
            reruen s;
    }
}


class  SingleDemo  
{
    public static void main(String[] args) 
    {
        System.out.println("Hello World!");
    }
}

11.死锁示例:

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

*/
class Ticket implements Runnable  
{
    private  int num = 100;  
    private Object obj = new Object();      
    boolean flag = true;

    public void run()
    {
        sale();
    }


        public void sale()  
    {

                if(flag)
                    {
                        while(true)         
                        {       
                            synchronized(obj)
                            {
                                show();     
                            }
                        }
                    }


                else           
                {
                    while(true)
                    {

                        show();   //线程二拿着this锁进obj锁,同时发生导致程序死锁
                    }
            }   

            }       



    private  synchronized void show()  //线程一拿着obj锁进this锁,同时发生导致程序死锁
    {       
        synchronized(obj)   
                {
                        if(num >0)
                            {
                            try{Thread.sleep(10); }catch (InterruptedException e){}

                            System.out.println(Thread.currentThread().getName() + ":余票(obj):"+ --num);
                            }
                    }   


    }
}


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

        Thread t1 = new Thread(t,"窗口1");  
        Thread t2 = new Thread(t,"窗口2");

        t1.start();   

        try{Thread.sleep(100); }catch (InterruptedException e){}    
        t.flag = false;
        t2.start();

    }
}

死锁程序:面试用

class Test implements Runnable
{
    private boolean flag;
    Test(boolean flag)
    {
        this.flag = flag;
    }

    public void run()
    {

                if(flag)
                {
                    while(true)     //增加死锁概率
                    {
                            synchronized(MyLock.locka)
                            {
                                System.out.println(Thread.currentThread().getName()+ "...if         locka...");
                                    synchronized(MyLock.lockb)
                                    {
                                        System.out.println(Thread.currentThread().getName()+"...if          lockb...");
                                    }
                            }
                        }
                    }


                else
                {
                    while(true)
                    {
                            synchronized(MyLock.lockb)                      
                            {
                                System.out.println(Thread.currentThread().getName()+"...else            lockb...");
                                    synchronized(MyLock.locka)
                                    {
                                        System.out.println(Thread.currentThread().getName()+"...else            locka...");
                                    }
                            }
                    }   
            }
    }
}

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);     //因为是boolean型变量,值是固定的 。所以多个内容没有影响
            Test b = new Test(false);

            Thread t1 = new Thread(a);
            Thread t2 = new Thread(b);

            t1.start();
            t2.start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值