黑马程序员——多线程


------- <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------


一、线程
(1)定义
   
    进程:正在进行中的程序(直译)。
    线程:进程中一个负责程序执行的控制单元(执行路径)。

    注:
        1、一个进程中可以有多个执行路径,称之为多线程。
        2、一个进程中至少要有一个线程。
        3、开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

(2)创建一个线程的两种方式(掌握)
     1、定义一个类继承Thread类
          public class A extends Thread{
          
          }
          new A().start();
     2、定义一个类实现Runnable接口,并且重写run()方法
          public class A implements Runnable{
               @Override
               public void run(){
                    
               }
          }
          new Thread(new A()).start();


(3)线程的随机性原理
     多个程序实际是通过CPU在做高效切换实现的


(4)线程的声明周期(掌握)
     新建 --> 就绪 --> 运行 -->阻塞 --> 死亡
     
     这里要注意,线程阻塞后就无法执行,回到就绪状态
(5)创建线程方式一:继承Thread类
    1.定义一个类继承Thread类。
    2.覆盖Thread类中的run方法。
    3.直接创建Thread的子类对象创建线程。
    4.调用start方法开启线程并调用线程的任务run方法执行。

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

二、卖票案例

(1)问题代码
 
public class TicketRunnable implements Runnable{

       private int tickets = 100;

       @Override

       public void run() {

              while(true){

                     if(tickets > 0){

                            try {

                                   Thread.sleep(1000);//必须加这个,否则不一定出现负数-----语句1

                            } catch (InterruptedException e) {

                                   e.printStackTrace();

                            }

                       System.out.println(Thread.currentThread().getName()+"正在卖第"+tickets--+"张的票");---语句2

                     }else{

                            break;//必须加这个,否则无法跳出循环,造成死机

                     }

              }

       }

}



(2)产生问题的原因
        比如现在只剩一张票了tickets=1,现在有两个线程,线程1和线程2
        线程1先执行,判断tickets >0,执行线程1的语句1,然后被休眠1s
        在这个时候线程2抢到了执行权,首先判断tickets>0,继续往下走,执行线程2的语句1,然后被休眠1秒
        在线程2休眠的时候,线程1醒了,执行语句1,然后线程1停止,这时候tickets=0
         线程2醒了,执行语句2,这时候tickets=-1

(3)如何查找问题
     1、看有没有共享数据
     2、看操作共享数据的语句是不是多条语句
     3、看是不是在多线程的环境中

     最后,把操作共享数据的多条语句用锁 锁起来
 
(4)改良后的代码
    修改后的代码/卖票案例完整代码
 
public class TicketRunnable implements Runnable {

       private int tickets = 50;

       private Object lock = new Object();

       @Override

       public void run() {

              while (true) {

       synchronized (lock) {
                            if (tickets > 0) {
                                 try {
                                          Thread.sleep(100);
                                  } catch (InterruptedException e) {
                                          e.printStackTrace();
                                   }
                                   System.out.println(Thread.currentThread().getName()+ "正在卖第" + tickets-- + "张的票");
                            } else {

                                   break;

                            }

                     }

              }

       }

}
 


(5)问题补充:
    为什么Object obj要加静态修饰?
    原因
          不加static修饰完全可以,但是在使用的时候需要注意创建多个线程时,传入的必须是同一个Runnable对象的实例,因为每个TicketThread对象中的obj的值是不一样的。这样锁就不一样,所以代码只能这么写:
    TicketThread runnable = new TicketThread();
    Thread t1 = new Thread(runnable);
    Thread t2 = new Thread(runnable);
       
    t1.start();
    t2.start();


    要是加static的话,即使创建多个TicketThread对象,每个对象共享obj这个变量,锁就是一样的,代码还可以这么写
    TicketThread runnable1 = new TicketThread();
    TicketThread runnable2 = new TicketThread();
         
    Thread t1 = new Thread(runnable1);
    Thread t2 = new Thread(runnable2);
       
    t1.start();
    t2.start();


三、结构
(1)同步代码块

     synchronized(锁对象){
          需要被锁的代码//线程只有拿到了锁对象,才能执行这里的代码!!!换言之,这里的代码如果执行了,说明该线程拿到了锁对象,其他线程不能拿到该锁对象
     }

     注意
          多个线程必须使用同一个锁对象,要不然锁无效


(2)同步方法(掌握)
     public synchronized void show(){}               //普通方法的锁是this
     public static synchronized void show(){}    //静态方法的锁是当前类的字节码文件对象 类名.class


(3)注意问题(掌握)
     多个线程必须使用同一个锁对象,要不然锁无效
     同步代码块锁可以是任意对象
     同步方法的锁是this
     静态方法的锁是当前类的字节码文件对象 类名.class 


(4)什么时候用同步代码块,什么时候用同步方法
     尽可能用同步代码块
     如果一个方法内的所有代码都被同步代码块包住了,那就用同步方法就可以了


(5)死锁

死锁原因总结
     线程1自身拿着一个锁:A锁,线程2自身拿着一个锁:B锁
     当线程1要用B锁,线程B要用A锁的时候就会发生死锁


线程1
 
package sisuo;
public class Thread1 extends Thread {

       @Override

       public void run() {

              synchronized (Lock.LOCK_A) {

                     System.out.println("我是线程1,已经拿到A锁,将要去哪B锁");

                     synchronized (Lock.LOCK_B) {

                            System.out.println("我是线程1,成功拿到B锁");

                     }

              }

       }

}


 
线程2
 
package sisuo;

 

public class Thread2 extends Thread {

       @Override

       public void run() {

              synchronized (Lock.LOCK_B) {

                     System.out.println("我是线程2,已经拿到B锁,将要去哪A锁");

                     synchronized (Lock.LOCK_A) {

                            System.out.println("我是线程2,成功拿到A锁");

                     }

              }

       }

}


 
锁对象
 
package sisuo;

 

public class Lock {

       public static final  Object LOCK_A = new Object();

       public static final  Object LOCK_B = new Object();

}


 
 
测试代码
 
package sisuo;

 

public class Test {

       public static void main(String[] args) {

              Thread1 t1= new Thread1();

              Thread2 t2= new Thread2();

              t1.start();

              t2.start();

             

       }

}




四、多个线程操作同一数据的问题(线程键通讯问题)

线程键通讯:(掌握)
     其实就是多个线程同时操作同一个对象
     卖票案例就是线程间通讯问题


问题代码(因为没有对操作共享数据的代码加入同步代码块)
共享数据(学生类)
 
package tongxin;
public class Student {

       public String name;

       public int age;

 

       @Override

       public String toString() {

              return "Student [name=" + name + ", age=" + age + "]";

       }

}



线程1:负责修改共享数据
 
package tongxin;
public class SetThread  extends Thread{

       private Student stu;

       public SetThread(Student stu){

              this.stu = stu;

       }

      

      

       @Override

       public void run() {

              int i=0;

              while(true){

                     if(i%2 ==0){//执行%2操作,是为了写入不同的数据,测试在写入过程中,是否影响另一个线程的读取操作

                            stu.name = "张三";

                            stu.age = 13;

                     }else{

                            stu.name = "李四";

                            stu.age = 14;

                     }

                     i++;

              }

       }

}

 


 
线程2:负责获取共享数据信息
 
package tongxin;
public class GetThread extends Thread {

       private Student stu;

       public GetThread(Student stu){

              this.stu = stu;

       }

       @Override

       public void run() {

              while(true){

                     System.out.println(stu);

              }

       }

}


 
 
测试
 
package tongxin;

 

public class Test {

       public static void main(String[] args) {

              //创建共享数据

              Student stu = new Student();

              //创建两个线程,并且让这两个线程同时操作这个共享数据

              GetThread get = new GetThread(stu);

              SetThread set = new SetThread(stu);

             

              get.start();

              set.start();

       }

}


(1)问题产生原理分析
     注意:每个线程有个普通的成员变量Student,创建线程的时候需要先初始化该变量
(2)解决方案
     1、看有没有共享数据
     2、看操作共享数据的语句是不是多条语句
     3、看是不是在多线程的环境中
     最后,把操作共享数据的多条语句用锁 锁起来
所以给两个线程while(true){}中间的内容用加锁
修改线程1和线程2中的代码,做到必须修改完才能读取


五、等待唤醒机制
(1)前提(掌握)
     两个线程共用一把锁,此时可以调用该锁的wait和notify方法,实现等待唤醒机制
(2)IllegalMonitorStateException异常原因及解决办法
     如果当前的线程不是此对象锁的所有者,缺调用该对象的notify(),notify(),wait()方法时抛出该异常
     换句话说就是当前线程中的同步代码块的锁 和 调用这三个方法的锁对象不一致就会报错,例如
     synchronized(Student.class){
          Object.class.notify();
     }
     注意 必须有线程现在自食用Object.class锁
(3)sleep和wait的区别
wait:是Object类的方法,可以不用传递参数,释放锁对象
sleep:是Thread类的静态方法,需要传递参数
(4)以下代码有没有问题
public synchronized void set(String name, int age) {
          if (this.flag) {
               try {
                    Object.class.wait();
               } catch (Exception e) {
               }
          }
          this.name = name;
          this.age = age;
          this.flag = true;
          Object.class.notify();
 }
继续修改代码,做到读一次,写一次
共享数据(学生类)
 
package tongxin;
 
public class Student {
       public String name;
       public int age;
     
       public boolean flag = false;
 
       @Override
       public String toString() {
              return "Student [name=" + name + ", age=" + age + "]";
       }
}

六、线程的优先级(Thread类中)
(1)线程优先级级别
     线程默认优先级是5。范围是1-10
     Thread.MAX_PRIORITY         //10
     Thread.MIN_PRIORITY         //1
     Thread.NORM_PRIORITY     //5
(2)方法
     public final int getPriority():获取线程优先级
     public final void setPriority(int newPriority):更改线程的优先级
(3)注意
     优先级可以在一定的程度上,让线程获较多的执行机会
(4)举例
     MyThread t = new MyThread();
     System.out.println(t.getPriority());
     t.setPriority(Thread.MAX_PRIORITY);
七、暂停线程(Thread类中)
(1)概述
     暂停当前正在执行的线程,让其他线程执行
(2)成员方法
     public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
(3)注意
     是为了让线程更和谐一些的运行,但是你不要依赖这个方法保证,如果要真正的实现数据依次输出,请使用等待唤醒机制
八、加入线程(Thread类中)
(1)概念
     如果调用一个线程的join方法,那么其他线程必须等待该线程执行完毕后才能执行    
(2)成员方法
     public final void join():等待该线程终止
     线程启动后调用该方法
九、守护线程(Thread类中)
(1)成员方法
     public final void setDaemon(boolean on):设置线程为守护线程,一旦前台(主线程),结束,守护线程就结束了
(2)注意
     main方法就本身是一个线程,我们在main方法里创建线程,并且设置线程为守护线程后,main方法结束后,守护线程就自动结束了

 
------- <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------


 



黑马程序员多线程练习题主要包括两个问题。第一个问题是如何控制四个线程在打印log之前能够同时开始等待1秒钟。一种解决思路是在线程的run方法中调用parseLog方法,并使用Thread.sleep方法让线程等待1秒钟。另一种解决思路是使用线程池,将线程数量固定为4个,并将每个调用parseLog方法的语句封装为一个Runnable对象,然后提交到线程池中。这样可以实现一秒钟打印4行日志,4秒钟打印16条日志的需求。 第二个问题是如何修改代码,使得几个线程调用TestDo.doSome(key, value)方法时,如果传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果。一种解决方法是使用synchronized关键字来实现线程的互斥排队输出。通过给TestDo.doSome方法添加synchronized关键字,可以确保同一时间只有一个线程能够执行该方法,从而实现线程的互斥输出。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [黑马程序员——多线程10:多线程相关练习](https://blog.csdn.net/axr1985lazy/article/details/48186039)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值