JavaSE 15 多线程

1、线程的相关概念

程序

为了让计算机完成某个特定的功能,而编写的一系列的有序指定的集合。即:一段静态的代码。

进程

正在运行的程序。即:动态的过程,有自己的产生、存在和消亡。

线程

一个进程可以划分为若干个小的执行单位,每一个执行单位,就是线程。

一个进程可以包含多个线程。

若一个程序可以在同一时间执行多个线程,就是支持多线程的。

多任务

同一时刻,好像同时执行多个任务(进程)。比如Windows操作系统就是多任务操作系统。多任务可以理解为多进程。

2、多线程

<h2>概念</h2>

同一时刻,好像同时执行多个线程。

每个Java程序都有一个隐含的主线程:main方法。

多线程的执行

多个线程轮流抢占CPU时间片,因为速度非常快,所以造成了多个线程同时执行的错觉。

使用的好处

⑴ 有效的占用了CPU的资源,从某种意义上来讲提高了效率。

⑵ 提高了用户体验性。

应用场景

⑴ 程序需要同时执行两个或多个任务。

⑵ 程序需要实现一些等待的任务,比如:用户输入、文件读写操作、网络操作、搜索等。

⑶ 程序需要后台一直完成某项操作。

3、Thread类

Java语言使用Thread类及其子类的对象来表示线程。

4、Thread类的构造方法

Thread()

public Thread() {}

构造一个Thread对象。

<h2>Thread(String name)</h2>
  public Thread(String name) {}
  构造一个Thread对象,同时指定线程的名字。

Thread的子类可以通过调用super(String name); 来设置线程的名字。

<h2>Thread(Runnable target, String name)</h2>
  public Thread(Runnable target, String name) {}
  构造一个Thread对象,需要传入一个Runnable的实现类,同时指定该线程的名字。

5、线程的创建和启动

继承Thread

⑴ 定义一个类,让其继承Thread类,再重写run方法。

⑵ 创建继承Thread的类的对象,并调用对象的start方法。

 class MyThread extends Thread {
   @Override
   public void run() {
     // 任务体
   }
 }

 MyThread mt = new MyThread();
 mt.start();

实现Runnable接口

⑴ 定义一个类,让其实现Runnable接口,再实现run方法。

⑵ 创建一个Thread对象,将实现Runnable接口的实现类的对象传入Thread的构造方法。

⑶ 调用Thread对象的start方法。

 class MyRunnable implements Runnable {
   @Override
   public void run() {
     // 任务体
   }
 }

  MyRunnable mr = new MyRunnable();
  Thread t = new Thread(mr);
  t.start();

对比

相同点

⑴ 任务体都写在run方法中

⑵ 都调用了的Thread类(本类或子类)的start方法来启动线程

  <h3>不同点</h3>
   相对于继承Thread类,实现Runnable接口:
    ⑴ 避免了单继承的局限性,更加灵活
    ⑵ 比较适合解决多个线程共享数据资源的情况

    <h4>卖票【多个线程共享数据资源】</h4>

public class Test {

public static void main(String[] args) {

WindowRunnable wr = new WindowRunnable();

Thread t1 = new Thread(wr, “窗口1”);

Thread t2 = new Thread(wr, “窗口2”);

Thread t3 = new Thread(wr, “窗口3”);

t1.start();

t2.start();

t3.start();

}

}

class WindowRunnable implements Runnable {

private int tickets = 100; // 总票数

@Override

public void run() {

for (;;) {

if (0 < tickets) {

System.out.println(Thread.currentThread().getName() + “卖出了一张票,剩余票数:” + (–tickets));

} else {

System.out.println(Thread.currentThread().getName() + “票卖完了!”);

break;

}

}

}

}

6、Thread类的方法列举

start

public synchronized void start() {}

启动线程,并执行对应的run方法。

run

public void run() {}

子线程要执行的代码,要放入重写(实现)的此方法中。

currentThread

public static native Thread currentThread();

获取当前正在执行的线程对象。静态方法,通过Thread来调用

setName

public final void setName(String name) {}

设置线程的名称。

getName

public final String getName() {}

获取线程的名称。

 <h3>设置和获取线程名称【Thread的子类】</h3>

public class Test {

public static void main(String[] args) {

MyThread mt1 = new MyThread();

mt1.start(); // Thread-0 【默认线程名字从0开始】

MyThread mt2 = new MyThread(“线程2”);

mt2.start(); // 线程2

MyThread mt3 = new MyThread();

mt3.setName(“线程3”); // 调用设置线程名称的方法

mt3.start(); // 线程3

}

}

class MyThread extends Thread {

public MyThread() {

}

public MyThread(String name) { // 添加有参构造

super(name); // 调用父类【Thread】的构造方法

}

@Override

public void run() {

// 因为该线程类继承了Thread,所以可以直接调用【省略了this 完整:this.getName()】

System.out.println(getName());

}

}

 <h3>设置和获取线程名称【Runnable的实现类】</h3>

public class Test {

public static void main(String[] args) {

MyRunnable mr = new MyRunnable();

Thread t1 = new Thread(mr, “线程1”); // 设置线程名称

Thread t2 = new Thread(mr, “线程2”); // 设置线程名称

t1.start(); // 线程1

t2.start(); // 线程2

}

}

class MyRunnable implements Runnable {

@Override

public void run() {

// 通过调用Thread的静态方法获取当前线程对象,再调用方法获取线程名称

System.out.println(Thread.currentThread().getName());

}

}

setPriority

public final void setPriority(int newPriority) {}

设置线程的优先级。需要传入一个int类型的优先级。优先级范围1-10。注意:不能超过最高优先级【10】或低于最高优先级【1】,否则报错。看源码:

    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }

  Thread类中有三个常量:

public final static int MIN_PRIORITY = 1; // 最低优先级

public final static int NORM_PRIORITY = 5; // 默认优先级

public final static int MAX_PRIORITY = 10; // 最高优先级

注意:<b>优先级高的线程不一定先执行完,它只是具有CPU抢占的优先权。</b>

getPriority

public final int getPriority() {}

返回线程的优先级。

sleep

public static native void sleep(long millis) throws InterruptedException;

让当前线程休眠(暂停执行)指定的毫秒数。静态方法,通过Thread来调用

 <h3>设置和获取线程的优先级并测试执行的优先级</h3>

public class Test {

public static void main(String[] args) {

MyThread mt1 = new MyThread(“最高优先级线程”);

mt1.setPriority(Thread.MAX_PRIORITY); // 设置最高优先级【10】

mt1.start();

MyThread mt2 = new MyThread(“默认优先级线程”);

mt1.setPriority(Thread.NORM_PRIORITY); // 设置默认优先级【5】

mt2.start();

MyThread mt3 = new MyThread(“最低优先级线程”);

mt3.setPriority(Thread.MIN_PRIORITY); // 设置最低优先级【1】

mt3.start();

}

}

class MyThread extends Thread {

public MyThread(String name) {

super(name);

}

@Override

public void run() {

for (int i = 0; i < 10; i++) {

try {

Thread.sleep(300); // 休眠线程300毫秒

} catch (InterruptedException e) {

e.printStackTrace();

}

  System.out.println(getName() + "被执行了");
}

}

}

可以看出最终的执行效果具有随机性,所以可以验证:

① 优先级高的线程不一定先执行完,它只是具有CPU抢占的优先权。

② CPU抢占的优先权是随机的。

isAlive

public final native boolean isAlive();

测试线程是否处于活动状态。

isInterrupted

public boolean isInterrupted() {}

测试线程是否已经中断。

interrupt

public void interrupt() {}

中断线程,但实际上并没有真正停止线程。方法的本质:给线程添加一个中断的标记。

注意

如果中断的是正在休眠的线程,则会执行try住Thread.sleep(long millis); 方法中的catch块中的语句【可能是错误:java.lang.InterruptedException: sleep interrupted 或 自定义代码】。

示例:

public class Test {

public static void main(String[] args) {

MyThread mt = new MyThread();

mt.start();

mt.interrupt(); // 添加中断标记

}

}

class MyThread extends Thread {

@Override

public void run() {

try {

Thread.sleep(1000); // 线程休眠1000毫秒

} catch (InterruptedException e) {

// e.printStackTrace();

System.out.println(“我就是被执行的文字!”);

}

}

}

理解

给线程添加一个中断的标记,可以配合isInterrupted方法来使用。

示例:

public class Test {

public static void main(String[] args) {

MyThread mt = new MyThread();

mt.start();

try {
  Thread.sleep(50); // 主线程休眠(暂停执行)50毫秒
} catch (InterruptedException e) {
  e.printStackTrace();
}
mt.interrupt(); // 添加中断标记

}

}

class MyThread extends Thread {

@Override

public void run() {

for (;;) {

System.out.println(“线程任务打印文字”);

if (this.isInterrupted()) { // 判断是否已经添加中断标记

break; // 跳出循环

}

}

System.out.println(“============任务结束了============”);

}

}

yield

public static native void yield();

暂停当前正在执行的线程对象,并执行其他线程。静态方法,通过Thread来调用

注意:让出CPU的占用权,但是让出的时间具有不确定性。不一定让出CPU占用权的线程最后执行完。

  <h3>查看其中一个线程暂停后的执行结果</h3>

public class Test {

public static void main(String[] args) {

MyThread1 mt1 = new MyThread1();

mt1.setName(“线程1”);

MyThread2 mt2 = new MyThread2();

mt2.setName(“线程2”);

MyThread3 mt3 = new MyThread3();

mt3.setName(“线程3”);

mt1.start();

mt2.start();

mt3.start();

}

}

class MyThread1 extends Thread {

@Override

public void run() {

for (int i = 0; i < 20; i++) {

if (10 == i) {

Thread.yield(); // 暂停当前线程

System.out.println(“==========暂停” + getName() + “==========”);

}

  System.out.println(getName() + "被执行");
}

System.out.println("==========" + getName() + "执行完毕!==========");

}

}

class MyThread2 extends Thread {

@Override

public void run() {

for (int i = 0; i < 20; i++) {

System.out.println(getName() + “被执行”);

}

}

}

class MyThread3 extends Thread {

@Override

public void run() {

for (int i = 0; i < 20; i++) {

System.out.println(getName() + “被执行”);

}

}

}

join

public final void join() throws InterruptedException {}

让线程抢占CPU的时间片。一旦抢到,该线程肯定会被执行完。

应用于当一个线程需要调用另一个线程的执行结果,而另一个线程并未执行完毕,这时就可以调用此方法,以便获取执行的结果。

  <h3>主线程获取另外一子线程的执行结果</h3>

public class Test {

public static void main(String[] args) {

MyThread mt = new MyThread();

mt.start();

for (int i = 0; i < 10; i++) {

System.out.println(mt.sum); // 获取求和的结果

}

try {
  System.out.println("让mt线程获取CPU时间片, 请耐心等待结果的输出。。。");
  mt.join(); // 调用join方法,让mt线程获取CPU时间片【若不让mt线程抢占CPU时间片,则主线程打印结果会是0】
} catch (InterruptedException e) {
  e.printStackTrace();
}
System.out.println("1-100的和为:" + mt.sum); // 输出求和的结果

}

}

class MyThread extends Thread {

int sum; // 求和结果

@Override

public void run() {

for (int i = 1; i <= 100; i++) {

try {

Thread.sleep(15);

} catch (InterruptedException e) {

e.printStackTrace();

}

  sum += i;
}

}

}

setDaemon

public final void setDaemon(boolean on) {}

将该线程标记为守护线程或用户线程。true为守护线程,false为用户线程。

线程的分类

用户线程

守护线程

<h4>区别</h4>
     线程的执行是否要结束的方式

 ⑴ 用户线程有2种方式:
   ① 正常结束(任务执行完毕)
   ② 被中断

     ⑵ 守护线程只有1中方式:
        用户线程执行结束
     注意:如果用户线程已经执行结束,这时不管守护线程有没有结束,其都要结束。

    <h4>JDK中典型的守护线程</h4>
       垃圾回收机制

  <h3>守护线程随着主线程的结束而结束</h3>

public class Test {

public static void main(String[] args) {

MyThread mt = new MyThread();

mt.setDaemon(true); // 设置为守护线程

mt.start();

for (int i = 0; i < 10; i++) {
  try {
    Thread.sleep(10);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }

  System.err.println("主线程执行");
}
System.err.println("==========主线程执行完毕==========");

}

}

class MyThread extends Thread {

@Override

public void run() {

for (;;) {

try {

Thread.sleep(40);

} catch (InterruptedException e) {

e.printStackTrace();

}

  System.out.println("-----守护线程执行任务-----");
}

}

}

7、线程的停止

实现方法

⑴ 调用线程类的stop方法。但该方法已经过时(@Deprecated),不建议使用。

⑵ 调用线程类的interrupt方法。但是不能真正的停止。

⑶ 采用通知的方式。该方式可确保线程以安全的方式结束运行,建议使用。

  <h3>通知方式的实现步骤</h3>
    ⑴ 在线程类中添加一个boolean类型的循环标记,默认为true。
    ⑵ 在线程类中的run方法中,添加一个循环,循环条件为刚才添加的boolean类型的循环标记。
    ⑶ 在线程类中添加一个更新循环标记的方法。
    ⑷ 在需要停止线程时,将循环标记改为false即可。

public class Test {

public static void main(String[] args) {

MyThread mt = new MyThread();

mt.start();

try {
  Thread.sleep(50); // 主线程休眠(暂停执行)50毫秒
} catch (InterruptedException e) {
  e.printStackTrace();
}
mt.setLoop(false); // 更新循环标记

}

}

class MyThread extends Thread {

private boolean loop = true; // 循环标记

@Override

public void run() {

while (loop) { // 循环条件为循环标记

System.out.println(“线程任务打印文字”);

}

System.out.println(“============任务结束了============”);

}

public void setLoop(boolean loop) { // 更新循环标记的方法

this.loop = loop;

}

}

8、线程的生命周期

新建 —— 就绪 —— 运行 —— 阻塞 —— 死亡

新建

创建线程对象

就绪

调用了start方法

注意:调用此方法的线程将会进入线程队列等待CPU时间片,它只是具备了运行的条件

运行

抢到了CPU的执行权

线程的run方法定义了线程的操作和功能。

阻塞

调用了sleep方法,或wait方法

在某种特殊的情况下,被人为挂起或输入输出操作时,让出CPU,并临时中止。

死亡

线程任务正常执行完毕,或异常停止

9、线程的同步

问题的产生

多个线程,共同操作同一个数据资源时,有可能其中一个线程还没有执行完毕,而另外一个线程参与进来。这样就会导致数据出现了脏读。这就是线程同步问题。

解决办法

对多条操作共享数据的语句,只能让一个线程都执行完,其他的线程再参与进来。在其中一个线程执行过程中,其他线程不可以参与执行。

  具体就是:将需要共同操作的代码,上个“锁”。

互斥锁

Java中引入了对象互斥锁的概念,来保证共享数据操作的完整性。

每一个对象都对应一个可以称为“互斥锁”的标记。这个标记用来保证在任一时刻,只能有一个线程对象访问该对象。

  <h3>synchronized关键字</h3>
    用synchronized关键字来与对象的互斥锁联系。<b>当某个对象用synchronized修饰时,表明该对象在任一时刻只能有一个线程访问。</b>

 <h3>同步代码块</h3>
    synchronized(对象) { // 对象就是“锁”
      // 需要被同步的代码
    }

 <h3>同步方法</h3>
 修饰符 synchronized 返回值类型 方法名(形参列表) {
   // 需要被同步的代码
 }

  整个方法为同步方法。
