当前位置:首页 > Java技术文章 > 【java 多线程】守护线程与非守护线程的详解

【java 多线程】守护线程与非守护线程示例效果

  • 发布时间:
  • 作者:码农之家原创
  • 点击:161

这篇文章主要知识点是关于java、java多线程、守护线程、非守护线程、JAVA多线程编程实例详解 的内容,如果大家想对相关知识点有系统深入的学习,可以参阅以下java相关资源

Java程序员面试宝典

Java程序员面试宝典(第4版) 是《Java程序员面试宝典》的第4版。第4版在保留前三版数据结构、字符串处理、Java程序设计等主干内容的基础上,更新了部分程序员面试题目,内容主要取材于

查看详情

【java 多线程】守护线程与非守护线程的详解

Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当VM检测仅剩一个守护线程,而用户线程都已经退出运行时,VM就会退出,因为没有如果没有了被守护这,也就没有继续运行程序的必要了。如果有非守护线程仍然存活,VM就不会退出。

守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。

虽然守护线程可能非常有用,但必须小心确保其他所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。 因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。、

另外有几点需要注意:

  1. 1、setDaemon(true)必须在调用线程的start()方法之前设置,否则会抛出IllegalThreadStateException异常。
  2. 2、在守护线程中产生的新线程也是守护线程。
  3. 3、 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。

Timer代码示例:

package day003;

import java.util.Date;
import java.util.TimerTask;

/**
*
* 项目名称:JavaThread
* 类名称:MyTask
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:05:28
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:05:28
* 修改备注:
* @version
*
*/
public class MyTask extends TimerTask{

 /**
 * (non-Javadoc)
 * @see java.util.TimerTask#run()
 */
 public void run() {
   System.out.println("任务执行了,时间为:"+new Date());
 }
}
-----------------------------------------------------------------------------------
package day003;

import java.util.Calendar;
import java.util.Date;
import java.util.Timer;

/**
*
* 项目名称:JavaThread
* 类名称:TimerTaskRun
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:08:01
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:08:01
* 修改备注:
* @version
*
*/
public class TimerTaskRun {
 public static void main(String[] args) {
  System.out.println("系统当前时间:"+new Date());
  Calendar calendar = Calendar.getInstance();
  calendar.add(Calendar.SECOND, 10);
  Date date = calendar.getTime();
  MyTask task = new MyTask();
  Timer timer = new Timer();
  timer.schedule(task, date);
 }
}

运行结果:

系统当前时间:Mon Mar 19 15:11:47 CST 2018
任务执行了,时间为:Mon Mar 19 15:11:57 CST 2018

 任务虽然运行完了,但进程还未销毁,呈红色状态,为什么会出现这种情况呢?

【java 多线程】守护线程与非守护线程的详解

可以看一下Timer的源码

/**
  * Creates a new timer. The associated thread does <i>not</i>
  * {@linkplain Thread#setDaemon run as a daemon}.
  */
 public Timer() {
  this("Timer-" + serialNumber());
 }


 /**
  * Creates a new timer whose associated thread has the specified name.
  * The associated thread does <i>not</i>
  * {@linkplain Thread#setDaemon run as a daemon}.
  *
  * @param name the name of the associated thread
  * @throws NullPointerException if {@code name} is null
  * @since 1.5
  */
 public Timer(String name) {
  thread.setName(name);
  thread.start();
 }

 可以看出每创建一个Timer就是启动一个新的线程,那么启动的线程不是守护线程,所以一直运行。那我们该如何将 新创建的的Timer改成守护线程呢?更改如上的代码:

package day003;

import java.util.Calendar;
import java.util.Date;
import java.util.Timer;

/**
*
* 项目名称:JavaThread
* 类名称:TimerTaskRun
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:08:01
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:08:01
* 修改备注:
* @version
*
*/
public class TimerTaskRun {
 public static void main(String[] args) {
  System.out.println("系统当前时间:"+new Date());
  Calendar calendar = Calendar.getInstance();
  calendar.add(Calendar.SECOND, 10);
  Date date = calendar.getTime();
  MyTask task = new MyTask();
  Timer timer = new Timer(true);
  timer.schedule(task, date);
 }
}

运行结果如下:

系统当前时间:Mon Mar 19 15:21:42 CST 2018

 【java 多线程】守护线程与非守护线程的详解

守护线程中产生的线程也是守护线程
如下示例:

package day003;

/**
*
* 项目名称:JavaThread
* 类名称:Daemon
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:30:53
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:30:53
* 修改备注:
* @version
*
*/
public class Daemon implements Runnable {
 private Thread[] t = new Thread[10];

 /**
 * (non-Javadoc)
 * @see java.lang.Runnable#run()
 */
 public void run() {
  for (int i = 0; i < t.length; i++) {
   t[i] = new Thread(new DaemonSpawn());
   t[i].start();
   System.out.println("DaemonSpawn " + i + " started.");
  }
  for (int i = 0; i < t.length; i++) {
   System.out.println("t[" + i + "].isDaemon() = " + t[i].isDaemon() + ".");
  }
  while (true) {
   Thread.yield();
  }
 }
}
-----------------------------------------------------------------------------------
package day003;

