`
holdbelief
  • 浏览: 697137 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

第十六课时: 多线程

阅读更多

一. 继承 Thread 类创建线程

通过继承 Thread 类来创建并启动多线程的步骤如下:

 

  • 定义 Thread 类的子类, 并重写该类的 run 方法, 该 run 方法的方法体就是代表了线程需要完成的任务
  • 创建 Thread 类的实例, 即创建了线程对象
  • 用线程对象的 start 方法来启动线程.
示例:
public class FirstThread extends Thread
{
private int i;
// 重写 run 方法, run 方法的方法体就是线程执行体
public void run()
{
for (; i < 100; i++)
{
// 当线程类继承 Thread 类时, 可以直接调用 getName() 方法来返回当前线程的名字
// 如果想获得当前线程, 直接点用 this 即可
System.out.println(this.getName());
}
}

public static void main(String[] args)
{
for(int i = 0; i < 100; i++)
{
// 调用 Thread 的 currentThread 方法获取当前线程
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20)
{
// 创建并启动第一条线程
new FirstThread().start();
// 创建并启动第二条线程
new FirstThread().start();
}
}
}
}
二. 实现 Runnable 接口创建线程类
实现 Runnable 接口来创建并启动多条线程的步骤如下:
  • 定义 Runnable 接口的实现类, 并重写该接口的 run 方法, 该 run 方法的方法体同样是该线程的线程执行体
  • 创建 Runnable 实现类的实例, 并以此实例作为 Thread 的 target 来创建 Thread 对象, 该 Thread 对象才是真正的线程对象
示例:
// 创建 Runnable 实现类的对象
SecondThread st = new SecondThread();
// 以 Runnable 实现类的对象作为 Thread 的 target 来创建 Thread 对象, 即线程对象
new Thread(st);
// 创建 Thread 对象时指定 target 和新线程的名字
new Thread(st, "新线程1");
  • 调用线程对象的 start 方法来启动该线程
示例:
//通过实现Runnable接口来创建线程类
public class SecondThread implements Runnable
{
private int i ;
//run方法同样是线程执行体
public void run()
{
for ( ; i < 100 ; i++ )
{
//当线程类实现Runnable接口时,
//如果想获取当前线程,只能用Thread.currentThread()方法。
System.out.println(Thread.currentThread().getName() + "  " + i);
}
}
    public static void main(String[] args) 
    {
        for (int i = 0; i < 100;  i++)
        {
System.out.println(Thread.currentThread().getName() + "  " + i);
if (i == 20)
{
SecondThread st = new SecondThread();
//通过new Thread(target , name)方法创建新线程
new Thread(st , "新线程1").start();
new Thread(st , "新线程2").start();
}
        }
    }
}
三. 线程的生命周期
当线程被创建并启动以后, 它既不是一启动就进入了执行状态, 也不是一直处于执行状态, 在县城的生命周期中, 它要经过新建 (New), 就绪 (Runnable), 运行 (Running), 阻塞 (Blocked) 和死亡 (Dead) 五种状态. 尤其是当线程启动以后, 它不能一直 "霸占" 着 CPU 独自运行, 所以 CPU 需要在多条线程之间切换, 于是线程状态也会多次在运行, 阻塞之间切换.
1. 新建和就绪状态
当程序使用 new 关键字创建了一个线程后, 该线程就处于新建状态.
当线程对象调用了 start() 方法之后, 该线程处于就绪状态, Java 虚拟机会为其创建方法调用栈和程序计数器, 出于这个状态中的线程并没有开始运行, 它只是表示该线程可以运行了, 至于该线程何时运行, 取决于 JVM 里线程调度器的调度.
不要对已经处于启动状态的线程再次调用 start 方法, 否则将引发 IllegalThreadStarteException 异常.
2. 运行和阻塞状态
如果处于就绪状态的线程获得了 CPU, 开始执行 run 方法的线程执行体, 则该线程处于运行状态.
当发生如下情况下, 线程将会进入阻塞状态:
  • 线程调用 sleep 方法主动放弃所占用的处理器资源
  • 线程调用了一个阻塞式 IO 方法, 在该方法返回之前, 该线程被阻塞
  • 线程试图获得一个同步监视器, 但该同步监视器正被其它线程所持有, 
  • 线程正在等待某个通知 (notify)
  • 程序调用了线程的 suspend 方法将该线程挂起, 不过这个方法容易导致死锁, 所以程序应该尽量避免使用该方法
当前正在执行的线程被阻塞之后, 其它线程就可以获得执行的机会了. 被阻塞的线程会在何时时候重新进入就绪状态, 注意是就绪状态而不是运行状态. 也就是说被阻塞线程的阻塞解除后, 必须重新等待调度器再次调度他.
针对上面的几种情况, 当发生如下特定情况将可以解除上面的阻塞, 让线程重新进入就绪状态:
  • 调用 sleep 方法的线程经过了指定的时间
  • 线程调用的阻塞式 IO 方法已经返回
  • 线程成功地获取了视图取得同步监视器
  • 线程正在等待某个通知时, 其他线程发出了一个通知
  • 处于挂起状态的线程被调用了 resume 恢复方法
3. 线程的死亡
线程会以以下三种方式之一结束, 结束之后就处于死亡状态
  • run() 方法执行完成, 线程正常结束
  • 线程抛出一个未捕获的 Exception 或 Error
  • 直接调用该线程的 stop() 方法来结束该线程-----该方法容易导致死锁, 通常不推荐使用
注意当主线程结束时, 其他线程不受影响, 并不会随之结束, 一旦子线程启动起来后, 它就拥有了和主线程相同的地位, 他不会受主线程的影响
为了测试某条线程是否已经死亡, 可以调用该线程对象的 isAlive() 方法, 当线程处于就需, 运行, 阻塞三种状态时, 该方法返回 true, 当线程处于新建, 死亡两种状态时, 该方法返回 false
不要试图对一个已经死亡的线程调用 start() 方法使他重新启动, 死亡就是死亡, 该线程将不可在此作为线程执行.
如果对已经死亡的线程执行 start() 方法将引发 IllegalThreadStateException 异常, 并且不能对新建状态的的线程两次调用 start() 方法.
四. 控制线程
1、join 线程
Thread 提供了让一个线程等待另一个线程完成的方法: join() 方法. 当某个程序执行流中调用其它线程的 join() 方法时, 调用线程将被阻塞, 知道被 join 方法加入的 join 线程完成为止.
join() 方法通常由使用线程的程序调用, 已将大问题划分成许多小问题, 每个小问题分配一个线程, 当所有小问题都得到处理后, 再调用主线程来进一步操作.
示例:
public class JoinThread extends Thread
{
// 提供一个有参数的构造器, 用于设置线程的名字
public JoinThread(String name)
{
super(name);
}

// 重写 run 方法, 定义线程执行体
public void run()
{
for(int i = 0; i < 100; i++)
{
System.out.println(getName() + " " + i);
}
}

public static void main(String[] args) throws Exception
{
// 启动子线程
new JoinThread("新线程").start();
for(int i = 0; i < 100; i++)
{
if (i == 20)
{
JoinThread jt = new JoinThread("被 Join 的线程");
jt.start();
// main 线程调用了 jt 线程的 join 方法,
// main 线程必须等待 jt 线程执行结束了才会向下执行
jt.join();
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
join 方法有三种重载形式:
  • join(): 等待被 join 的线程执行完成
  • join(long millis): 等待被 join 的线程的时间最长为 millis 毫秒, 如果在 millis 毫秒内, 被 join 的线程还没有执行结束则不再等待
  • join(long millis, int nanos): 等待被 join 的线程的时间最长为 millis 毫秒加上 nanos 微妙(千分之一毫秒)
2、后台线程
有一种线程, 它是在后台运行的, 它的任务是为其它线程提供服务的, 这种线程被称为 "后台线程"
后台线程有个特征: 如果所有前台线程都死亡, 后台线程会自动死亡
调用 Thread 对象 setDaemon(true) 方法可将指定的线程设置成后台线程, 也可以使用 isDaemon() 方法判断该线程是否是后台线程.
3. 线程睡眠 sleep
如果让当前正在执行的线程暂停一段时间, 并进入阻塞状态, 则可以通过调用 Thread 类的静态方法 sleep 方法,
sleep 方法有两种重载形式:
  • static void sleep(long millis)
  • static void sleep(long millis, int nanos)
4. 线程让步 yield
yield() 方法和 sleep 方法有点类似, 它也是一个 Thread 类提供的静态方法, 他也可以让当前正在执行的线程暂停, 但它不会阻塞该线程, 它只是将该线程转入就绪状态. yield 只是让当前线程暂停一下, 让系统的线程调度器重新调度一次, 当某个程序调用了 yield 方法暂停之后, 线程调度器将其调度出来重新执行.
示例:
public class TestYield extends Thread
{
public TestYield()
{
}

publi TestYield(String name)
{
super(name);
}

public void run()
{
for(int i = 0; i < 50; i++)
{
System.out.println(getName() + " " + i);
if (i == 20)
{
Thread.yield();
}
}
}

public static void main(String[] args) throws Exception
{
TestYield ty1 = new TestYield("高级");
ty1.setPriority(Thread.MAX_PRIORITY);
ty1.start();

TestYield ty2 = new TestYield("低级");
ty1.setPriority(Thread.MIN_PRIORITY);
ty2.start();
}
}
sleep 方法和 yield 方法的区别:
  • sleep 方法暂停当前线程后, 会给其他线程执行机会, 不会理会其他线程的优先级. 但 yield 方法只会给优先级相同, 或者优先级更高的线程执行机会
  • sleep 方法会将线程转入阻塞状态, 直到经过阻塞时间才会转入就绪状态. 而 yield 方法不会将线程转入阻塞状态, 它只是强制当前线程进入就绪状态. 因此完全有可能某个线程调用 yield 方法暂停之后, 立即在此获得处理器资源被执行
  • sleep 方法声明抛出了 InterruptedException 异常, 所以调用 sleep 方法时要么捕获该异常, 要么显示声明该异常. 而 yield 方法则没有声明抛出任何异常
  • sleep 方法比 yield 方法有更好的可移植性, 通常不要依靠 yield 来控制并发线程的执行
5. 改变线程的优先级
每个线程的优先级都与创建它的父线程具有相同的优先级, 在默认情况下, main 线程具有普通优先级, 由 main 线程创建的子线程也有普通优先级
Thread 提供了 setPriority(int newPriority) 和 getPriority() 方法来设置和获取优先级, 其中 setPriority 方法的参数可以是一个整数, 范围是 1 - 10 之间, 也可以使用 Thread 类的三个静态常量:
  • MAX_PRIORITY: 其值是 10
  • MIN_PRIORITY: 其值是 1
  • NORM_PRIORITY: 其值是 5
示例:
public class PriorityTest extends Thread
{
public PriorityTest(){}
//定义一个有参数的构造器,用于创建线程时指定name
public PriorityTest(String name)
{
super(name);
}
public void run()
{
for (int i = 0 ; i < 50 ; i++ )
{
System.out.println(getName() +  ",其优先级是:"
+ getPriority() + ",循环变量的值为:" + i);
}
}
public static void main(String[] args) 
{
//改变主线程的优先级
Thread.currentThread().setPriority(6);
for (int i = 0 ; i < 30 ; i++ )
{
if (i == 10)
{
PriorityTest low  = new PriorityTest("低级");
low.start();
System.out.println("创建之初的优先级:" + low.getPriority());
//设置该线程为最低优先级
low.setPriority(Thread.MIN_PRIORITY);
}
if (i == 20)
{
PriorityTest high = new PriorityTest("高级");
high.start();
System.out.println("创建之初的优先级:" + high.getPriority());
//设置该线程为最高优先级
high.setPriority(Thread.MAX_PRIORITY);
}
}
}
}

五. 线程的同步
1. 线程安全问题
        示例:
package xianchengtongbu;
public class Account {
 private String accountNo;
 private double balance;
 
 public Account(String accountNo, double balance)
 {
  this.accountNo = accountNo;
  this.balance = balance;
 }
 public String getAccountNo() {
  return accountNo;
 }
 public void setAccountNo(String accountNo) {
  this.accountNo = accountNo;
 }
 public double getBalance() {
  return balance;
 }
 public void setBalance(double balance) {
  this.balance = balance;
 }
 
 public int hashCode()
 {
  return this.accountNo.hashCode();
 }
 
 public boolean equals(Object obj)
 {
  if (obj != null && obj.getClass() == Account.class)
  {
   Account target = (Account) obj;
   return target.getAccountNo().equals(this.accountNo);
  }
  
  return false;
 }
}
package xianchengtongbu;
public class DrawThread extends Thread {
 // 模拟用户账户
 private Account account;
 private double drawAmount;
 
 public DrawThread(String name, Account account, double drawAmount) {
  super(name);
  this.account = account;
  this.drawAmount = drawAmount;
 }
 /**
  * 当多条线程修改同一个共享数据时,将涉及数据安全问题
  */
 @Override
 public void run() {
  // 账户余额大于取钱数目
  if (account.getBalance() >= this.drawAmount)
  {
   // 吐出钞票
   System.out.println(this.getName() + "取钱成功! 吐出钞票" + this.drawAmount);
   
   
   try
   {
    Thread.sleep(1);
   }
   catch (InterruptedException e) {
    e.printStackTrace();
   }
   
   
   account.setBalance(account.getBalance() - this.drawAmount);
   System.out.println("\t 余额为: " + account.getBalance());
  }
  else
  {
   System.out.println(this.getName() + "取钱失败!余额不足!");
  }
 }
}
package xianchengtongbu;
public class TestDraw {
 public static void main(String[] args) {
  // 创建一个账户
  Account acct = new Account("1234567", 1000);
  // 模拟两个线程同时对一个账户取钱
  new DrawThread("甲", acct, 800).start();
  new DrawThread("乙", acct, 800).start();
 }
}
        运行结果为:
甲取钱成功! 吐出钞票800.0
乙取钱成功! 吐出钞票800.0
  余额为: 200.0
  余额为: -600.0
        2、同步代码块
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics