转---黑马程序员-Java学习笔记(线程)

  1. 进程:是一个正在执行中的程序

    每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元

线程:就是进程中的一个独立的控制单元

    线程控制着进程的执行

    一个进程至少有一个线程

    Java VM启动的时候会有一个java.exe进程

    而且这个线程运行的代码存在于main方法中,称为主线程.

    同时运行的还有一个负责垃圾回收的线程

  1. 线程的创建

    第一种方法:继承Thread类

    步骤:

    1. 定义类继承Thread
    2. 复写Thread类中的run方法

      --目的:将自定义的代码存储在run方法中

    3. 调用线程的start方法

      该方法有两个作用:启动线程,调用run方法

      (如果直接使用run方法,则新线程未启动,按顺序执行(单线程))

      为什么要覆写run方法?

          --run方法用于存储线程要运行的代码

     

    同一时刻,单核CPU只能执行一个线程,如果有多个任务,则会在各个任务间切换;

多线程的一个特性:随机性,谁抢到谁执行

示例代码:

class ThreadTest extends Thread

{

    public void run() //覆写run 方法

    {

        for(int x =0;x<60;x++)

            System.out.println("Thread Run!"+x);

    }

}

class ThreadDemo

{

    public static void main(String[] args)

    {

        ThreadTest tt = new ThreadTest();

        tt.start();

        for(int x =0;x<60;x++)

            System.out.println("Main Run!"+x);

    }    

}

运行结果:

从结果中可以看出,主线程和tt线程是随机运行的,一直来回切换

 

    线程的状态

运行:

临时状态:阻塞

冻结:睡眠(sleep),等待(wait)

消亡:stop();

Thread(String name);//指定线程名称

Thread.currentThread();//获取当前运行线程

getName():获取线程名称

setName(String name);//设置线程名称

 

    创建线程的第二种方式:

声明实现Runnable接口的类

步骤:

  1. 定义类实现Runnable接口
  2. 覆盖Runnable接口中的run方法

    --将线程要运行的代码存放在run方法中

     

  3. 通过Thread类建立线程对象
  4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数

    --为什么要将Runnable接口的子类对象传递给Thread的构造函数?

    因为,自定义的run方法所属的对象是Runnable接口的子类对象

    所以要让线程去指定对象的run方法,就必须明确该run方法所属的对象

     

  5. 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法

 

实现方式和继承方式的区别

实现方式的好处:避免了单继承的局限性

建议使用实现方式

区别:

继承Thread:线程代码存放在Thread子类的run方法中

实现Runnable:线程代码存放在接口的子类的run方法中

简单使用:

class Ticket implements Runnable{

    void run(){};

}

Ticket t = new Ticket();

new Thread(t).start();

 

示例:

class SellTicket

{

    public static void main(String[] args)

    {

        /*

        Ticket t1 = new Ticket("t1");

        Ticket t2 = new Ticket("t2");

        Ticket t3 = new Ticket("t3");

        Ticket t4 = new Ticket("t4");

        */

        Ticket t = new Ticket();    //run方法所在的对象

        new Thread(t).start();    //创建一个新线程

        new Thread(t).start(); //创建一个新线程

        new Thread(t).start(); //创建一个新线程

        new Thread(t).start(); //创建一个新线程

    }

}

class Ticket implements Runnable

{

    private int ticket = 100;

    public void run(){        

        while(ticket > 0)

            System.out.println(Thread.currentThread().getName()+"::"+ticket--);

    }

}

运行结果:

 

 

线程的同步问题:

synchronized(对象)

//对象如同一把锁,持有锁的线程可以在同步中执行

//没有锁的线程,即使获取了CPU的执行权,也进不去,不能执行

{

    出现问题的代码块;//操作共享数据的内容

}

public void run(){    

        Object obj =new Object();

Synchronized(obj)//需要同步的代码块,obj相当于一把锁

//以下的代码块,操作了共享数据,因此需要同步

{

            while(ticket > 0)

                System.out.println(Thread.currentThread().getName()+"::"+ticket--);

}

    }

    同步的好处:解决了多线程的安全问题

    弊端:多个线程需要判断锁,比较消耗资源

同步问题示例2:

/*

多线程同步

提问:该程序是否有安全问题?如果有,如何解决?

 

如何找问题:

1.明确哪些代码是多线程运行代码

    --run方法中的代码,包括run方法调用的函数

2.明确共享数据

    --b,sum

3.明确多线程运行代码中哪些语句是操作共享数据的

    --操作sum和b的语句

 

*/