/**
*
* 项目名称:JavaThread
* 类名称:DaemonSpawn
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:32:06
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:32:06
* 修改备注:
* @version
*
*/
public class DaemonSpawn implements Runnable {

 /**
 * (non-Javadoc)
 * @see java.lang.Runnable#run()
 */
 public void run() {
  while (true) {
   Thread.yield();
  }
 }
}
-----------------------------------------------------------------------------------
package day003;

import java.util.concurrent.TimeUnit;
/**
*
* 项目名称:JavaThread
* 类名称:DaemonRun
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:36:34
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:36:34
* 修改备注:
* @version
*
*/
public class DaemonRun {
 public static void main(String[] args) throws InterruptedException {
  Thread d = new Thread(new Daemon());
  d.setDaemon(true);//必须在启动线程前调用
  d.start();
  System.out.println("d.isDaemon() = " + d.isDaemon() + ".");
  TimeUnit.SECONDS.sleep(1);
 }
}

 运行结果如图:

d.isDaemon() = true.
DaemonSpawn 0 started.
DaemonSpawn 1 started.
DaemonSpawn 2 started.
DaemonSpawn 3 started.
DaemonSpawn 4 started.
DaemonSpawn 5 started.
DaemonSpawn 6 started.
DaemonSpawn 7 started.
DaemonSpawn 8 started.
DaemonSpawn 9 started.
t[0].isDaemon() = true.
t[1].isDaemon() = true.
t[2].isDaemon() = true.
t[3].isDaemon() = true.
t[4].isDaemon() = true.
t[5].isDaemon() = true.
t[6].isDaemon() = true.
t[7].isDaemon() = true.
t[8].isDaemon() = true.
t[9].isDaemon() = true.

 如果将mian函数中的TimeUnit.SECONDS.sleep(1);注释掉,看一下TimeUnit.SECONDS.sleep()的源码:

/**
  * Performs a {@link Thread#sleep(long, int) Thread.sleep} using
  * this time unit.
  * This is a convenience method that converts time arguments into the
  * form required by the {@code Thread.sleep} method.
  *
  * @param timeout the minimum time to sleep. If less than
  * or equal to zero, do not sleep at all.
  * @throws InterruptedException if interrupted while sleeping
  */
 public void sleep(long timeout) throws InterruptedException {
  if (timeout > 0) {
   long ms = toMillis(timeout);
   int ns = excessNanos(timeout, ms);
   Thread.sleep(ms, ns);
  }
 }

其实就是对Thread.sleep()的封装,提供了可读性更好的线程暂停操作

注释后代码运行如下:

d.isDaemon() = true.

以上结果也说明了如果用户线程全部退出了,只剩下守护线程存在了,虚拟机也就退出了。

典型的守护线程是(GC)垃圾回收线程。

package day003;


/**
*
* 项目名称:JavaThread
* 类名称:MyThread
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午3:50:12
* 修改人:liuc
* 修改时间:2018年3月19日 下午3:50:12
* 修改备注:
* @version
*
*/
public class MyThread extends Thread{
 private int i = 0;
 /**
 * (non-Javadoc)
 * @see java.lang.Thread#run()
 */

 public void run() {
  super.run();
  try {
   while (true) {
    i++;
    System.out.println("i="+i);
    Thread.sleep(1000);
   }
  } catch (Exception e) {
   e.printStackTrace();
  }

 }

 public static void main(String[] args) throws InterruptedException {
  MyThread daemonThread = new MyThread();
  daemonThread.setDaemon(true);
  daemonThread.start();
  Thread.sleep(5000);
  System.out.println("当main线程执行完毕,守护线程也停止了。");
 }
}

运行结果:

i=1
i=2
i=3
i=4
i=5
当main线程执行完毕,守护线程也停止了。

 除 JVM 内部的守护线程外,用户可以通过以下方法设置守护线程:

public final void setDaemon(boolean on)

可以通过以下方法查询线程是否为守护线程:

public final boolean isDaemon()

关于守护线程的几个要点:

1、setDaemon 方法必须在 thread.start() 之前设置,否则会抛出 java.lang.IllegalThreadStateException 异常,不能将正在运行的常规线程设置为守护线程

package day003;

/**
*
* 项目名称:JavaThread
* 类名称:TestDaemon
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午4:01:32
* 修改人:liuc
* 修改时间:2018年3月19日 下午4:01:32
* 修改备注:
* @version
*
*/
public class TestDaemon {
 public static void main(String[] args) {
  Thread thread = new Thread();
  thread.start();
  thread.setDaemon(true);
 }
}

