黑马程序员-Java多线程-day11

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

1、多线程概述

进程、线程、多线程存在的意义

多线程的创建方式、多线程的特性。

多线程中Thread类中的run方法,是用于存储线程要运行的代码。

1.进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行 路径,或者叫一个控制单元

2.线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。一个进程中至少有 一个线程。

3.Java VM 启动的时候会有一个进程java.exe。该进程中至少一个线程负责java程序的执行。

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

扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。

 

2、继承Thread类

1、如何让在自定义的代码自定义一个线程呢?

通过对API的查找,java已经提供了对线程这类事物的描述,就是Thread类。

创建线程的第一种方式:继承Thread类。

步骤:

1.定义类继承Thread;

2.复写Thread类中的run方法;目的:将自定义代码存储在run方法中,让线程运行

3.调用线程的start方法,该方法有两个作用:启动线程,调用run方法。

代码演示:

class Demo extends Thread
{
       public void run()
       {
              for(int x=0; x<60; x++)
                     System.out.println("demorun~~~~~~~"+x);
       }
}
 
class ThreadDemo
{
       public static void main(String[] args)
       {
              Demo d = new Demo();//创建好一个线程
              //d.start();//开启线程并执行该线程的run方法。
              d.run();//仅仅是对象调用方法,而线程创建了,却并没有运行。
 
              for(int x=0;x<60;x++)
                     System.out.println("HelloWorld!-----"+x);
       }
}

细节:

1.发现运行结果每一次都不同,因为多个线程都获取CPU的执行权,CPU执行到谁,谁就运行。明确一点,CPU在某一个时刻,只能有一个程序在运行。(多核除外)

2.CPU在做着快速的切换,以达到看上去是同时运行的效果。我们可以看作是多线程的运行行为在互相抢夺CPU执行权。

这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,CPU说了算。

 

3、创建线程run和start特点

1.为什么要覆盖run方法呢?

Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。

该存储功能就是run方法。也就是说Thread类中的run方法,用于存储线程要运行的代码

 

练习:创建两个线程,和主线程交替运行。

代码演示:

class Test extends Thread
{
       //private String name;
       Test(Stringname)
       {
              //this.name = name;
              super(name);
       }
       public void run()
       {
              for(int x=0; x<60; x++)
              {
                     System.out.println((Thread.currentThread()==this)+"..."+this.getName()+"  run....."+x);
              }
       }
}
 
class ThreadTest
{
       public static void main(String[] args)
       {
              Test t1 = new Test("one----");
              Test t2 = new Test("two++++");
              t1.start();
              t2.start();
 
              for(int x=0; x<60; x++)
              {
                     System.out.println("main....."+x);
              }
       }
}

小结:

1.原来线程都有自己默认的名称,Thread-编号,该编号从0开始。

2.static Thread currentThread():获取当前线程对象

3.设置线程名称:setName或者构造函数。


4、线程运行状态

1.线程运行的五种状态:

(1)被创建

(2)运行

(3)阻塞(临时状态),具备资格但没有执行权

(4)冻结,放弃执行资格

(5)消亡

注:建立线程子类对象的同时线程也被建立。

 

5、获取线程对象以及名称

1.两个属性:setName和getName

2.原来线程都有自己默认的名称,Thread-编号,该编号从0开始。

练习:创建两个线程,和主线程交替运行。

class Test extends Thread
{
       //private String name;
       Test(Stringname)
       {
              //this.name = name;
              super(name);
       }
       publicvoid run()
       {
              for(int x=0; x<60; x++)
              {
                     System.out.println((Thread.currentThread()==this)+"..."+this.getName()+"  run....."+x);
              }
       }
}
 
class ThreadTest
{
       public static void main(String[] args)
       {
              Test t1 = new Test("one----");
              Test t2 = new Test("two++++");
              t1.start();
              t2.start();
 
              for(int x=0; x<60; x++)
              {
                     System.out.println("main....."+x);
              }
       }
}

小结:

static Thread currentThread():获取当前线程对象

getName():获取线程名称