注意:

⑴ 需要同步的代码一般里面没有循环。

① 普通的同步方法默认的锁对象为:this

② 静态的同步方法默认的锁对象为:当前类.class

实现步骤

⑴ 找到需要同步的代码【多个线程共同操作的代码】。

⑵ 添加同步代码块或同步方法。

⑶ 判断锁对象是否为同一个

释放锁的操作

⑴ 当前线程的同步代码块、同步方法执行完毕。

⑵ 当前线程在同步代码块、同步方法中遇到break或return终止了该代码块、方法的继续执行。

⑶ 当前线程在同步代码块、同步方法中出现了Error或Exception,导致异常结束。

⑷ 当前线程在同步代码块、同步方法中执行了线程对象的wait 方法。会使当前线程暂停,并释放互斥锁。

不会释放锁的操作

当某线程执行同步代码块、同步方法时,程序调用了Thread.sleep()【休眠线程】 或 Thread.yield()【暂停线程,让出CPU的抢占权】,暂停了当前线程的执行。这并不会释放互斥锁。

同步的前提

⑴ 有多个线程。

⑵ 多个线程有共享的数据。

⑶ 多个线程都有操作共享数据的代码。

同步的局限性

在一定程度上降低了程序的执行效率。

使用示例

不使用同步来卖票【出现了线程同步问题】

public class Test {

public static void main(String[] args) {

WindowRunnable wr = new WindowRunnable();

Thread t1 = new Thread(wr, “窗口1”);

Thread t2 = new Thread(wr, “窗口2”);

Thread t3 = new Thread(wr, “窗口3”);

t1.start();

t2.start();

t3.start();

}

}

class WindowRunnable implements Runnable {

private int tickets = 100; // 总票数

@Override

public void run() {

for (;;) {

if (0 < tickets) {

try {

Thread.sleep(10); // 让当前线程休眠10毫秒

} catch (InterruptedException e) {

e.printStackTrace();

}

      System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余票数:" + (--tickets));
  } else {
      System.out.println(Thread.currentThread().getName() + "票卖完了!");
      break;
  }
}

}

}

可以看出最终的结果存在负数,所以出现了线程同步问题。

使用同步来卖票【解决线程同步问题】

public class Test {

public static void main(String[] args) {

WindowRunnable wr = new WindowRunnable();

Thread t1 = new Thread(wr, “窗口1”);

Thread t2 = new Thread(wr, “窗口2”);

Thread t3 = new Thread(wr, “窗口3”);

t1.start();

t2.start();

t3.start();

}

}

class WindowRunnable implements Runnable {

private int tickets = 100; // 总票数

private boolean loop = true; // 循环标记

private synchronized void sellTickets() { // 同步方法

if (0 < tickets) {

try {

Thread.sleep(10); // 让当前线程休眠10毫秒

} catch (InterruptedException e) {

e.printStackTrace();

}

    System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余票数:" + (--tickets));
  } else {
    System.out.println(Thread.currentThread().getName() + "票卖完了!");
    loop = false;
 }

}

@Override

public void run() {

while (loop) {

sellTickets();

 /* 同步代码块
  synchronized(this) {
    if (0 < tickets) {
        try {
          Thread.sleep(10); // 让当前线程休眠10毫秒
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余票数:" + (--tickets));
    } else {
        System.out.println(Thread.currentThread().getName() + "票卖完了!");
        break;
    }
  }
   */
}

}

}

解决单例模式(懒汉式)的线程同步问题

  <h3>旧的代码【线程同步问题】</h3>

public class Test {

public static void main(String[] args) {

PrintThread pt1 = new PrintThread();

PrintThread pt2 = new PrintThread();

pt1.start();

pt2.start();

}

}

class PrintThread extends Thread {

@Override

public void run() {

for (int i = 0; i < 100; i++) {

System.out.println(getName() + Singleton.getInstance());

}

}

}

class Singleton {

private Singleton(){}

private static Singleton instance;

public static Singleton getInstance() {

if (null == instance) {

instance = new Singleton();

}

return instance;

}

}

可以看到:之前的代码,如果在多个线程中运行,会发现创建出的对象不是唯一的。

  <h3>修改后的单例模式【解决线程同步问题】</h3>

public class Test {

public static void main(String[] args) {

PrintThread pt1 = new PrintThread();

PrintThread pt2 = new PrintThread();

pt1.start();

pt2.start();

}

}

class PrintThread extends Thread {

@Override

public void run() {

for (int i = 0; i < 100; i++) {

System.out.println(getName() + Singleton.getInstance());

}

}

}

class Singleton {

private Singleton(){}

private static Singleton instance;

public static Singleton getInstance() {

if (null == instance) { // 提高效率

synchronized(Singleton.class) { // 同步代码块

if (null == instance) {

instance = new Singleton();

}

}

}

return instance;

}

}

10、线程的通信

概念

多个线程之间可以进行通话。

例如:开启了两个线程来打印100以内的数字,结果可能会是:

A 1

A 2

B 3

B 4

B 5

 而我们希望是:
      A  1
      B  2
      A  3
      B  4
      ...
  这种交替打印的效果
  这时就需要使用线程的通信

常见方法

wait

public final native void wait(long timeout) throws InterruptedException;

使某个同步代码块或同步方法中的当前线程进入等待状态,直到另外一线程对该线程发出notify或notifyAll为止。该方法只能被锁对象【同步代码块或同步方法的互斥锁(唯一性)】执行。

注意:

⑴ 该方法在Object类中,即每个对象类都有。

⑵ 调用此方法后,当前线程会释放对象监控权(互斥锁),然后进入等待。

⑶ 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁了)【线程必须可以执行同步方法、或同步代码块中的代码(被锁包起来)】。

⑷ 当线程被notify后,重新获取监控权(互斥锁),这时会从断点处【被wait的地方】继续执行。

  <h3>notify</h3>
    public final native void notify();
    唤醒某个同步代码块或同步方法中的被wait的一个线程。<b>该方法只能被锁对象【同步代码块或同步方法的互斥锁(唯一性)】执行。</b>
 注意:

⑴ 该方法在Object类中,即每个对象类都有。

⑵ 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁了)【线程必须可以执行同步方法、或同步代码块中的代码(被锁包起来)】。

  <h3>notifyAll</h3>
    public final native void notifyAll();
    唤醒某个同步代码块或同步方法中的被wait的所有线程。唤醒的顺序是随机的。<b>该方法只能被锁对象【同步代码块或同步方法的互斥锁(唯一性)】执行。</b>
 注意:

⑴ 该方法在Object类中,即每个对象类都有。

⑵ 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁了)【线程必须可以执行同步方法、或同步代码块中的代码(被锁包起来)】。

使用示例

  <h3>两个线程交错打印1-100内的数</h3>

public class Test {

public static void main(String[] args) {

PrintNum pn = new PrintNum();

Thread t1 = new Thread(pn, “打印者一”);

Thread t2 = new Thread(pn, “打印者二”);

t1.start();

t2.start();

}

}