运行结果:

Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.setDaemon(Thread.java:1359)
at day003.TestDaemon.main(TestDaemon.java:32)

 2、不是所有的应用都可以分配给 Daemon 线程来进行服务,比如读写操作或者计算逻辑,因为在 Daemon 线程还没来的及进行操作时虚拟机可能已经退出了

package day003;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
*
* 项目名称:JavaThread
* 类名称:TestDaemon2
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午4:03:22
* 修改人:liuc
* 修改时间:2018年3月19日 下午4:03:22
* 修改备注:
* @version
*
*/
public class TestDaemon2 extends Thread{

 /**
 * (non-Javadoc)
 * @see java.lang.Thread#run()
 */

 public void run() {
  super.run();
  FileOutputStream outputStream = null;
  try {
   Thread.sleep(3000);
   File file = new File("daemon.txt");
   outputStream = new FileOutputStream(file);
   outputStream.write("daemon".getBytes());
  } catch (InterruptedException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   if (outputStream != null) {
    try {
     outputStream.close();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  }
 }
 public static void main(String[] args) {
  Thread thread = new TestDaemon2();
  thread.setDaemon(true);
  thread.start();
 }

以上代码中线程功能是向工程根目录的“daemon.txt”文件写入字符串“daemon”,实际运行结果发现并未成功写入,且未报任何错误,原因是写入文件的线程被设置为守护线程,该线程还在 sleep 过程中时所有用户线程就全部结束了,守护线程也会随着 JVM 一起退出。

如果将上面代码中的thread.setDaemon(true);注释掉,

 public static void main(String[] args) {
  Thread thread = new TestDaemon2();
  //thread.setDaemon(true);
  thread.start();
 }

不将线程设置为守护线程可以在工程根目录的“daemon.txt”文件中看到字符串“daemon”

【java 多线程】守护线程与非守护线程的详解

示例2:

package day003;

/**
*
* 项目名称:JavaThread
* 类名称:CustomThread
* 类描述:
* 创建人:liuc
* 创建时间:2018年3月19日 下午4:16:42
* 修改人:liuc
* 修改时间:2018年3月19日 下午4:16:42
* 修改备注:
* @version
*
*/
public class CustomThread extends Thread {

 /**
 * (non-Javadoc)
 * @see java.lang.Thread#run()
 */
 public void run() {
  super.run();
  for (int i = 0; i < 100; i++) {
   System.out.println("Daemon Thread : " + i);
  }
 }

 public static void main(String[] args) {
  Thread daemonThread = new CustomThread();
  daemonThread.setDaemon(true);
  Thread userThread = new Thread();
  daemonThread.start();
  userThread.start();
 }
}

多次执行示例2代码,控制台要么不打印任何信息,要么打印一部分循环的输出信息就结束了,从运行结果可以看出,守护线程并未执行完成所有循环就结束了,因为用户线程在守护线程执行循环的过程中就已全部结束,守护线程也随着 JVM 一起结束。

以上所述是小编给大家介绍的java守护线程与非守护线程详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对码农之家网站的支持!

JAVA多线程编程实例详解

本文实例讲述了JAVA多线程编程。分享给大家供大家参考,具体如下:

  • 进程是系统进行资源调度和分配的一个独立单位。
  • 进程的特点
    独立性:进程是系统中独立存在的实体,拥有自己的独立资源和私有空间。在没有经过进程本身允许的情况下,不能直接访问其他进程。
    动态性:进程与程序的区别在于,前者是一个正在系统中活动的指令,而后者仅仅是一个静态的指令集合
    并发性:多个进程可以在单个处理器上并发执行,而不受影响。

并发性和并行性的区别:
并行性:在同一时刻,有多条指令在多个处理器上同时执行(多个CPU)
并发性:在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果(单核)。

  • 通过继承Thread类来创建并启动多线程
public class Deom_1 extends Thread{

  public void run(){
      super.run();
      System.out.println("MyThread01");
    }


  public static void main(String[] args){
    Deom_1 demo=new Deom_1();
    demo.setName("Demo_1");
    demo.start();
    System.out.println("当前线程的名字:"+Thread.currentThread().getName()+" 运行结束");
  }
}

多次调用start会抛出java.lang.IllegalThreadStateException异常
如果只调用run(),不调用start()方法,就相当于调用了一个普通的函数,实际上还是在同一个线程中运行的run()方法。

  • 多线程编程时不要忘记了Java程序运行时默认的主线程,main方法的方法体就是主线程的线程执行体
  • 使用继承Thread类的方法来创建线程类,多条线程之间无法共享线程的实例变量
  • 实现Runnable接口创建线程类
//通过实现Runnable接口来创建线程类
public class SecondThread implements Runnable
{
  private int i ;
  //run方法同样是线程执行体
  public void run()
  {
    for ( ; i < 20 ; i++ )
    {
      //当线程类实现Runnable接口时,
      //如果想获取当前线程,只能用Thread.currentThread()方法。
      System.out.println(Thread.currentThread().getName() + " " + i);
    }
  }

  public static void main(String[] args) 
  {
    for (int i = 0; i < 50; 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();
      }
    }
  }
}
  • 采用实现继承方式的多线程:
    编程相对简单
    多条线程之间无法共享线程类实例变量
    继承Thread类之后就不能继承其他的父类

  • 采用实现接口的方式的多线程:
    线程类只是实现了Runnable接口,还可以继承其他类
    在这种方式下,可以多个线程共享同一个对象,非常适合多个相同线程来处理同一份资源的情况。
    编程稍稍复杂一点

  • 线程的生命周期:新建(NEW)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)