class Bank

{

    private int sum;

    public void add(int n)

    {

        sum+=n;

        try{

            Thread.sleep(10); //加了sleep后出现了同步问题

        }

        catch(Exception 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);

        }

    

    }

}

class BankDemo

{

    public static void main(String[] args)

    {

        Cus c = new Cus();

        new Thread(c).start();

        new Thread(c).start();

    }

}

 

运行结果出现异常:

 

同步代码块:

    Object obj = new Object();

    public void add(int n)

    {

        synchronized(obj)

        {

            sum+=n;

            try{

                Thread.sleep(10);//加了sleep后出现了同步问题

            }

            catch(Exception e){}

            System.out.println("Sum = "+sum);

        }

    }

运行结果:正常了,问题解决

 

也可以采用同步函数

    //Object obj = new Object();

    public synchronized void add(int n)

    {

        //synchronized(obj)

        {

            sum+=n;

            try{

                Thread.sleep(10);//加了sleep后出现了同步问题

            }

            catch(Exception e){}

            System.out.println("Sum = "+sum);

        }

    }

    注意:run方法不能同步!否则就算建立了多个线程,将只有一个线程运行run方法的内容

    可以将run方法内的同步内容,独立新建一个同步函数,再用run方法调用该同步函数

将run方法同步:

    public synchronized void run(){        

        while(ticket > 0)

            System.out.println(Thread.currentThread().getName()+"::"+ticket--);

    }

运行结果:

 

可以看到只有Thread 0在运行,Thread 1,2,3都没有运行

 

另外,如果将包含共享数据的循环判断条件包括到同步代码块里面,