class PrintNum implements Runnable {

private int num = 0;

@Override

public void run() {

for (;;) {

synchronized (this) {

this.notify(); // 在此唤醒上次打印过的线程

        if (num < 100) {
          try {
            Thread.sleep(26);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          System.out.println(Thread.currentThread().getName() + "---" + (++num));

          try {
            this.wait(); // 每次打印一个数字,就让当前线程等待
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        } else {
          break;
    }
    }
 }

}

}

  <h3>生产者和消费者</h3>

public class Test {

public static void main(String[] args) {

Storehouse storehouse = new Storehouse();

Producer producer = new Producer(storehouse);

Consumer consumer = new Consumer(storehouse);

producer.start();

consumer.start();

}

}

/**

* 仓库

*/

class Storehouse {

private int total = 0; // 货物总数

/**

* 存储方法

*/

public synchronized void store() {

// 唤醒消费者 【可能消费者的线程的抢到的CPU时间片较多,其一旦消费了所有的产品,就会wait,所以需要用生产者来唤醒】

this.notify();

if (total < 20) {
  System.out.println("生产者生产了1个产品,当前库存:" + (++total));
} else {
  try {
    this.wait(); // 达到库存上线,停止生产
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
}

}

/**

* 取出方法

*/

public synchronized void getOut() {

// 唤醒生产者 【可能生产者的线程的抢到的CPU时间片较多,其一旦生产够了库存,就会wait,所以需要用消费者来唤醒】

this.notify();

if (total > 0) {
  System.out.println("消费者消费了1个产品,当前库存:" + (--total));
} else {
  try {
    this.wait(); // 没有库存,无法消费
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
}

}

}

/**

* 生产者

*/

class Producer extends Thread {

private Storehouse storehouse;

public Producer(Storehouse storehouse) {

this.storehouse = storehouse;

}

@Override

public void run() {

for (;;) {

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

storehouse.store();

}

}

}

/**

* 消费者

*/

class Consumer extends Thread {

private Storehouse storehouse;

public Consumer(Storehouse storehouse) {

this.storehouse = storehouse;

}

@Override

public void run() {

for (;;) {

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

storehouse.getOut();

}

}

}

线程同步的前提

⑴ 必须在同步的基础上(必须在同步代码块或同步方法中实现通信)。

⑵ 要求调用wait、notify、notifyAll的对象必须为锁对象。

线程的死锁问题

概念

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

示例【小华和小明同时过独木桥】

public class Test {

public static void main(String[] args) {

Person p1 = new Person(true); // 小明

Person p2 = new Person(false); // 小华

p1.start();

p2.start();

}

}

/**

* 小明

*/

class XiaoMing {

public void holdOn() {

System.out.println(“小明说:你先让我过去,我再让你过去”);

}

public void pass() {

System.out.println(“小明过了独木桥”);

}

}

/**

* 小华

*/

class XiaoHua {

public void holdOn() {

System.out.println(“小华说:你先让我过去,我再让你过去”);

}

public void pass() {

System.out.println(“小华过了独木桥”);

}

}

class Person extends Thread {

private static final XiaoMing xm = new XiaoMing();

private static final XiaoHua xh = new XiaoHua();

private boolean which; // true代表小明;false代表小华

public Person(boolean which) { // 传入boolean值,创建小明或小华

this.which = which;

}

@Override

public void run() {

if (which) { // 小明

synchronized (xm) {

xm.holdOn();

synchronized(xh) {

xm.pass();

}

}

} else { // 小华

synchronized (xh) {

xh.holdOn();

synchronized(xm) {

xh.pass();

}

}

}

}

}

时间: 2024-10-26 02:22:36

JavaSE 15 多线程的相关文章

JavaSE之多线程

今天打算重新学习一遍多线程,通过条理的的梳理,更加巩固基础知识. 谈起多线程,我们需要分清楚一些概念,什么是程序.进程和线程? 程序(program):是为了完成特定任务,用某种语言编写的一组指令的集合,指的是一段静态的代码,静态对象 进程(process):是程序的一段执行过程,或者是正在运行的一个程序.动态过程:有它自身的产生,存在和消亡的过程 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径 线程与进程的关系: 这里一个进程可以分为单线程进程和多线程进程,它们之间

JavaSE基础---多线程

进程:正在进行的程序.其实就是一个应用程序运行时的内存分配空间. 线程:进程中一个程序执行控制单元,一条执行路径.进程负责的事应用程序的空间的标识,线程负责的事应用程序的执行顺序. 进程和线程的关系:一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区.和变量. JVM启动时,首先有一个主线程,负责程序的执行,调用的是main函数.主线程执行的代码都在main方法中. 当产生垃圾时,收垃圾的动作,是不需要主线

Delphi多线程编程(15)--多线程同步之WaitableTimer(等待定时器对象)[续]

转载自:万一的博客 本次专门研究下 SetWaitableTimer的第二个参数(起始时间) 它有正值.负值.0值这三种情况,前面已经用过 0 值 先学习负值(相对时间),也就是从当前算起隔多长时间开始执行 这个相对时间是以 1/100 纳秒为单位的,譬如赋值:3*10000000 相当于 3 秒 1 s(秒) = 1,000            ms(毫秒); 1 s(秒) = 1,000,000        µs(微妙); 1 s(秒) = 1,000,000,000    ns(纳秒)

04-java学习笔记-多线程1

1.多线程概述 进程:是一个正在执行中的程序. 每一个进程都有一个执行单元.该顺序是一个执行路径,或者叫一个控制单元. 线程:就是进程中的一个独立的控制单元. 线程在控制着进程的执行. 一个进程中至少有一个线程. Java VM  启动的时候会有一个进程 java.exe 该进程中至少一个线程负责java程序的执行. 而且这个线程运行的代码存在于main方法中. 该线程称之为主线程. 扩展:其实更细节说明jvm, jvm启动不止一个线程,还有负责垃圾回收机制的线程. 2.创建线程-继承Threa

如何自学 Java 开发

如何自学 Java 开发? 568赞同反对,不会显示你的姓名 李艾米IT路上学习 568 人赞同 Java Web前端技术 HTML 入门视频课程 1 HTML 简介 2 HTML基本结构[ 3 HTML的BODY标签以及颜色的设定 4 HTML之br标签 5 HTML之p标签 6 HTML之pre标签. 7 HTML之center 8 HTML之引文标签 9 HTML之hr 10 HTML之address 11 HTML之meta 标签 12 HTML之特殊字符 13 HTML之注释 14 H

javaSE27天学习目录

? 第一阶段(java基础知识) 计算机基础知识 Java开发环境的搭建和应用 机制转换 有符号数据表示法(原码.反码.补码) Java语句基础(关键字.标识符.注释.常量.变量.数据类型.运算符) 程序流程控制语句及应用(if.switch.for.while) 函数及其应用 数组及其应用 水仙花.斐波那契数列.数据加密 等经典问题的讲解. ? 第二阶段(面向对象) 面向对象思想 类与对象 类的组成 封装及其应用 继承及其应用 多态及其应用 抽象类及其应用 接口及其应用 包.导包及其应用 单例

并发设计模式之生产者消费者设计模式

主函数: 1 package com.ietree.basicskill.mutilthread.designpattern.ProducerConsumer; 2 3 import java.util.concurrent.BlockingQueue; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 import java.util.concurrent.Link

Servlet的线程安全

Servlet的线程安全 一.什么是Servlet的线程安全 1.在Servlet的整个生命周期中,构造方法只被执行一次.也就是说,在Servlet的整个生命周期中,只存在一个Servlet实例对象.这说明Servlet是单例多线程的,可能会引起线程安全问题. 所谓线程安全就是一个Servlet实例对象会同时处理多个请求,这样的Servlet工作效率的确很高.但如果Servlet中包含成员变量的话,可能一个线程对该成员变量进行写操作,而另一个线程对该成员变量进行读操作.所以,单例多线程的Serv

试题总结1

1.tcp断开连接为什么要四次握手,如果没有四次将会有什么隐患?TCP建立连接要进行3次握手,而断开连接要进行4次,这是由于TCP的半关闭造成的,因为TCP连接是全双工的(即数据可在两个方向上同时传递)所以进行关闭时每个方向上都要单独进行关闭,这个单方向的关闭就叫半关闭.关闭的方法是一方完成它的数据传输后,就发送一个FIN来向另一方通告将要终止这个方向的连接.当一端收到一个FIN,它必须通知应用层TCP连接已终止了这个方向的数据传送,发送FIN通常是应用层进行关闭的结果. 2.开机启动流程 3.