  • 当程序使用new关键字创建一个线程后,该线程就处于新建状态;当线程对象调用了start()方法之后,该线程就处于就绪状态;如果处于就绪状态的线程获得了CPU,开始执行run方法的线程执行体,则该线程就处于运行状态;但是它不可能一直处于运行状态,可能会被中断进入阻塞状态;线程运行结束后就会进入到死亡状态。

  • 线程进入阻塞状态的情况:
    线程调用了sleep方法主动放弃所占有的资源
    线程调用了一个阻塞式IO方法,在该方法返回的时候被阻塞
    线程尝试获取一个同步监听器,但是被其他线程占有
    在等待某个通知
    调用线程suspend方法挂起,不推荐使用容易造成死锁。

  • 线程进入就绪状态的情况:
    调用sleep方法的线程经过了指定时间
    线程调用的阻塞式IO方法已经返回
    线程成功获取试图取得同步监听器
    线程在等待某个通知,其他线程发出一个通知
    处于挂起状态的线程调用了resume恢复方法

进入阻塞状态的线程在获得执行机会后重新进入就绪状态,而不是运行状态

  • 线程进入死亡状态的情况:
    run()方法执行完成,线程正常结束
    线程抛出一个未捕获的异常或者错误
    直接调用该线程的stop方法来结束该线程——该方法容易导致死锁,通常不推荐。

当主线程结束的时候,其他线程不受影响。一旦子线程启动它就拥有和主线程一样的地位。

  • 不要试图对一个已经死亡的线程调用start()方法使它重新启动,会抛出IllegalThreadStateException异常。调用线程的isAlive()方法可以测试某条线程是否死亡。

  • JAVA中控制线程的方法:
    join线程
    后台线程
    线程睡眠:sleep
    线程让步:yield
    改变线程优先级

  • join线程
    join():等待被join的线程执行完成
    join(long millis):等待被join的线程时间最长millis毫秒
    join(long millis, int nanos):等待被join的线程的时间最长millis毫秒加上nanos微秒

  • 后台线程:任务是给其他线程提供服务,成为“后台线程”、“守候线程”。如果说有的前台线程死亡,后台线程会自动死亡。

调用Thread类的setDaemon(true)方法可以将指定线程设置为后台线程。该方法一定要在启动线程之前设置,否则会发生异常。同时提供isDaemon()方法判断是否是后台线程。主线程一般默认为前台线程前台线程创建的子线程默认是前台,后台线程创建的子线程默认是后台。

  • 改变线程的优先级
PriorityTest low = new PriorityTest("低级");
low.start();
System.out.println("创建之初的优先级:" + low.getPriority());
//设置该线程为最低优先级
low.setPriority(Thread.MIN_PRIORITY);
PriorityTest high = new PriorityTest("高级");
high.start();
System.out.println("创建之初的优先级:" + high.getPriority());
//设置该线程为最高优先级
high.setPriority(Thread.MAX_PRIORITY);

每个线程的默认优先级都与创建它的父线程具有相同的线程,在默认的情况下,main线程具有普通优先级。

  • 线程让步
    yield()方法是Thread提供的一个静态方法,可以让当前正在执行的线程暂停转入就绪状态。等待下一次的重新调度。
    实际上,当某个线程调用了yield()方法后只有优先级相同或者高于当前线程的其他就绪状态的线程才会获得执行的机会。

  • sleep和yield方法的区别
    1、sleep方法暂停当前线程后,会给其他线程机会执行,不会理会其他线程的优先级。但是yield方法只会给优先级相同或者更高的线程
    2、sleep方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态。而yield方法直接转入就绪状态
    3、sleep方法会抛出InterruptedException异常,所以调用时需要显示捕获异常,yield方法不会抛出任何异常
    4、sleep方法比yield方法具有更多的移植性,通常不依靠yield方法控制并发线程执行。

  • 如果多个线程共同访问1个对象中的实例变量,则有可能出现”非线程安全”

class SelfPrivateNum {
  private int num = 0;