则只有第一个抢到执行权的线程才能运行同步代码块里的内容,也降低了效率:

    Object obj = new Object(); //obj这个锁需要放在run方法的外面,否则每个线程运行时都会建立一个独立的锁,起不到锁的作用

    public void run(){            

        synchronized(obj)

        {

            while(ticket > 0)    //循环判断条件应该放在同步代码块外面,并且不应该包含共享数据

            

            {                

                try{Thread.sleep(10);}

                catch(Exception e){}

                System.out.println(Thread.currentThread().getName()+"::"+ticket--);

            }

        }

运行结果:

可以看到只有Thread-2线程运行了同步代码块的内容.

 

正确的做法:

需要将对共享数据的操作单独封装一个函数,并同时synchronized:

    public synchronized void ticketDemo(){

        if(ticket > 0)

        {

            try{

                Thread.sleep(10);

            }

            catch(Exception e){}

            System.out.println(Thread.currentThread().getName()+"::"+ticket--);

        }

    }

    public void run(){        

        while (true)    //循环判断条件不包括共享数据

        {

            ticketDemo();    

        }

}

运行结果:

可以看到Thread-2,Thread-3线程都参与了卖票,这样就提高了效率

 

同步的前提:

  1. 必须要有两个或者两个以上的线程
  2. 必须是多个线程使用同一个锁

同步函数的锁:

class SellTicket

{

    public static void main(String[] args)

    {

        Ticket t = new Ticket();

        new Thread(t).start();

        new Thread(t).start();

 

        try{Thread.sleep(10);}

        catch(Exception e){}

        t.flag = true;

        new Thread(t).start();

        new Thread(t).start();

    }

}

class Ticket implements Runnable

{

    private int ticket = 100;

    Object obj = new Object();

    boolean flag = false;

    public void run(){

        if(flag)

        {

            while(true)

                ticketDemo();

        }

        else

        {

            while (true)

            {                    

                synchronized(obj)

                //synchronized(this)

                {

                    if(ticket > 0)

                    {                        

                        try{Thread.sleep(10);}

                        catch(Exception e){}

                        System.out.println(Thread.currentThread().getName()+".......Code::"+ticket--);

                    }

                }                    

            }

        }            

    }    

    public synchronized void ticketDemo(){

        if(ticket > 0)

        {

            try{Thread.sleep(10);}

            catch(Exception e){}

            System.out.println(Thread.currentThread().getName()+".......Func::"+ticket--);

        }

    }

}

运行结果:

结果中出现了0号票,说明同步出现了问题!

说明第二个两个前提出现了问题,使用了不同的锁!

将同步代码块的锁,改成this

        //synchronized(obj)

        synchronized(this)

运行结果:

可以看到票打印到1就结束了,同步问题解决!

说明同步函数用的锁是this!

同步静态方法的锁是什么?

private static int ticket = 100;

public static synchronized void ticketDemo()

运行结果:

可以看到出现了安全问题.说明同步静态方法的锁并不是this.

将锁改为Ticket.class:

//synchronized(this)

synchronized(Ticket.class)

运行结果:

运行结果恢复正常

说明同步静态函数使用的锁是类名.class.所在该类的字节码文件对象

结论:同步函数被静态修饰后,同步锁不再是this,因为静态方法中也不可以定义this!

静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象

类名.class,该对象的类型是class

静态的同步方法,使用的锁就是该方法所在类的字节码文件对象.(类名.class)

单例设计模式:

饿汉式:

class Single{

    private static final Single s = new Single();

    private Single(){}

    public static Single getInstance(){

    return s;

}

}

懒汉式:

class Single{

    private static Single s = null;

    private Single(){}

    public static Single getInstance(){

        if(s == null)

            |-->线程A停在这里,等B执行完后,也会创建对象

            |-->线程B,会创建对象

            s = new Single();

    return s;

}

}

多线程访问时容易出现安全问题!

如果线程在创建s = new Single();之前停住的话,可能会在内存中创建多个实例对象;

可以将getInstance函数变成同步函数,但是这样的话效率比较低,因为每个线程都需要判断这个同步锁;

可以考虑将同步函数改为同步代码块,并采用双重判断的方法提高效率

class Single{

    private static Single s = null;

    private Single(){}

    public static Single getInstance(){

        |-->线程D,E,F…..以后的线程跑到这里就不会再往下跑了,不会再去判断锁,提高了效率

        if(s == null)

        {

            |-->线程C,第三个线程,由于没有锁,进不去同步代码块

|-->线程B,第二个线程,由于没有锁,进不去同步代码块

            synchronized(Single.class){

                |-->线程C如果跑到这里,s已经不再为null.不会再创建对象

                |-->线程B,这时线程A执行结束,s已经不再为null.不会再创建对象

            if(s == null)

                |-->线程A,第一个线程,如果暂时在这里sleep                

                s = new Single();

}

}

    return s;

}

}

面试问题:

饿汉式和懒汉式的区别?

懒汉式的特点在于实例的延时加载;

延时加载有没有问题?

有,如果多线程访问,会出现安全问题;

安全问题如何解决?

可以加同步来解决;

用同步函数和同步代码块的方式都可以

但是,同步函数比较低效,可以采用双重判断的同步代码块方式提高效率;

同步函数使用的锁是什么?

该函数所在类的字节码文件对象,类名.classs

 

同步的死锁问题:

同步中嵌套同步

 

class DeadLockTest

{

    public static void main(String[] args)

    {

        new Thread(new Test(true)).start();    //一个对象对应一个线程

        new Thread(new Test(false)).start();    //另外一个不同的对象,对应另外的线程

    }

}

class Test implements Runnable

{

    private boolean flag;

    Test(boolean flag)

    {

        this.flag = flag;

    }

    Object locka = new Object();    //不能用这个对象当锁,因为每个线程通过不同的对象创建,因此每个线程的locka都会不同

    Object lockb = new Object();

    public void run()

    {

        

        if(flag)

        {

            while(true)

            {

            synchronized(MyLock .locka)    //不同的线程对应同一个锁

            {

                System.out.println("if locka A");

                synchronized(MyLock.lockb)    //同步中嵌套同步

                {

                    System.out.println("if lockb B");

                }

            

            }

            }

        }

        else

        {

            while(true)

            {

            synchronized(MyLock.lockb)

            {

                System.out.println("else lockb B");

                synchronized(MyLock .locka)

                {

                    System.out.println("else locka A");

                }

            

            }

            }

        }

    

    }

 

}

class MyLock

{

    static Object locka = new Object();

    static Object lockb = new Object();

}

运行结果:

 

可以看到,程序停止在那里没法动了,锁住了

线程1需要线程2的锁,线程2需要线程1的锁,两边都在等对方释放锁,就这样停在那里动不了了

posted @ 2016-12-12 14:10  mayya  阅读(102)  评论(0)    收藏  举报