17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(中)

简介: 17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)

17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(上):https://developer.aliyun.com/article/1580253

17.3线程状态


17.3.1 线程状态

线程状态:新建、就绪、运行、终止。

17.3.2 常见方法

方法名 说明
public static void sleep(long millis) 当前线程主动休眠 millis 毫秒。
public static void yield() 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
public final void join() 允许其他线程加入到当前线程中。
public void setPriority(int) 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。
public void setDaemon(boolean) 设置为守护线程线程有两类:用户线程(前台线程)、守护线程(后台线程)

17.3.3 线程状态(等待)

线程状态:新建、就绪、运行、等待、终止。

17.4 线程安全


为什么会出现线程安全问题?

  • 需求:A线程将“Hello”存入数组;B线程将“World”存入数组。
  • 线程不安全:
  • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
  • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
  • 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。

案例演示:

public class ThreadSafe {
  private static int index=0;
  public static void main(String[] args)  throws Exception{
    //创建数组
    String[] s=new String[5];
    //创建两个操作
    Runnable runnableA=new Runnable() {
      
      @Override
      public void run() {
        //同步代码块
        synchronized (s) {
          s[index]="hello";
          index++;
        }
        
      }
    };
    Runnable runnableB=new Runnable() {
      
      @Override
      public void run() {
        synchronized (s) {
          s[index]="world";
          index++;
        }
        
      }
    };
    
    //创建两个线程对象
    Thread a=new Thread(runnableA,"A");
    Thread b=new Thread(runnableB,"B");
    a.start();
    b.start();
    
    a.join();//加入线程
    b.join();//加入线程
    
    System.out.println(Arrays.toString(s));
    
  }
}


17.4.1 同步代码块

语法:

synchronized(临界资源对象){ //对临界资源对象加锁

//代码(原子操作)

}

注意:

演示案例:

Ticket类:

public class Ticket implements Runnable{
  
  private int ticket=100;
  //创建锁
  //private Object obj=new Object();
  
  @Override
  public void run() {
    
    while(true) {
      synchronized (this) {//this ---当前对象
        if(ticket<=0) {
          break;
        }
        System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"票");
        ticket--;
      }
      
    }
  }
  
}

17.4.2 线程状态(阻塞)

线程状态:新建、就绪、运行、阻塞、终止。

17.4.3 同步方法

语法:

synchronized 返回值类型 方法名称(形参列表){ //对当前对象(this)加锁

// 代码(原子操作)

}

注意:

17.4.4 同步规则

  • 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
  • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。

JDK中线程安全的类:

  • StringBuffer
  • Vector
  • Hashtable
    以上类中的公开方法,均为synchonized修饰的同步方法。

17.5 死锁

17.5.1 什么是死锁?

  • 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。

17.5.2 死锁案例

MyLock类:

public class MyLock {
  //两个锁(两个筷子)
  public static Object a=new Object();
  public static Object b=new Object();
}

BoyThread类:

public class Boy extends Thread{
  @Override
  public void run() {
    synchronized (MyLock.a) {
      System.out.println("男孩拿到了a");
      synchronized (MyLock.b) {
        System.out.println("男孩拿到了b");
        System.out.println("男孩可以吃东西了...");
      }
    }
  }
}

GirlThread类:

public class Girl extends Thread {
  @Override
  public void run() {
    synchronized (MyLock.b) {
      System.out.println("女孩拿到了b");
      synchronized (MyLock.a) {
        System.out.println("女孩拿到了a");
        System.out.println("女孩可以吃东西了...");
      }
    }
  }
}

TestDeadLock类:

public class TestDeadLock {
  public static void main(String[] args) {
    Boy boy=new Boy();
    Girl girl=new Girl();
    girl.start();
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    
    boy.start();
  }
}

17.6 线程通信


17.6.1 线程通信方法

方法 说明
public final void wait() 释放锁,进入等待队列
public final void wait(long timeout) 在超过指定的时间前,释放锁,进入等待队列
public final void notify() 随机唤醒、通知一个线程
public final void notifyAll() 唤醒、通知所有线程

注意:所有的等待、通知方法必须在对加锁的同步代码块中。

17.6.2 生产者消费者

若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品。

Bread类:

pupublic class Bread {
  private int id;
  private String productName;
  public Bread() {
    // TODO Auto-generated constructor stub
  }
  public Bread(int id, String productName) {
    super();
    this.id = id;
    this.productName = productName;
  }
  public int getId() {
    return id;
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getProductName() {
    return productName;
  }
  public void setProductName(String productName) {
    this.productName = productName;
  }
  @Override
  public String toString() {
    return "Bread [id=" + id + ", productName=" + productName + "]";
  }
}

BreadCon类:

public class BreadCon {
  //存放面包的数组
  private Bread[] cons=new Bread[6];
  //存放面包的位置
  private int index=0;
  
  //存放面包
  public synchronized void input(Bread b) { //锁this
    //判断容器有没有满
    while(index>=6) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    
    cons[index]=b;
    System.out.println(Thread.currentThread().getName()+"生产了"+b.getId()+"");
    index++;
    //唤醒
    this.notifyAll();
    
    
    
  }
  //取出面包
  public synchronized void output() {//锁this
    while(index<=0) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
    index--;
    Bread b=cons[index];
    System.out.println(Thread.currentThread().getName()+"消费了"+b.getId()+" 生产者:"+b.getProductName());
    cons[index]=null;
    //唤醒生产者
    this.notifyAll();
  }
}

Consume类:

public class Consume implements Runnable{

  private BreadCon con;
  
  public Consume(BreadCon con) {
    super();
    this.con = con;
  }

  @Override
  public void run() {
    for(int i=0;i<30;i++) {
      con.output();
    }
  }

}

Produce类:

public class Prodcut implements Runnable {

  private BreadCon con;
  
  public Prodcut(BreadCon con) {
    super();
    this.con = con;
  }

  @Override
  public void run() {
    for(int i=0;i<30;i++) {
      con.input(new Bread(i, Thread.currentThread().getName()));
    }
  }
  
}

Test类:

public class Test {
  public static void main(String[] args) {
    //容器
    BreadCon con=new BreadCon();
    //生产和消费
    Prodcut prodcut=new Prodcut(con);
    Consume consume=new Consume(con);
    //创建线程对象
    Thread chenchen=new Thread(prodcut, "晨晨");
    Thread bingbing=new Thread(consume, "消费");
    Thread mingming=new Thread(prodcut, "明明");
    Thread lili=new Thread(consume, "莉莉");
    //启动线程
    chenchen.start();
    bingbing.start();
    mingming.start();
    lili.start();
  }
}

17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(下):https://developer.aliyun.com/article/1580256

目录
相关文章
|
6天前
|
Java
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式
Java 8 引入的 Streams 功能强大,提供了一种简洁高效的处理数据集合的方式。本文介绍了 Streams 的基本概念和使用方法,包括创建 Streams、中间操作和终端操作,并通过多个案例详细解析了过滤、映射、归并、排序、分组和并行处理等操作,帮助读者更好地理解和掌握这一重要特性。
14 2
|
6天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
10天前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
10天前
|
存储 Java 开发者
在 Java 中,如何遍历一个 Set 集合?
【10月更文挑战第30天】开发者可以根据具体的需求和代码风格选择合适的遍历方式。增强for循环简洁直观,适用于大多数简单的遍历场景;迭代器则更加灵活,可在遍历过程中进行更多复杂的操作;而Lambda表达式和`forEach`方法则提供了一种更简洁的函数式编程风格的遍历方式。
|
10天前
|
存储 Java 开发者
Java中的集合框架深入解析
【10月更文挑战第32天】本文旨在为读者揭开Java集合框架的神秘面纱,通过深入浅出的方式介绍其内部结构与运作机制。我们将从集合框架的设计哲学出发,探讨其如何影响我们的编程实践,并配以代码示例,展示如何在真实场景中应用这些知识。无论你是Java新手还是资深开发者,这篇文章都将为你提供新的视角和实用技巧。
11 0
|
Java
Java多线程之Lock的使用
import java.util.concurrent.ExecutorService;   import java.util.concurrent.
893 0
|
Java
Java多线程之Lock的使用(转)
package thread.lock; import java.util.concurrent.ExecutorService; import java.util.concurrent.
834 0
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
17天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
22 9