Java多线程

多线程

当今的操作系统绝大部分都是基于 多任务 的操作系统;多任务操作系统的最大特点,是可以同时 运行多个程序;由于操作系统支持 时间片 轮换算法,使得用户感觉多个程序在同时运行,似乎有多个CPU在起作用。

运行在操作系统之上的每个应用程序,都会占用一个独立的 进程(process),而 进程内又允许运行多个线程(thread),这意味着一个程序可以同时执行多个任务的功能;在基于线程的多任务而处理环境中, 线程是执行特定任务的可执行代码的最小单位;多线程帮助你写出CPU最大利用率的高效程序,因为空闲时间保持最低,这对Java运行的交互式的网络互连环境是至关重要的,例如:网络的数据传输速率远低于计算机的处理能力,在传统的单线程环境中,你的计算机必须花费大量的空闲时间来等待,多线程能够使你充分利用这些空闲时间。

进程和线程的区别

进程是指系统中正在运行中的应用程序,它拥有自己独立的内存空间;

线程是指进程中一个执行流程,一个进程中允许同时启动多个线程,他们分别执行不同的任务;

线程与进程的主要区别在于:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中,这些线程可以共享数据,因此线程间的通信比较简单,消耗的系统开销也相对较小。

在Java中实现线程有两种方式

  java.lang.Thread

  java.lang.Runnable

主线程

任何一个Java程序启动时,一个线程立刻运行,它执行 main 方法,这个线程称为程序的 主线程; 也就是说,任何Java程序都 至少有一个线程 ,即主线程; 主线程的特殊之处在于:

  它是产生其它线程子线程的线程;

  通常它必须最后结束,因为它要执行其它子线程的关闭工作。

public class MainThreadDemo {
public static void main(String[] args) {
  Thread tMain=Thread.currentThread();
  System.out.println("当前的线程是:"+tMain);
try{
  for(int i=0;i<5;i++){
  System.out.println(i);
  Thread.sleep(2000);
}
}catch (Exception e) {
  e.printStackTrace();
}
}
}

只有Thread.start()才能创建线程,执行线程中的run方法。 如果直接调用run方法,则无法创建线程。

Thread类示例

泡茶的时候可以一边洗杯子,一边烧水。

public class MakeTea extends Thread {
public static void main(String[] args) {
  new BoilThread().start();
  new WashThread().start();
}
}

class BoilThread extends Thread {
@Override
public void run() {
try {
  System.out.println("开始烧水");
  Thread.sleep(10000);
  System.out.println("水烧开了");
} catch (InterruptedException e) {
  e.printStackTrace();
}
}
}

class WashThread extends Thread {
@Override
public void run() {
try {
  for (int i = 1; i <= 5; i++) {
  System.out.println("开始洗第" + i + "个茶杯");
  Thread.sleep(1500);
  System.out.println("第" + i + "个茶杯洗干净了");
}
} catch (Exception e) {
  e.printStackTrace();
}
}
}

Runnable接口

public class MakeTeaRunnable {
public static void main(String[] args) {
  new Thread(new BoilThreadRunnable()).start();
  new Thread(new WashThreadRunnable()).start();
}
}

class BoilThreadRunnable implements Runnable {

@Override
public void run() {
try {
  System.out.println("开始烧水");
  Thread.sleep(10000);
  System.out.println("水烧开了");
} catch (Exception e) {
  e.printStackTrace();
}
}
}

class WashThreadRunnable implements Runnable {

@Override
public void run() {
try {
  for (int i = 1; i < 5; i++) {
  System.out.println("开始烧" + i + "杯水");
  Thread.sleep(1500);
  System.out.println("第" + i + "个被子洗干净了");
}
} catch (Exception e) {
  e.printStackTrace();
}

}

}

线程同步

多线程共享内存时,如果其中有一个线程对数据进行修改,那么可能出现数据不一致的情况,这时就需要使用同步来解决这个问题。 需要同步的场景:

  至少有一个共享数据

  至少有一个线程对数据进行修改

同步锁的实现

  同步块:(同步块内容一定不能太多,影响运行效率)

只有 引用类型 才能加同步锁。(基础类型的 包装类 也不能加同步锁,因为装箱类型和字符串的值都具有不可变性。意思是:改变值就会使内存地址跟着改变。内存地址改变会使同步锁就会失去共享对象,从而失效。)

多线程的通讯

通过对共享数据进行 wait() 和 notify() ,来达到多线程之间的相互牵制。

  wait():让本线程进入等待状态,同时释放同步锁。

  notify():通知等待状态的线程,继续执行。

  notifyAll():通知所有等待状态的线程,继续执行。

wait和sleep的区别 :·

  1. wait会释放同步锁,sleep不会。在我们生产者和消费者案例里头,如果使用sleep,不管生产者是否生产出商品,消费者都要睡指定长的时间。但如果使用wait,只要生产者生产出商品就可以立即执行。
  2. wait是Object的方法,sleep是Thread的静态方法。

计算机只有一个CPU,各个线程 轮流获得CPU 的使用权,才能执行任务;优先级较  的线程有 更多 获得CPU的机会,反之亦然;优先级用 整数 表示,取值范围是 1~10 ,一般情况下,线程的默认优先级都是 5 ,但是也可以通过 setPriority 和 getPriority 方法来设置或返回优先级;

多线程死锁

两个线程各自等待对方的资源,并且不释放对方想要的资源。

线程等待

两个线程,一个线程需要等待另一个线程执行后再执行。

线程状态

新建状态:使用new关键字创建线程对象,仅仅被分配了内存;

就绪状态:线程对象被创建后,等待它的start方法被调用,以获得CPU的使用权;

运行状态:执行run方法,此时的线程的对象正占用CPU;

睡眠状态:调用sleep方法,线程被暂停,睡眠时间结束后,线程回到就绪状态,睡眠状态的线程不占用CPU;

死亡状态:run方法执行完毕后,线程进入死亡状态;

阻塞状态:线程由于某些事件(如等待键盘输入)放弃CPU,暂停运行,直到线程重新进入就绪状态,才有机会转到运行状态;

sleep、yield和join

sleep和yield都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,但两者的区别在于:

  sleep给其它线程运行的机会,但不考虑其它线程的优先级;但yield只会让位给相同或更高优先级的线程;

  当线程执行了sleep方法后,将转到阻塞状态,而执行了yield方法之后,则转到就绪状态;

sleep方法有可能抛出异常,而yield则没有;

  在一般情况下,我们更建议使用sleep方法。

join方法用于等待其它线程结束,当前运行的线程可以调用另一线程的join方法,当前运行线程将转到阻塞状态,直至另一线程执行结束,它才会恢复运行。

原文地址:https://www.cnblogs.com/qhcyp/p/10651726.html