java多线程详细总结

一、Thread.start()与Thread.run()的区别

通过调用Thread类的start()方法来启动一个线程,这时此线程是处于就绪状态,并没有运行。然后通过此Thread类调用方法run()来完成其运行操作的,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程终止,而CPU再运行其它线程。

而如果直接用Run方法,这只是调用一个方法而已,程序中依然只有“主线程”这一个线程,并没有开辟新线程,其程序执行路径还是只有一条,这样就没有达到写线程的目的。

测试代码如下


代码如下:

public class MyThread implements Runnable {
public void run() {
System.err.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread thread = new MyThread();
Thread t1 = new Thread(thread, "Thread-1");
Thread t2 = new Thread(thread, "Thread-2");
t1.run();
t2.run();
}
}

输出结果为

>>当前进程为:main

>>当前进程为:main

改为用start方法:


代码如下:

package thread;
public class MyThread implements Runnable {
public void run() {
System.err.println(">>当前进程为:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
MyThread thread = new MyThread();
Thread t1 = new Thread(thread, "Thread-1");
Thread t2 = new Thread(thread, "Thread-2");
t1.start();
t2.start();
}
}

结果为:

>>当前进程为:Thread-1

>>当前进程为:Thread-2

二、ThreadLocal类详解

ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

下面是线程局部变量(ThreadLocal variables)的关键点:

一个线程局部变量(ThreadLocal variables)为每个线程方便地提供了一个单独的变量。在多个线程操作该变量时候能够互不影响,因为每个线程操作的实际上是改变量的副本。ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联线程。当多个线程访问ThreadLocal实例时,每个线程维护ThreadLocal提供的独立的变量副本。

下面是测试代码,用于测试:作用于一个对象上面的三个线程来操作同一个ThreadLoacl对象(integer 类型),看是否会出现脏读等现象:


代码如下:

public class Test implements Runnable {
private static ThreadLocal<Integer> num = new ThreadLocal<Integer>();
public void run() {
num.set(0);
for (int i = 0; i < 3; i++) {
num.set(num.get() + 1);
System.out.println(Thread.currentThread().getName() + ":num="
+ num.get());
}
}
public static void main(String[] args) {
Test test = new Test();
Thread t1 = new Thread(test, "Thread-1");
Thread t2 = new Thread(test, "Thread-2");
Thread t3 = new Thread(test, "Thread-3");
t1.start();
t2.start();
t3.start();
}
}

运行结果如下:

Thread-3:num=1
Thread-2:num=1
Thread-1:num=1
Thread-2:num=2
Thread-3:num=2
Thread-2:num=3
Thread-1:num=2
Thread-1:num=3
Thread-3:num=3

从上面可以看出,完全没有出现脏读等的现象,因此ThreadLocal线程安全。

常用的使用:当DAO类作为一个单例类时,数据库链接(connection)被每一个线程独立的维护,互不影响。

可以用来控制session的创建和使用,如下ThreadLocal<Session> session = new ThreadLocal<Session>();

ThreadLoacal与同步机制的比较:

1、在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

2、而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

3、概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

三、InvalidMonitorStateException异常

调用wait()/notify()/notifyAll()中的任何一个方法时,如果当前线程没有获得该对象的锁,那么就会抛出IllegalMonitorStateException的异常(也就是说程序在没有执行对象的任何同步块或者同步方法时,仍然尝试调用wait()/notify()/notifyAll()时)。由于该异常是RuntimeExcpetion的子类,所以该异常不一定要捕获(尽管你可以捕获只要你愿意).作为RuntimeException,此类异常不会在wait(),notify(),notifyAll()的方法签名提及。

如下代码,划线的部分会发生该异常,因为没有对该对象执行同步操作。


代码如下:

public class Common implements Runnable {
public synchronized void method1() throws InterruptedException {
Thread.sleep(1000);
System.out.println("Method 1 called");
Thread.sleep(1000);
System.out.println("Method 1 done");
}
public synchronized void method2() throws InterruptedException {
Thread.sleep(1000);
System.err.println("Method 2 called");
Thread.sleep(1000);
System.err.println("Method 2 done");
}
public void run() {
System.out.println("Running " + Thread.currentThread().getName());
try {
if (Thread.currentThread().getName().equals("Thread-1")) {
this.wait(1000);
method1();
} else {
method2();
notifyAll();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Common c = new Common();
Thread t1 = new Thread(c, "Thread-1");
Thread t2 = new Thread(c, "Thread-2");
t1.start();
t2.start();
}
}

改为一下代码,划线部分:


代码如下:

public class Common implements Runnable {
public synchronized void method1() throws InterruptedException {
Thread.sleep(1000);
System.out.println("Method 1 called");
Thread.sleep(1000);
System.out.println("Method 1 done");
}
public synchronized void method2() throws InterruptedException {
Thread.sleep(1000);
System.err.println("Method 2 called");
Thread.sleep(1000);
System.err.println("Method 2 done");
}
public void run() {
System.out.println("Running " + Thread.currentThread().getName());
try {
if (Thread.currentThread().getName().equals("Thread-1")) {
synchronized(this){
this.wait(1000);
}
method1();
} else {
method2();
synchronized (this) {
notifyAll();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Common c = new Common();
Thread t1 = new Thread(c, "Thread-1");
Thread t2 = new Thread(c, "Thread-2");
t1.start();
t2.start();
}
}

四、sleep()和wait()和suspend()的区别

区别一:

sleep是Thread类的方法,是线程用来控制自身流程的,比如有一个要报时的线程,每一秒中打印出一个时间,那么我就需要在print方法前面加上一个sleep让自己每隔一秒执行一次。就像个闹钟一样。 sleep() 指示当前线程暂停执行指定时间,把执行机会让给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。

wait是Object类的方法,用来线程间的通信,这个方法会使当前拥有该对象锁的线程等待,直到其他线程调用notify方法时再醒来,不过你也可以给它指定一个时间,自动醒来。这个方法主要是用在不同线程之间的调度。对象调用wait()方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。

区别二 :
 
调用wait方法会释放当前线程的锁,其实线程间的通信是靠对象来管理的,所有操作一个对象的线程是这个对象通过自己的wait方法来管理的。就好像这个对象是电视机,三个人是三个线程,那么电视机的遥控器就是这个锁,假如现在A拿着遥控器,电视机调用wait方法,那么A就交出自己的遥控器,由jVM虚拟机调度,遥控器该交给谁。

调用sleep方法不会释放锁,因为sleep()是一个线程用于管理自己的方法,不涉及线程通信。还是上面的例子,如果A拿遥控器的期间,他可以用自己的sleep每隔十分钟调一次台,而在他调台休息的十分钟期间,遥控器还在他的手上,其他人无法获得遥控器。

suspend() 方法容易发生死锁。调用suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁

在以下情况下,持有锁的线程会释放锁:

1. 执行完同步代码块。

2. 在执行同步代码块的过程中,遇到异常而导致线程终止。

3. 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进行对象的等待池。

在以下情况下,线程虽然停止执行,但是线程不会释放锁:

1. 在执行同步代码块的过程中,执行了Thread.sleep()方法,当前线程放弃CPU,开始睡眠,在睡眠中不会释放锁。

2. 在执行同步代码块的过程中,执行了Thread.yield()方法,当前线程放弃CPU,但不会释放锁。

3. 在执行同步代码块的过程中,其他线程执行了当前对象的suspend()方法,当前线程被暂停,但不会释放锁。

五、在静态方法上使用同步

JAVA只识别两种类型的锁:对象锁和类锁。

同步静态方法时会获取该类的"Class”对象,所以当一个线程进入同步的静态方法中时,线程监视器获取类本身的锁,对整个类加锁,其它线程不能进入这个类的任何静态同步方法。它不像实例方法,因为多个线程可以同时访问不同实例同步实例方法。测试代码如下:


代码如下:

public class Common implements Runnable {
public synchronized static void method1() throws InterruptedException {
Thread.sleep(1000);
System.out.println("Method 1 called");
Thread.sleep(1000);
System.out.println("Method 1 done");
}
public synchronized static void method2() throws InterruptedException {
Thread.sleep(1000);
System.err.println("Method 2 called");
Thread.sleep(1000);
System.err.println("Method 2 done");
}
public void run() {
System.out.println("Running " + Thread.currentThread().getName());
try {
if (Thread.currentThread().getName().equals("Thread-1")) {
method1();
} else {
method2();
// Thread.currentThread().notify();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
//以下代码创建了不同的对象上的不同线程,用来测试对于同一个类,会不会有锁
Common c1 = new Common();
Common c2 = new Common();
Thread t1 = new Thread(c1, "Thread-1");
Thread t2 = new Thread(c2, "Thread-2");
t1.start();
t2.start();
}
}

执行结果如下:
Running Thread-2
Running Thread-1
Method 2 called
Method 2 done
Method 1 called
Method 1 done

六、在一个对象上两个线程可以在同一时间分别调用两个不同的同步实例方法吗?

不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。所以只有执行完该方法释放对象锁后才能执行其它同步方法。测试代码如下:


代码如下:

public class Common implements Runnable {
public synchronized void method1() throws InterruptedException {
Thread.sleep(1000);
System.out.println("Method 1 called");
Thread.sleep(1000);
System.out.println("Method 1 done");
}
public synchronized void method2() throws InterruptedException {
Thread.sleep(1000);
System.err.println("Method 2 called");
Thread.sleep(1000);
System.err.println("Method 2 done");
}
public void run() {
System.out.println("Running " + Thread.currentThread().getName());
try {
if (Thread.currentThread().getName().equals("Thread-1")) {
method1();
} else {
method2();
// Thread.currentThread().notify();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Common c = new Common();
Thread t1 = new Thread(c, "Thread-1");
Thread t2 = new Thread(c, "Thread-2");
t1.start();
t2.start();
//以下代码作为对比,创建不同的对象,则就不会受对象锁的干扰了
//Common c1 = new Common();
//Common c2 = new Common();
//c1.start();
//c2.start();
}
}

执行结果如下:
Running Thread-1
Running Thread-2
Method 1 called
Method 1 done
Method 2 called
Method 2 done

(0)

相关推荐

  • java基本教程之join方法详解 java多线程教程

    本章涉及到的内容包括:1. join()介绍2. join()源码分析(基于JDK1.7.0_40)3. join()示例 1. join()介绍join() 定义在Thread.java中.join() 的作用:让"主线程"等待"子线程"结束之后才能继续运行.这句话可能有点晦涩,我们还是通过例子去理解: 复制代码 代码如下: // 主线程public class Father extends Thread {    public void run() {     

  • Java多线程的用法详解

    1.创建线程 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口.在使用Runnable接口时需要建立一个Thread实例.因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例.Thread构造函数: public Thread( );  public Thread(Runnable target);  public Thread(String name);  public Thread(Runnable target

  • java向多线程中传递参数的三种方法详细介绍

    在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果.但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别.由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据.本文就以上原因介绍了几种用于向线程传递数据的方法,在下一篇文章中将介绍从线程中返回数据的方法. 欲先取之,必先予之.一般在使用线程时都需要有一些初始化数据,然后线程利用这些数据进行加工处理,并

  • java多线程并发中使用Lockers类将多线程共享资源锁定

    复制代码 代码如下: package com.yao; import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReadWriteLock;import java.util.c

  • java基本教程之java线程等待与java唤醒线程 java多线程教程

    本章,会对线程等待/唤醒方法进行介绍.涉及到的内容包括:1. wait(), notify(), notifyAll()等方法介绍2. wait()和notify()3. wait(long timeout)和notify()4. wait() 和 notifyAll()5. 为什么notify(), wait()等函数定义在Object中,而不是Thread中 wait(), notify(), notifyAll()等方法介绍在Object.java中,定义了wait(), notify()

  • java多线程和并发包入门示例

    一.java多线程基本入门java多线程编程还是比较重要的,在实际业务开发中经常要遇到这个问题. java多线程,传统创建线程的方式有两种. 1.继承自Thread类,覆写run方法. 2.实现Runnable接口,实现run方法. 启动线程的方法都是调用start方法,真正执行调用的是run方法.参考代码如下: 复制代码 代码如下: package com.jack.thread; /** * 线程简单演示例子程序 *  * @author pinefantasy * @since 2013-

  • java基本教程之Thread中start()和run()的区别 java多线程教程

    Thread类包含start()和run()方法,它们的区别是什么?本章将对此作出解答.本章内容包括:start() 和 run()的区别说明start() 和 run()的区别示例start() 和 run()相关源码(基于JDK1.7.0_40) start() 和 run()的区别说明start() : 它的作用是启动一个新线程,新线程会执行相应的run()方法.start()不能被重复调用.run()   : run()就和普通的成员方法一样,可以被重复调用.单独调用run()的话,会在

  • 基于Java回顾之多线程同步的使用详解

    首先阐述什么是同步,不同步有什么问题,然后讨论可以采取哪些措施控制同步,接下来我们会仿照回顾网络通信时那样,构建一个服务器端的"线程池",JDK为我们提供了一个很大的concurrent工具包,最后我们会对里面的内容进行探索. 为什么要线程同步? 说到线程同步,大部分情况下, 我们是在针对"单对象多线程"的情况进行讨论,一般会将其分成两部分,一部分是关于"共享变量",一部分关于"执行步骤". 共享变量 当我们在线程对象(Run

  • Java单利模式与多线程总结归纳

    概念: java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或少具有资源管理器的功能.每台计算机可以有若干个打印机,但只能有一个Printer

  • Java多线程之中断线程(Interrupt)的使用详解

    interrupt方法 interrupt字面上是中断的意思,但在Java里Thread.interrupt()方法实际上通过某种方式通知线程,并不会直接中止该线程.具体做什么事情由写代码的人决定,通常我们会中止该线程. 如果线程在调用Object类的wait().wait(long)或wait(long, int)方法,或者该类的 join() .join(long) .join(long, int) .sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态

随机推荐