您的位置:首页 > 职场人生

黑马程序员_Java_多线程总结

2014-06-18 12:03 363 查看
------- android培训java培训、期待与您交流! ----------

Java多线程是什么?

Java提供的并发(同时、独立)处理多个任务的机制。多个线程共存于同一JVM进程里面,所以共用相同的内存空间,较之多进程,多线程之间的通信更轻量级。依我的理解,Java多线程完全就是为了提高CPU的利用率。Java的线程有4种状态,新建(New)、运行(Runnable)、阻塞(Blocked)、结束(Dead),关键就在于阻塞(Blocked),阻塞意味着等待,阻塞的的线程不参与线程分派器(Thread
Scheduler)的时间片分配,自然也就不会使用到CPU。多线程环境下,那些非阻塞(Blocked)的线程会运行并充分利用CPU

 

1.线程

 

进程:正在进行中的程序

线程:就是进程中一个负责程序执行的控制单元

一个进程中可以有多条执行路径,称之为多线程

一个进程中至少要有一个线程

开启多个线程是为了同时运行多部分代码

每一个线程都有自己运行的内容。这个内容可以称为线程要执行的任务。

多线程好处:解决了多部分同时运行的问题。

多线程的弊端:线程太多回到效率的降低。

其实应用程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的。

JVM启动时就启动了多个线程,至少有两个线程可以分析的出来。

1,执行main函数的线程,

该线程的任务代码都定义在main函数中。

2,负责垃圾回收的线程。

2.创建线程方法之一继承Thread类

如何创建一个线程呢?

创建线程方式一:继承Thread类。

定义一个类继承自Thread类,

然后覆盖Thread类中的run()方法,

然后创建此类的对象调用start()方法开启此线程

 

创建线程有两种方法:

第一种是直接继承Thread类

步骤:

1,继承Thread类。

2,覆盖run方法。将线程要运行的代码定义其中。

3,创建Thread类的子类对象,其实就是在创建线程,调用start方法。

public class test11 {

public static void main(String[] args){

Thread t1 = new T1();  //创建一个x线程

t1.start();  //执行线程,调用start方法

}

}
class T1 extends Thread{

public void run() {

System.out.print("t1线程开启");

}

}


  

第二种是当自定义的类中继承了其它类,这就不能继承其它类,复写run()方法,这时候就让该类实现Runnable接口,复写该接口的run()方法。步骤:

 

1,定义了实现Runnable接口。

2,覆盖接口的run方法。将多线程要运行的代码存入其中。

3,创建Thread类的对象(创建线程),并将Runnable接口的子类对象作为参数传递给Thread的构造函数。  
 

4,调用Thread对象的start方法。开启线程。

 

public class test11 {

public static void main(String[] args){

T1 t1 = new T1();

Thread thread = new Thread(t1);

thread.start();

}

}  <pre name="code" class="java">

class T1 implements Runnable{

public void run() {

System.out.print("t1线程开启");

}

}




两种方式的特点:

实现方式,因为避免了单继承的局限性,所以创建线程建议使用第二种方式。

 

多线程具备随机性。因为是由cpu不断的快速切换造成的。这就有可能会产生死锁问题。

 

死锁的产生有四个条件:

1、 互斥条件:一个资源每次只能被一个进程使用。

2、 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

3、 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

4、 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

 

而在代码的实现当中就只有两个关键点:

1,多线程代码中有操作共享数据。

2,多条语句操作该共享数据。

 

当具备两个关键点时,有一个线程对多条操作共享数据的代码执行的一部分。还没有执行完,另一个线程开始参与执行。

就会发生数据错误。

解决方法:

 使用同步锁,当一个线程在执行多条操作共享数据代码时,其他线程即使获取了执行权,也不可以参与操作,这就使得这个线程所需的多个资源,在其运行完之前,不被其他线程获取。这也就破坏了上述死锁形成的第二个条件,也就不会造成死锁,但这样使用同步锁一次性就安排好所有的资源,会使得资源的利用率不高,较消耗资源。

 

Java就对这种解决方式提供了专业的代码。同步

 

同步的原理:就是将部分操作功能数据的代码进行加锁。

同步的表现形式:1,同步代码块。

2,同步函数。两者有什么不同:

 

同步代码块使用的锁是任意对象。同步函数使用的锁是this。

注意:对于static的同步函数,使用的锁不是this。是类名.class 是该类的字节码文件对象,涉及到了单例设计模式的懒汉式。

同步的好处:解决了线程的安全问题。

弊端:较为消耗资源。同步嵌套后,容易死锁。

 

 一、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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: