Java 线程(二)

简介: Java 线程(二)

Java 实例 - 死锁及解决方法


死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

java 死锁产生的四个必要条件:

互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。下面用java代码来模拟一下死锁的产生。

解决死锁问题的方法是:一种是用synchronized,一种是用Lock显式锁实现。

而如果不恰当的使用了锁,且出现同时要锁多个对象时,会出现死锁情况,如下:


import java.util.Date;
public class LockTest {
   public static String obj1 = "obj1";
   public static String obj2 = "obj2";
   public static void main(String[] args) {
      LockA la = new LockA();
      new Thread(la).start();
      LockB lb = new LockB();
      new Thread(lb).start();
   }
}
class LockA implements Runnable{
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockA 开始执行");
         while(true){
            synchronized (LockTest.obj1) {
               System.out.println(new Date().toString() + " LockA 锁住 obj1");
               Thread.sleep(3000); // 此处等待是给B能锁住机会
               synchronized (LockTest.obj2) {
                  System.out.println(new Date().toString() + " LockA 锁住 obj2");
                  Thread.sleep(60 * 1000); // 为测试,占用了就不放
               }
            }
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}
class LockB implements Runnable{
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockB 开始执行");
         while(true){
            synchronized (LockTest.obj2) {
               System.out.println(new Date().toString() + " LockB 锁住 obj2");
               Thread.sleep(3000); // 此处等待是给A能锁住机会
               synchronized (LockTest.obj1) {
                  System.out.println(new Date().toString() + " LockB 锁住 obj1");
                  Thread.sleep(60 * 1000); // 为测试,占用了就不放
               }
            }
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}


以上代码运行输出结果为:

Tue May 05 10:51:06 CST 2015 LockB 开始执行
Tue May 05 10:51:06 CST 2015 LockA 开始执行
Tue May 05 10:51:06 CST 2015 LockB 锁住 obj2
Tue May 05 10:51:06 CST 2015 LockA 锁住 obj1


此时死锁产生。

为了解决这个问题,我们不使用显示的去锁,我们用信号量去控制。

信号量可以控制资源能被多少线程访问,这里我们指定只能被一个线程访问,就做到了类似锁住。而信号量可以指定去获取的超时时间,我们可以根据这个超时时间,去做一个额外处理。

对于无法成功获取的情况,一般就是重复尝试,或指定尝试的次数,也可以马上退出。

来看下如下代码:

import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class UnLockTest {
   public static String obj1 = "obj1";
   public static final Semaphore a1 = new Semaphore(1);
   public static String obj2 = "obj2";
   public static final Semaphore a2 = new Semaphore(1);
   public static void main(String[] args) {
      LockAa la = new LockAa();
      new Thread(la).start();
      LockBb lb = new LockBb();
      new Thread(lb).start();
   }
}
class LockAa implements Runnable {
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockA 开始执行");
         while (true) {
            if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
               System.out.println(new Date().toString() + " LockA 锁住 obj1");
               if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
                  System.out.println(new Date().toString() + " LockA 锁住 obj2");
                  Thread.sleep(60 * 1000); // do something
               }else{
                  System.out.println(new Date().toString() + "LockA 锁 obj2 失败");
               }
            }else{
               System.out.println(new Date().toString() + "LockA 锁 obj1 失败");
            }
            UnLockTest.a1.release(); // 释放
            UnLockTest.a2.release();
            Thread.sleep(1000); // 马上进行尝试,现实情况下do something是不确定的
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}
class LockBb implements Runnable {
   public void run() {
      try {
         System.out.println(new Date().toString() + " LockB 开始执行");
         while (true) {
            if (UnLockTest.a2.tryAcquire(1, TimeUnit.SECONDS)) {
               System.out.println(new Date().toString() + " LockB 锁住 obj2");
               if (UnLockTest.a1.tryAcquire(1, TimeUnit.SECONDS)) {
                  System.out.println(new Date().toString() + " LockB 锁住 obj1");
                  Thread.sleep(60 * 1000); // do something
               }else{
                  System.out.println(new Date().toString() + "LockB 锁 obj1 失败");
               }
            }else{
               System.out.println(new Date().toString() + "LockB 锁 obj2 失败");
            }
            UnLockTest.a1.release(); // 释放
            UnLockTest.a2.release();
            Thread.sleep(10 * 1000); // 这里只是为了演示,所以tryAcquire只用1秒,而且B要给A让出能执行的时间,否则两个永远是死锁
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}


以上实例代码输出结构为:

Tue May 05 10:59:13 CST 2015 LockA 开始执行
Tue May 05 10:59:13 CST 2015 LockB 开始执行
Tue May 05 10:59:13 CST 2015 LockB 锁住 obj2
Tue May 05 10:59:13 CST 2015 LockA 锁住 obj1
Tue May 05 10:59:14 CST 2015LockB 锁 obj1 失败
Tue May 05 10:59:14 CST 2015LockA 锁 obj2 失败
Tue May 05 10:59:15 CST 2015 LockA 锁住 obj1
Tue May 05 10:59:15 CST 2015 LockA 锁住 obj2


Java 实例 - 获取线程id


以下实例演示了如何使用 getThreadId() 方法获取线程id:

public class Main extends Object implements Runnable {
  private ThreadID var;
  public Main(ThreadID v) {
    this.var = v;
  }
  public void run() {
    try {
      print("var getThreadID =" + var.getThreadID());
      Thread.sleep(2000);
      print("var getThreadID =" + var.getThreadID());
    } catch (InterruptedException x) {
    }
  }
  private static void print(String msg) {
    String name = Thread.currentThread().getName();
    System.out.println(name + ": " + msg);
  }
  public static void main(String[] args) {
    ThreadID tid = new ThreadID();
    Main shared = new Main(tid);
    try {
      Thread threadA = new Thread(shared, "threadA");
      threadA.start();
      Thread.sleep(500);
      Thread threadB = new Thread(shared, "threadB");
      threadB.start();
      Thread.sleep(500);
      Thread threadC = new Thread(shared, "threadC");
      threadC.start();
    } catch (InterruptedException x) {
    }
  }
}
class ThreadID extends ThreadLocal {
  private int nextID;
  public ThreadID() {
    nextID = 10001;
  }
  private synchronized Integer getNewID() {
    Integer id = new Integer(nextID);
    nextID++;
    return id;
  }
  protected Object initialValue() {
    print("in initialValue()");
    return getNewID();
  }
  public int getThreadID() {
    Integer id = (Integer) get();
    return id.intValue();
  }
  private static void print(String msg) {
    String name = Thread.currentThread().getName();
    System.out.println(name + ": " + msg);
  }
}


以上代码运行输出结果为:

threadA: in initialValue()
threadA: var getThreadID =10001
threadB: in initialValue()
threadB: var getThreadID =10002
threadC: in initialValue()
threadC: var getThreadID =10003
threadA: var getThreadID =10001
threadB: var getThreadID =10002
threadC: var getThreadID =10003


Java 实例 - 线程挂起


以下实例演示了如何将线程挂起:


public class SleepingThread extends Thread {
   private int countDown = 5;
   private static int threadCount = 0;
   public SleepingThread() {
      super("" + ++threadCount);
      start();
   }
   public String toString() { 
      return "#" + getName() + ": " + countDown;
   }
   public void run() {
      while (true) {
         System.out.println(this);
         if (--countDown == 0)
            return;
         try {
            sleep(100);
         }
         catch (InterruptedException e) {
            throw new RuntimeException(e);
         }
      }
   }
   public static void main(String[] args) 
   throws InterruptedException {
      for (int i = 0; i < 5; i++)
      new SleepingThread().join();
      System.out.println("线程已被挂起");
   }
}


以上代码运行输出结果为:

#1: 5
#1: 4
#1: 3
#1: 2
#1: 1
……
#5: 3
#5: 2
#5: 1
线程已被挂起


Java 实例 - 终止线程


Java中原来在Thread中提供了stop()方法来终止线程,但这个方法是不安全的,所以一般不建议使用。

本文向大家介绍使用interrupt方法中断线程。

使用interrupt方法来终端线程可分为两种情况:

线程处于阻塞状态,如使用了sleep方法。

使用while(!isInterrupted()){……}来判断线程是否被中断。

在第一种情况下使用interrupt方法,sleep方法将抛出一个InterruptedException例外,而在第二种情况下线程将直接退出。下面的代码演示了在第一种情况下使用interrupt方法。

public class ThreadInterrupt extends Thread 
{ 
    public void run() 
    { 
        try 
        { 
            sleep(50000);  // 延迟50秒 
        } 
        catch (InterruptedException e) 
        { 
            System.out.println(e.getMessage()); 
        } 
    } 
    public static void main(String[] args) throws Exception 
    { 
        Thread thread = new ThreadInterrupt(); 
        thread.start(); 
        System.out.println("在50秒之内按任意键中断线程!"); 
        System.in.read(); 
        thread.interrupt(); 
        thread.join(); 
        System.out.println("线程已经退出!"); 
    } 
}


以上代码运行输出结果为:


在50秒之内按任意键中断线程!
sleep interrupted
线程已经退出!


Java 实例 - 生产者/消费者问题


生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,如下图所示,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况:

存储空间已满,而生产者占用着它,消费者等着生产者让出空间从而去除产品,生产者等着消费者消费产品,从而向空间中添加产品。互相等待,从而发生死锁。



以下实例演示了如何通过线程解决生产者/消费者问题:

/*
 author by nowcoder.com
 ProducerConsumerTest.java
 */
public class ProducerConsumerTest {
   public static void main(String[] args) {
      CubbyHole c = new CubbyHole();
      Producer p1 = new Producer(c, 1);
      Consumer c1 = new Consumer(c, 1);
      p1.start(); 
      c1.start();
   }
}
class CubbyHole {
   private int contents;
   private boolean available = false;
   public synchronized int get() {
      while (available == false) {
         try {
            wait();
         }
         catch (InterruptedException e) {
         }
      }
      available = false;
      notifyAll();
      return contents;
   }
   public synchronized void put(int value) {
      while (available == true) {
         try {
            wait();
         }
         catch (InterruptedException e) { 
         } 
      }
      contents = value;
      available = true;
      notifyAll();
   }
}
class Consumer extends Thread {
   private CubbyHole cubbyhole;
   private int number;
   public Consumer(CubbyHole c, int number) {
      cubbyhole = c;
      this.number = number;
   }
   public void run() {
      int value = 0;
         for (int i = 0; i < 10; i++) {
            value = cubbyhole.get();
            System.out.println("消费者 #" + this.number+ " got: " + value);
         }
    }
}
class Producer extends Thread {
   private CubbyHole cubbyhole;
   private int number;
   public Producer(CubbyHole c, int number) {
      cubbyhole = c;
      this.number = number;
   }
   public void run() {
      for (int i = 0; i < 10; i++) {
         cubbyhole.put(i);
         System.out.println("生产者 #" + this.number + " put: " + i);
         try {
            sleep((int)(Math.random() * 100));
         } catch (InterruptedException e) { }
      }
   }
}


以上代码运行输出结果为:

消费者 #1 got: 0
生产者 #1 put: 0
生产者 #1 put: 1
消费者 #1 got: 1
生产者 #1 put: 2
消费者 #1 got: 2
生产者 #1 put: 3
消费者 #1 got: 3
生产者 #1 put: 4
消费者 #1 got: 4
生产者 #1 put: 5
消费者 #1 got: 5
生产者 #1 put: 6
消费者 #1 got: 6
生产者 #1 put: 7
消费者 #1 got: 7
生产者 #1 put: 8
消费者 #1 got: 8
生产者 #1 put: 9
消费者 #1 got: 9


Java 实例 - 获取线程状态


Java 线程的生命周期中,在 Thread 类里有一个枚举类型 State,定义了线程的几种状态,分别有:


New

Runnable

Blocked

Waiting

Timed Waiting

Terminated


各个状态说明:


初始状态 - NEW

声明:

public static final Thread.State NEW

RUNNABLE

声明:

public static final Thread.State RUNNABLE

2.1. 就绪状态


就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。


调用线程的 start() 方法,此线程进入就绪状态。


当前线程 sleep() 方法结束,其他线程 join() 结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。


当前线程时间片用完了,调用当前线程的 yield() 方法,当前线程进入就绪状态。


锁池里的线程拿到对象锁后,进入就绪状态。


2.2. 运行中状态


线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。


阻塞状态 - BLOCKED

声明:

public static final Thread.State BLOCKED

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。


等待 - WAITING

声明:

public static final Thread.State WAITING

处于这种状态的线程不会被分配 CPU 执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。


超时等待 - TIMED_WAITING

声明:

public static final Thread.State TIMED_WAITING

处于这种状态的线程不会被分配 CPU 执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。


终止状态 - TERMINATED

声明:

public static final Thread.State TERMINATED

当线程的 run() 方法完成时,或者主线程的 main() 方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。


在一个终止的线程上调用 start() 方法,会抛出 java.lang.IllegalThreadStateException 异常。


以下实例演示了如何获取线程的状态:


// Java 程序 - 演示线程状态
class thread implements Runnable 
{ 
    public void run() 
    { 
        //  thread2  - 超时等待
        try
        { 
            Thread.sleep(1500); 
        }  
        catch (InterruptedException e)  
        { 
            e.printStackTrace(); 
        } 
        System.out.println("State of thread1 while it called join() method on thread2 -"+ 
            Test.thread1.getState()); 
        try
        { 
            Thread.sleep(200); 
        }  
        catch (InterruptedException e)  
        { 
            e.printStackTrace(); 
        }      
    } 
} 
public class Test implements Runnable 
{ 
    public static Thread thread1; 
    public static Test obj; 
    public static void main(String[] args) 
    { 
        obj = new Test(); 
        thread1 = new Thread(obj); 
        // 创建 thread1,现在是初始状态
        System.out.println(
        "State of thread1 after creating it - " + thread1.getState()); 
        thread1.start(); 
        // thread1 - 就绪状态
        System.out.println(
        "State of thread1 after calling .start() method on it - " +  
            thread1.getState()); 
    } 
    public void run() 
    { 
        thread myThread = new thread(); 
        Thread thread2 = new Thread(myThread); 
        // 创建 thread1,现在是初始状态
        System.out.println(
        "State of thread2 after creating it - "+ thread2.getState()); 
        thread2.start(); 
        // thread2 - 就绪状态
        System.out.println(
        "State of thread2 after calling .start() method on it - " +  
            thread2.getState()); 
        // moving thread1 to timed waiting state 
        try
        { 
            //moving - 超时等待
            Thread.sleep(200); 
        }  
        catch (InterruptedException e)  
        { 
            e.printStackTrace(); 
        } 
        System.out.println(
        "State of thread2 after calling .sleep() method on it - "+  
            thread2.getState() ); 
        try 
        { 
            // 等待 thread2 终止
            thread2.join(); 
        }  
        catch (InterruptedException e)  
        { 
            e.printStackTrace(); 
        } 
        System.out.println(
        "State of thread2 when it has finished it's execution - " +  
            thread2.getState()); 
    } 
}


以上代码运行输出结果为:

State of thread1 after creating it - NEW
State of thread1 after calling .start() method on it - RUNNABLE
State of thread2 after creating it - NEW
State of thread2 after calling .start() method on it - RUNNABLE
State of thread2 after calling .sleep() method on it - TIMED_WAITING
State of thread1 while it called join() method on thread2 -WAITING
State of thread2 when it has finished it's execution - TERMINATED
相关文章
|
6天前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
58 23
|
13天前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
81 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
17天前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
142 60
【Java并发】【线程池】带你从0-1入门线程池
|
1月前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
109 14
|
1月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
57 13
|
1月前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
2月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
2月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
128 17
|
3月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
3月前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。

热门文章

最新文章