多线程学习笔记(一)

1、在java中要实现多线程,有两种手段,一种是继承Thread类,另外一种是实现Runable接口
1)继承了Thread类
public class HelloThread extends Thread{
 private String name;
 public HelloThread(String name) {
  this.name = name;
 }
 
 @Override
 public void run() {
  for(int i = 0; i < 5; i++)
  System.out.println( name + "运行第 " + i + " 次");
 }
 
 public static void main(String[] arags) {
  HelloThread h1 = new HelloThread("线程A");
  HelloThread h2 = new HelloThread("线程B");
  h1.start();
  h2.start();
 }
}

2)实现了Runnable接口
public class HelloRunnable implements Runnable{
 private String name;
 
    public HelloRunnable(String name) {
        this.name = name;
    }

 @Override
 public void run() {
  for(int i = 0; i < 5; i++)
   System.out.println( name + "运行第 " + i + " 次");
 }
 
 public static void main(String[] args) {
  HelloRunnable h1 = new HelloRunnable("线程A");
  Thread t1 = new Thread(h1);
  HelloRunnable h2 = new HelloRunnable("线程B");
  Thread t2 = new Thread(h2);
  t1.start();
  t2.start();
 }
}

Thread其实也是实现了Runnable接口
class Thread implements Runnable {
    //…
public void run() {
        if (target != null) {
             target.run();
        }
        }
}

Thread和Runnable的区别:
如果一个类继承Thread,则不适合资源共享,但用Runnable接口的类就可以实现资源共享。同时避免了单继承,所以推荐使用Runnable作为多线程的开启方法
public class MyThread implements Runnable{
 //多个线程处理一个对象可实现资源共享
 private int ticket = 5;

 @Override
 public void run() {
  for(int i = 0; i <= 20; i++) {
   if(ticket > 0) {
    System.out.println(Thread.currentThread().getName() + "正在售票" +ticket--);
   }
  }
 }
 
 public static void main(String[] args) {
  MyThread my = new MyThread();
  new Thread(my, "一号窗口").start();
  new Thread(my, "二号窗口").start();
  new Thread(my, "三号窗口").start();
 }
}

2、线程使用的方法有:
1)isAlive 判断线程是否还在运行
public static void main(String[] args) {
  Thread t1 = new Thread();
  System.out.println("线程启动前: " + t1.isAlive());
  t1.start();
  System.out.println("线程启动后: " + t1.isAlive());
 }

2)join 在当前线程中调用另外一个线程的join方法,则会阻塞当前运行的线程直到调用join方法的线程执行完毕再继续执行,换句话说,如果调用join方法的线程没有执行完,则当前线程会一直等待,但其它无关线程不受阻碍。
    一般是用于使用子线程执行耗时操作,当要使用子线程中的结果时调用该方法把结果执行完毕再往下执行。
public static void main(String[] argss) {
  Thread t1 = new Thread(new Runnable() {
   @Override
   public void run() {
    try {
     for(int i = 0; i < 3; i++) {
      System.out.println("子线程: " +Thread.currentThread().getName());
      Thread.sleep(1000); //sleep 1 秒
     }
     
    } catch (InterruptedException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
   }
  });
  //开启线程
  t1.start();
  for(int i = 0; i < 50; i++) {
   if(i > 8) {
    try {
     t1.join();  //当大于8时把t1线程执行完再往下执行
    } catch (InterruptedException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    }
   }
   System.out.println("主线程执行到第 " + i + "次");
  }
 }

结果:


可以看到当线程达到第8次时会先把子线程执行完在往下执行,你自己可以尝试一下。

3)wait 释放锁对象并等待,如果等待对象没有被锁住,则抛出异常IllegalMonitorStateException,换句话说,需要使用对象的wait方法,则需要在synchronized 语句块或方法体内使用。wait方法不是线程的方法,而是每一个对象的方法。所以针对的是访问该对象的当前线程的阻塞。

①该对象没有被当前线程锁住,则调用wait方法报错:
public static void main(String[] argss) {
  Object object =new Object();
  try {
   object.wait();
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
 }


②使用synchronized语句块
 public static void main(String[] argss) {
  Object object =new Object();
  synchronized (object) {
   try {
    object.wait(); //阻塞访问当前对象的线程,即主线程,所以下面这句话永远不会打印出来
    System.out.println("当前线程为: " + Thread.currentThread().getName());
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }

4)notify 唤醒在等待该对象锁的线程,每次只唤醒一个线程,同时不能控制唤醒的是哪一个线程。一般与wait互用
    我们还是使用上一个例子,但是因为当前线程已被阻塞,我想你不会在当前线程中使用objecty.notify来调用吧。我们需要另外一个线程中唤醒被阻塞的线程。

 //将对象变成静态类变量实现两个线程之间共享
 static Object object =new Object();
 public static void main(String[] argss) {
  //使用t1对主线程进行唤醒操作
  Thread t1 = new Thread(new Runnable() {
   @Override
   public void run() {
    while(true) {
     //因为不知道几时进行阻塞状态,所以需要不断循环
     System.out.println("子线程运行中...");
     
     synchronized (object) {
      object.notify(); //唤醒主线程操作
      System.out.println("唤醒后我会不会继续操作。。"); //调用notify之后不会阻塞当前线程,其它线程会等待当前线程执行完再争夺对象锁
     }
     
     try {
      Thread.sleep(1000);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
     
    }
   
   }
  });
  t1.start(); //开启子线程
  synchronized (object) {
   try {
    System.out.println("主线程进行wait等待中");
    object.wait(); //阻塞访问当前对象的线程,即主线程
   
    System.out.println("当前线程为: " + Thread.currentThread().getName());
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }

结果:


如果操作的对象不是同一个,wait和notify也就没什么意义了。

5)synchronized关键字 用于保证同一时刻最多只有一个线程执行该代码段,用于锁定方法或者代码块

①synchronized方法
    如: public synchronized void accessVal(int newVal); 即创建该方法的实例并调用改方法时,同时刻只会有一个线程进行操作,锁对象是该方法的实例。 如果是静态类方法,则获得的是类锁,类锁不是把所有该类对象实例都加锁,而是单单只是类信息,即Object.class对象。 如: public synchronized static void accessVal(int newVal);

②synchronized快
    上面代码中也有用到,即synchronized(要加锁的对象),可以使用synchronized(this)锁住当前对象实例,或者锁住你需要的共享对象。用法比较简单。

6)interrupt 通过抛出一个中断信号,让线程获得InterruptException,从而退出阻塞状态,但不会中断一个正在运行的线程 。 如用Object.wait Thread.join 和 Thread.sleep阻塞,都可以使用interrupt方法提前退出阻塞。(个人觉得比较少用)

①.sleep() & interrupt()
 public static void main(String[] args) {
  //这里有两个线程threadA和threadB,将threadA调用sleep方法,并在threadB中调用threadA.interrupt()方法触发异常。
  final Thread threadA = new Thread(new Runnable() {
   public void run() {
    try {
     System.out.println("进入sleep状态");
     Thread.sleep(100000);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    System.out.println("捕获Exception后运行的一行。。");
   }
  });
 
  Thread threadB = new Thread(new Runnable() {
   public void run() {
    threadA.interrupt();
   }
  });
 
  threadA.start();
  //避免那么快执行完,先sleep一下
  try {
   Thread.sleep(100);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  threadB.start();
 }

结果:


②join() & interrupt()
interrupt的调用者一定是当前被阻塞的线程,同时这个被阻塞的线程由其他线程调用。这样才能发挥功效
public static void main(String[] args) {
  //这里有三个线程。
  //线程A会在i = 8时调用线程C的join方法,即自己被阻塞,让线程C方法运行完再继续运行
  //线程B会调用线程A的interrupt方法提前退出join方法
  final Thread threadC = new Thread(new Runnable() {
   public void run() {
    for(int i = 0; i < 10; i++) {
     System.out.println("ThreadC 线程执行第: " + i + "次" );
     try {
      Thread.sleep(100);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   }
  });
 
  final Thread threadA = new Thread(new Runnable() {
   public void run() {
    for(int i = 0; i < 30; i++) {
     if(i == 8) {
      try {
       threadC.join();
      } catch (InterruptedException e) {
       e.printStackTrace();
      }
     }
     System.out.println("ThreadA 线程执行第: " + i + "次" );
    }
   }
  });
 
  Thread threadB = new Thread(new Runnable() {
   public void run() {
    while(true) {
     try {
      Thread.sleep(500);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
     threadA.interrupt();
    }
     
   }
  });
  threadA.start();
  threadB.start();
  threadC.start();
 
 }

结果:


③wait() & interrupt() 
 public static void main(String[] argss) {
  final Object object = new Object();
  final Thread threadA = new Thread(new Runnable() {
   @Override
   public void run() {
   
     System.out.println("线程A开始执行...");
     
     synchronized (object) {
      try {
       object.wait();
      } catch (InterruptedException e) {
       e.printStackTrace();
      }
      System.out.println("线程A继续执行。。。");
     }
   
   
   }
  });
  threadA.start(); //开启子线程
 
  Thread threadB = new Thread(new Runnable() {
   @Override
   public void run() {
    System.out.println("线程B开始执行");
    //如果太快是会先打印出线程A继续执行。。而不是线程B开始执行
    try {
     Thread.sleep(100);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    threadA.interrupt();
   }
  });
  threadB.start();
 }
}

7) Thread.yield() 暂停线程的执行,给其它具有相同优先权的线程执行的集合,若没有其它线程执行,则线程继续执行。创建一个线程默认优先级为5,可以使用setPriority进行设置优先级。

    补充:线程有4个状态
    新建状态New 线程处于新建状态,还没调用start方法
    就绪状态Runnable:线程对象创建后,其它线程调用了该对象的start方法,该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
    运行状态Running:就绪状态的线程获得了CPU,执行程序代码。
    阻塞状态 Blocked:阻塞状态时线程因为某种原因放弃CPU使用权,暂时停止运行。需要重新进入就绪状态,等待获得CPU使用时间片
        (一)等待阻塞:运行的线程执行wait方法,JVM会把线程放入等待池
        (二)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入池中
        (三)其它阻塞:运行的线程执行sleep或join方法,或者发出了I/0请求时,JVM会把线程设为阻塞状态,当之前方法完毕之后,线程重新转入就绪状态。
    死亡状态Dead:线程执行完或者异常退出,则该线程结束生命周期

8)suspend和resume。因为suspend容易发生死锁,调用suspend方法时,目标线程会停下来,但仍会拥有之前获得的锁。所以不建议使用。


















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值