setName():设置线程名称:或者构造函数。

 

需求:简单的卖票程序。多个窗口同时卖票。

class Ticket extends Thread
{
       private  int tick =100;
       public void run()
       {
              while(true)
              {
                     if(tick>0)
                     {
                            System.out.println(Thread.currentThread().getName()+"   sale  "+tick--);
                     }
              }
       }
}
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();
              t1.start();
              t1.start();
       }
}

6、创建线程的第二种方式:实现Runnable接口

1、步骤:

1.定义类实现Runnable接口

2.覆盖Runnable接口中的run方法;将线程要运行的代码存放在该run方法中。

3.通过Thread类建立线程对象;

4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数

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

因为自定义的run方法所属的对戏那个是Runnable接口的子类对象,所以要让线程去    控制指定对象的run方法。就必须明确该run方法所属的对象。

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

 

2、实现方式和继承方式有什么不同?

实现方式的好处:避免了单继承的局限性;在定义线程时,建议使用实现方式。

 

3、两种方式的区别:

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

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

class Ticket implements Runnable//extendsThread
{
       private  int tick =100;
       public void run()
       {
              while(true)
              {
                     if(tick>0)
                     {
                            System.out.println(Thread.currentThread().getName()+"   sale  "+tick--);
                     }
              }
       }
}
class TicketDemo
{
       publicstatic 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();
       }
}
 

7、多线程的安全问题

代码演示:

class Ticket implements Runnable
{
       private int tick = 100;
       Object obj = new Object();
       public void run()
       {
              while(true)
              {
                     synchronized(obj)
                     {
                            if(tick>0)
                            {
                                   try{Thread.sleep(10);}catch(Exception e){}
                                   System.out.println(Thread.currentThread().getName()+"...sale:"+tick--);
                            }
                     }
              }
       }
}
class TicketDemo2
{
       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();
       }
}

通过分析,发现,打印出0,-1,-2等错票。多线程的运行出现了安全问题。

问题的原因:

       当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。

解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。

 

8、多线程中的同步代码块

Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块。

synchronized(对象)

{

       需要被同步的代码

}

同步代码块:哪些代码需要同步,就看哪些语句在操作共享数据

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

没有持有锁的线程即使获取了cpu执行权,也进不去,因为没有获取锁。

同步的前提:

1.必须要有两个或者两个以上的线程。

2.必须是多个线程使用同一个锁。

必须保证同步中只能有一个线程在运行。

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

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

 

9、多线程-同步函数

在复写run()方法的函数里,程序不能抛,因为Thread类中没有该方法

需求:银行有一个金库,有两个储户分别存300元,每次存100,存三次。

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

如何找问题:

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

2.明确共享数据。

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

同步代码块有两种表现形式:1.同步代码块;2.同步函数。

class Bank
{
       private int sum;
       //Object obj = new Object();
       public synchronized void add(int n)//第二种表现形式是同步函数
       {
              //synchronized(obj)//第一种表现形式是同步代码块
              //{
                     sum+=n;
                     try{Thread.sleep(10);}catch(Exceptione){}
                     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();
              Thread c1 = new Thread(c);
              Thread c2 = new Thread(c);
              c1.start();
              c2.start();
       }
}

10、多线程-同步函数的锁是this

同步函数用的是哪一个锁呢?

函数需要被对象调用,那么额函数都有一个所属对象引用,就是this。

所以同步函数使用的锁是this。

通过该程序进行验证。使用两个线程来卖票。

一个线程在同步代码块中,一个线程在同步函数中,都在执行卖票动作。