  public void addI(String username) {
    try {

      if (username.equals("a")) {
        num = 100;
        System.out.println("a set over!");
        Thread.sleep(2000);
      } else {
        num = 200;
        System.out.println("b set over!");
      }
      System.out.println(username + " num=" + num);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

}

class ThreadAA extends Thread {

  private SelfPrivateNum numRef;

  public ThreadAA(SelfPrivateNum numRef) {
    super();
    this.numRef = numRef;
  }

  @Override
  public void run() {
    super.run();
    numRef.addI("a");
  }

}

class ThreadBB extends Thread {

  private SelfPrivateNum numRef;

  public ThreadBB(SelfPrivateNum numRef) {
    super();
    this.numRef = numRef;
  }

  @Override
  public void run() {
    super.run();
    numRef.addI("b");
  }

}

public class RunUnsafe {

  public static void main(String[] args) {

    SelfPrivateNum numRef = new SelfPrivateNum();

    ThreadAA athread = new ThreadAA(numRef);
    athread.start();

    ThreadBB bthread = new ThreadBB(numRef);
    bthread.start();

  }

}
  • 同步方法:就是使用synchronized关键字来修饰某个方法。当多个线程调用这个方法的时,以排队的方式进行处理。同步方法不具有继承性
class SelfPrivateNum2 {
  private int num = 0;

  public synchronized void addI(String username) {
    try {

      if (username.equals("a")) {
        num = 100;
        System.out.println("a set over!");
        Thread.sleep(2000);
      } else {
        num = 200;
        System.out.println("b set over!");
      }
      System.out.println(username + " num=" + num);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

}

关键字synchronized取的是锁都是对象锁,而不是代码或者是方法当作锁。当多个线程访问的是同一个对象的同步方法的时候是排队的,而当多个线程访问多个对象的同步方法的时候运行的顺序是异步的。

  • A线程先持有object对象的锁,B线程可以以异步的方式调用object对象总的非synchronizaed类型的方法
  • A线程先持有object对象的锁,B线程如果调用object的synchronizaed类型的方法则需要等待,也就是同步。

  • 脏读

为了避免数据出现交叉的情况,使用synchronized关键字来进行同步。虽然在赋值时进行了同步,但是可能在取值的时候出现脏读(dirtyRead)的现象。发生脏读的情况是在读取实例变量时。出现脏读是应为getValue方法不是同步方法,解决方法可以定义为同步方法。

  • synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象进行加锁。
//线程开始执行同步代码块之前,必须先获得对同步监控器的锁定
synchronized (Obj){
  ………
  //此处的代码就是同步代码块  
}

通常使用可能被并发访问的共享资源充当同步监视器。

  • 对同步监视器释放的情况:
    当前线程的同步方法或代码块执行结束
    当前线程的同步方法或代码块中遇到break、return终止了该代码块、方法的继续执行
    当前线程的同步方法或代码块中出现未处理的Error或Exception
    当前线程的同步方法或代码块中执行了同步监控器对象的wait()方法
  • 以下情况不会释放同步锁:
    当前线程的同步方法或代码块调用 Thread.sleep(),Thread.yield()方法来暂停当前线程执行
    当前线程的同步方法或代码块时,其他线程调用了该线程的suspend方法让该线程挂起

  • LOCK锁

Class A{
   private final ReentrantLock lock= new ReentrantLock ();
   //需要保证线程安全的方法
   public void m(){
    //加锁
    lock.lock();
    try{  
      ……………
    }
    finally{
     lock.unlock();
    }
  }
}   

同步方法的比较
1、同步方法和同步代码块使用与竞争资源相关的、隐式的同步监视器,并且强制要求加锁和释放锁要出现在同一个块结构中,而且当获取多个锁的时候,他们必须按照相反的顺序依次释放。
2、Lock方法不仅可以使用与非结构快中,还可以试图中断锁和再次加锁的功能。被Lock加锁的代码中还可以嵌套调用。
3、资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍。

  • Volatile关键字
    强制性从公共堆栈中(存放实例)中取得修饰的变量的值,而不是从线程的私有数据栈中取得变量的值。不能保证不会出现脏读的情况,需要用关键字synchronized来解决。

  • ThreadLocal类
    ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响。ThreadLocal提供资源的共享对象!
    T get():返回此线程局部变量中当前线程副本的值
    void remove():删除此线程局部变量中当前副本的值
    void set(T value):设置此线程局部变量中当前线程副本的值

如果需要进行多个线程之间共享资源,以达到线程之间的通信功能,就使用同步机制;如果仅仅需要隔离多个线程之间的共享冲突,可以使Threadlocal。

  • 死锁:当两个线程相互等待对象释放同步监视器的时候就会发生死锁。

    使用一种称为资源排序的简单技术可以轻易避免死锁。

  • 等待通知机制

方法wait()的作用是使当前执行代码线程进行等待,是Object类的方法,该方法用来将当前线程置于“阻塞队列”中,并在wait()所在的代码处停止执行,直到接到通知被唤醒。

在调用wait()之前,线程必须持有该对象的对象级别锁,只能在同步方法或者是同步块中调用此方法。在执行wait方法后,当前线程释放锁。进入和其他线程竞争重新获得锁。如果调用wait方法没有持有锁,则会抛出异常。

方法notify()用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随便选择一个呈wait状态的线程。

方法nofity()也是在同步方法或者同步块中调用,调用前同样要获得对象的对象级别所,否则抛出异常。在执行notify方法后,当前线程不会马上释放该对象锁,要等到执行notify方法的线程将程序执行完才能会释放锁。

方法notifyAll()方法可以使正在等待队列中等待同一共享资源的”全部”线程从等待状态退出,进入可运行状态。

public class Test3 {
  //main方法中有三个等待线程,一个唤醒线程,一个唤醒线程只能唤醒一个等待线程,程序出现阻塞
  public static void main(String[] args) throws InterruptedException {

    Object lock = new Object();

    ThreadA a = new ThreadA(lock);
    new ThreadA(lock).start();
    new ThreadA(lock).start();
    new ThreadA(lock).start();

    Thread.sleep(1000);

    NotifyThread notifyThread = new NotifyThread(lock);
    notifyThread.start();

  }

}

class ThreadA extends Thread {
  private Object lock;

  public ThreadA(Object lock) {
    super();
    this.lock = lock;
  }

  @Override
  public void run() {
    Service service = new Service();
    service.testMethod(lock);
  }

}

class NotifyThread extends Thread {
  private Object lock;

  public NotifyThread(Object lock) {
    super();
    this.lock = lock;
  }

  @Override
  public void run() {
    synchronized (lock) {
      //notify()只能唤醒一个线程,nofiyAll()唤醒所有的等待线程
      lock.notify();
//     lock.notifyAll();
    }
  }

}

class Service {

  public void testMethod(Object lock) {
    try {
      synchronized (lock) {
        System.out.println("begin wait() ThreadName="
            + Thread.currentThread().getName());
        lock.wait();
        System.out.println(" end wait() ThreadName="
            + Thread.currentThread().getName());
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

}
  • 使用条件变量协调线程

在没有使用synchronized关键字保证同步,而采用Lock的程序。Java提供了condition类来保持协调。Lock替代synchronized,Condition替代同步监视器功能。
await: 类似于wait。
signal:唤醒在此Lock对象上等待的单个线程,选择是任意的。
signalAll:唤醒在此Lock对象上等待的所有线程,选择是任意的。

public class Account
{
  //显示定义Lock对象
  private final Lock lock = new ReentrantLock();
  //获得指定Lock对象对应的条件变量
  private final Condition cond = lock.newCondition(); 

  private String accountNo;
  private double balance;

  //标识账户中是否已经存款的旗标
  private boolean flag = false;

  public Account(){}

  public Account(String accountNo , double balance)
  {
    this.accountNo = accountNo;
    this.balance = balance;
  }

  public void setAccountNo(String accountNo)
  {
    this.accountNo = accountNo;
  }
  public String getAccountNo()
  {
     return this.accountNo;
  }

  public double getBalance()
  {
     return this.balance;
  }
  public void draw(double drawAmount)
  {
    //加锁
    lock.lock();
    try
    {
      //如果账户中还没有存入存款,该线程等待
      while(!flag)
      {
        cond.await();
      }
        //执行取钱操作
        System.out.println(Thread.currentThread().getName() + 
          " 取钱:" + drawAmount);
        balance -= drawAmount;
        System.out.println("账户余额为:" + balance);
        //将标识是否成功存入存款的旗标设为false
        flag = false;
        //唤醒该Lock对象对应的其他线程
        cond.signalAll();
    }
    catch (InterruptedException ex)
    {
      ex.printStackTrace();
    }
    //使用finally块来确保释放锁
    finally
    {
      lock.unlock();
    }
  }
  public void deposit(double depositAmount)
  {
    lock.lock();
    try
    {
      //如果账户中已经存入了存款,该线程等待
      while(flag)
      {
        cond.await();        
      }

        //执行存款操作
        System.out.println(Thread.currentThread().getName() + 
          " 存款:" + depositAmount);
        balance += depositAmount;
        System.out.println("账户余额为:" + balance);
        //将标识是否成功存入存款的旗标设为true
        flag = true;
        //唤醒该Lock对象对应的其他线程
        cond.signalAll();
    }
    catch (InterruptedException ex)
    {
      ex.printStackTrace();
    }
    //使用finally块来确保释放锁
    finally
    {
      lock.unlock();
    }
  }

  public int hashCode()
  {
    return accountNo.hashCode();
  }
  public boolean equals(Object obj)
  {
    if (obj != null && obj.getClass() == Account.class)
    {
      Account target = (Account)obj;
      return target.getAccountNo().equals(accountNo);
    }
    return false;
  }
}

使用notify()/notifyAll()方法进行通知时,被通知的线程却是JVM随机选择的。当notifyAll()通知所有WAITING线程,没有选择权,会出现相当大的效率问题。但是ReentrantLock结合Condition类可以”选择性通知”。Condition可以实现唤醒部分指定线程,这样有助于程序运行的效率。

  • Callable和Future

从JDK1.5之后,Java提供了Callable接口,实际上就是Runnable接口的增强版。提供call方法作为线程执行体,但是功能更加强大。
Callable接口不是Runnable接口,不能直接作为Thread的target,有返回值得call方法也不能直接运行。这里需要一个包装器Future。

import java.util.concurrent.*;

class RtnThread implements Callable<Integer>
{
  //实现call方法,作为线程执行体
  public Integer call()
  {
    int sum = 0;
    int i=0;
    for ( ; i < 10 ; i++ )
    {
      System.out.println(Thread.currentThread().getName()+ " 的循环变量i的值:" + i); 
      sum+=i;
    }
    //call()方法可以有返回值

    return sum;
  }
} 

public class CallableTest
{
  public static void main(String[] args) 
  {
    //创建Callable对象
    RtnThread rt = new RtnThread();
    //使用FutureTask来包装Callable对象
    FutureTask<Integer> task = new FutureTask<Integer>(rt);
    for (int i = 0 ; i < 10 ; i++)
    {
      System.out.println(Thread.currentThread().getName()
        + " 的循环变量i的值:" + i);
      if (i == 5)
      {
        //实质还是以Callable对象来创建、并启动线程
        new Thread(task , "有返回值的线程").start();
      }
    }
    try
    {
      //获取线程返回值
      System.out.println("子线程的返回值:" + task.get());          
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
  }
}
  • 线程池
import java.util.concurrent.*;

//实现Runnable接口来定义一个简单的
class TestThread implements Runnable
{
  public void run()
  {
    for (int i = 0; i < 10 ; i++ )
    {
      System.out.println(Thread.currentThread().getName()
        + "的i值为:" + i);
    }
  }
}

public class ThreadPoolTest
{
  public static void main(String[] args) 
  {
    //创建一个具有固定线程数(6)的线程池
    ExecutorService pool = Executors.newFixedThreadPool(6);
    //向线程池中提交3个线程
    pool.execute(new TestThread());
    Future f1=pool.submit(new TestThread());
    Future f2=pool.submit(new TestThread());

    Future<Integer> f3=pool.submit(new RtnThread());

    try
    {
      if(f1.get()==null&&f2.get()==null&&f3.get()!=null){
        System.out.println("执行完毕!");
        System.out.println(f3.get());

      }
      //获取线程返回值

    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
    //关闭线程池
    pool.shutdown();
  }
}

更多java相关内容感兴趣的读者可查看本站专题:《Java进程与线程操作技巧总结》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总》

希望本文所述对大家java程序设计有所帮助。

以上就是本次给大家分享的关于Java的全部知识点内容总结,大家还可以在下方相关文章里找到Java ArrayList底层实现代码分、 Spring Boot日志控制代码实例、 SpringBoot项目集成日志的方、 等java文章进一步学习,感谢大家的阅读和支持。

上一篇:实例详解mybatis的插件机制

下一篇:Java实现随机10道10以内加减法的代码详解

展开 +

收起 -

java多线程 相关内容
解密搜索引擎技术实战 Lucene Java精华版

本书总结搜索引擎相关理论与实际解决方案,并给出了Java实现,包括总体介绍部分、爬虫部分、自然语言处理部分、全文检索部分以及相关案例分析,欢迎下载

查看详情
Java攻略 Java常见问题的简单解法

这书以案例方式撰写,包含Java8和Java9的新特点,并得出了70多个能够 用以具体开发设计的实例,致力于让用户把握怎样运用这种新特点来处理开发设计中碰到的各种各样难题。这书致力于让用

查看详情
JavaScript网页动画设计

JavaScript网页动画设计 由业界先进的动画库Velocity。js的作者所著,书中内容共分为8章,简明扼要地总结了在网页上使用动画的技术技巧,让读者掌握如何有效利用动画实现无与伦比的用户体验

查看详情
JavaScript权威指南 查看详情
你不知道的JavaScript 下卷

本书讲解JavaScript开发人员不求甚解的大趋势,深入理解语言内部的机制,全面介绍JavaScript中常被人误解和忽视的重要知识点,分为上中下三套,欢迎下载

查看详情
Java语言程序设计教程

本书基础知识和实际应用完美结合,经典案例完美诠释,以培养学生的面向对象的思维和面向对象的编程技术为核心,通过精选案例详细地介绍Java的基础知识和实用核心技术

查看详情
JavaScript ES6函数式编程入门经典

本书使用JavaScript ES6带你学习函数式编程。你将学习柯里化、偏函数、高阶函数以及Monad等概念。具有一定的参考价值,感兴趣的小伙伴们可以参考一下

查看详情
java多线程 学习笔记
网友NO.337996

Java编程之多线程死锁与线程间通信简单实现代码

死锁定义 死锁是指两个或者多个线程被永久阻塞的一种局面,产生的前提是要有两个或两个以上的线程,并且来操作两个或者多个以上的共同资源;我的理解是用两个线程来举例,现有线程A和B同时操作两个共同资源a和b,A操作a的时候上锁LockA,继续执行的时候,A还需要LockB进行下面的操作,这个时候b资源在被B线程操作,刚好被上了锁LockB,假如此时线程B刚好释放了LockB则没有问题,但没有释放LockB锁的时候,线程A和B形成了对LockB锁资源的争夺,从而造成阻塞,形成死锁;具体其死锁代码如下: public class MyDeadLockTest { public static void main(String[] args){ Object obj1 = new Object(); Thread thread1 = new Thread(new DeadRes(true,obj1)); Thread thread2 = new Thread(new DeadRes(false,obj1)); thread1.start(); thread2.start(); }}class DeadRes implements Runnable{ boolean flag; Object obj; public DeadRes(boolean flag, Object ob……

网友NO.213071

Java多线程回调方法实例解析

所谓回调,就是客户程序C调用服务程序S中的某个方法A,然后S又在某个时候反过来调用C中的某个方法B,对于C来说,这个B便叫做回调方法。 下面看一个实际例子来理解: 本示例设置一个提问者,一个回答者,而回答者需要回答提问者一个很深奥的问题时,这时需要很多时间去查找,提问者又开始做其他的事情, 等回答者找到答案后,再把答案告诉提问者。 一.提问者的类 涉及到长时间的思考,要sleep,要继承Thread package com.xykj.thread;public class XiaoZhang extends Thread { // 回答1+1,很简单的问题不需要线程 public int add(int num1, int num2) { return num1 + num2; } // 重写run方法 @Override public void run() { // 回答地球为什么是圆的 askquestion(); super.run(); } // 回调接口的创建,里面要有一个回调方法 //回调接口什么时候用呢?这个思路是最重要的 // public static interface CallPhone { p……

网友NO.813828

java多线程中线程封闭详解

线程封闭的概念 访问共享变量时,通常要使用同步,所以避免使用同步的方法就是减少共享数据的使用,这种技术就是线程封闭。 实现线程封闭的方法 1:ad-hoc线程封闭 这是完全靠实现者控制的线程封闭,他的线程封闭完全靠实现者实现。也是最糟糕的一种线程封闭。所以我们直接把他忽略掉吧。 2:栈封闭 栈封闭是我们编程当中遇到的最多的线程封闭。什么是栈封闭呢?简单的说就是局部变量。多个线程访问一个方法,此方法中的局部变量都会被拷贝一分儿到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。 3:ThreadLocal封闭 使用ThreadLocal是实现线程封闭的最好方法,有兴趣的朋友可以研究一下ThreadLocal的源码,其实我们可以理解ThreadLocal内部维护了一个……

网友NO.122626

关于Java多线程编程锁优化的深入学习

正文 并发环境下进行编程时,需要使用锁机制来同步多线程间的操作,保证共享资源的互斥访问。加锁会带来性能上的损坏,似乎是众所周知的事情。然而,加锁本身不会带来多少的性能消耗,性能主要是在线程的获取锁的过程。如果只有一个线程竞争锁,此时并不存在多线程竞争的情况,那么JVM会进行优化,那么这时加锁带来的性能消耗基本可以忽略。因此,规范加锁的操作,优化锁的使用方法,避免不必要的线程竞争,不仅可以提高程序性能,也能避免不规范加锁可能造成线程死锁问题,提高程序健壮性。下面阐述几种锁优化的思路。 一、尽量不要锁住方法 在普通成员函数上加锁时,线程获得的是该方法所在对象的对象锁。此时整个对象都会被锁住。这也意味着,如果这个对象提供的多个同步方法是针对不同业务的,那么由于整个对象被锁住……

网友NO.227649

java使用多线程读取超大文件

接上次写的“JAVA读取超大文件”。在读取超过10G的文件时会发现一次读一行的速度实在是不能接受,想到使用多线程+FileChannel来做一个使用多线程版本。 基本思路如下: 1.计算出文件总大小 2.分段处理,计算出每个线程读取文件的开始与结束位置 (文件大小/线程数)*N,N是指第几个线程,这样能得到每个线程在读该文件的大概起始位置 使用"大概起始位置",作为读文件的开始偏移量(fileChannel.position("大概起始位置")),来读取该文件,直到读到第一个换行符,记录下这个换行符的位置,作为该线程的准确起 始位置.同时它也是上一个线程的结束位置.最后一个线程的结束位置也直接设置为-1 3.启动线程,每个线程从开始位置读取到结束位置为止 读文件工具类 import java.io.*;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;import java.util.Observable; /** * Created with IntelliJ IDEA. *……

<
1
>

Copyright 2018-2020 xz577.com 码农之家

本站所有电子书资源不再提供下载地址,只分享来路

免责声明:网站所有作品均由会员网上搜集共同更新,仅供读者预览及学习交流使用,下载后请24小时内删除

版权投诉 / 书籍推广 / 赞助:QQ:520161757