class Ticket implements Runnable
{
       private int tick =100;
       //Object obj = new Object();
       boolean flag = true;
       public void run()
       {
              if(flag)
              {
                     while(true)
                     {
                            synchronized(this)
                            {
                                   if(tick>0)
                                   {
                                          try{Thread.sleep(10);}catch(Exception e){}
                                          System.out.println(Thread.currentThread().getName()+"....code: "+tick--);
                                   }
                            }
                     }
              }
              else
                     while(true)
                            show();
       }
       public synchronized void show()//this
       {
              if(tick>0)
                     {
                            try{Thread.sleep(10);}catch(Exception e){}
                            System.out.println(Thread.currentThread().getName()+"...show...:"+tick--);
                     }
       }
}
class ThisLockDemo
{
       public static void main(String[] args)
       {
              Ticket t = new Ticket();
              Thread t1 = new Thread(t);
              Thread t2 = new Thread(t);
 
              t1.start();
              try{Thread.sleep(10);}catch(Exception e){}
              t.flag =false;
              t2.start();
       }
}

11、多线程-静态同步函数的锁是Class对象

如果同步函数被静态修饰后,使用的锁是什么呢?

通过验证,发现不再是this,因为静态方法中也不可以定义this。

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

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

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

class Ticket implements Runnable
{
       private static int tick =100;
       //Object obj = new Object();
       boolean flag = true;
       public void run()
       {
              if(flag)
              {
                     while(true)
                     {
                            synchronized(Ticket.class)
                            {
                                   if(tick>0)
                                   {
                                          try{Thread.sleep(10);}catch(Exception e){}
                                          System.out.println(Thread.currentThread().getName()+"....code: "+tick--);
                                   }
                            }
                     }
              }
              else
                     while(true)
                            show();
       }
       public static synchronized void show()//this
       {
              if(tick>0)
                     {
                            try{Thread.sleep(10);}catch(Exception e){}
                            System.out.println(Thread.currentThread().getName()+"...show...:"+tick--);
                     }
       }
}
class StaticMethodDemo
{
       public static void main(String[] args)
       {
              Ticket t = new Ticket();
              Thread t1 = new Thread(t);
              Thread t2 = new Thread(t);
 
              t1.start();
              try{Thread.sleep(10);}catch(Exceptione){}
              t.flag =false;
              t2.start();
       }
}

12、单例设计模式

//饿汉式
class Single
{
       private static final Single s = new Single();//让本类对象创建对象,加上final可以让代码显得更严谨
       private Single(){}//私有构造函数
       public static Single getInstance()//提供一个公共的访问方式
       {
              return s;
       }
}

//懒汉式

//懒汉式用于延时加载,多线程加载的时候容易出现问题,可以用同步代码块解决问题,但效率稍低;而加上双重判断的时候则可以提高效率,加同步的时候,该类所属的字节码对象:类名.class

class Single
{
       private static Single s = null;//让本类对象创建一个引用
       private Single(){}//私有构造函数
       public static Single getInstance()//通过双重判断,提供一个公共的访问方式
       {
              if(s==null)//双重判断,提高效率
              {
                     synchronized(Single.class)//给同步代码添加一把锁
                     {
                            if(s==null)
                                   s= new Single();
                     }
              }
              return s;
       }
}

小结:在懒汉式中,加入双重判断,可以提高程序运行效率;但是一般还是使用饿汉式,比较使用,但在面试中一般会面试到懒汉式。

例如:懒汉式和饿汉式有什么不同?

解答:懒汉式的特点在于实例的延时加载;懒汉式的延时加载有没有问题?有,多线程访问时会出现安全问题,可以加同步来解决,而用双重判断的方法可以解决效率问题;加同步的时候,使用的锁是哪个?该类所属的字节码对象

 

13、多线程-死锁

死锁,同步中嵌套同步,而锁却不同

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("iflockb");
                                  
                            }
                     }
              }
              else
              {
                     synchronized(MyLock.lockb)
                     {
                            System.out.println("elselockb");
                            synchronized(MyLock.locka)
                            {
                                   System.out.println("elselocka");
                                  
                            }
                     }
              }
       }
}
 
class MyLock
{
       static Object locka = new Object();
       static Object lockb = new Object();
}
class DeadLockTest
{
       public static void main(String[] args)
       {
              Thread t1 = new Thread(new Test(true));
              Thread t2 = new Thread(new Test(false));
              t1.start();
              t2.start();
       }
}

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------
详细请查看:http://edu.csdn.net
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值