首页 > 代码库 > 多线程

多线程

多线程

Java线程的实现

1)继承java.lang.Thread类,重写run()方法。(run()方法是线程体
2)定义实现java.lang.Runnable接口的类,实现run()方法。

可以使用一个线程类对象启动多个线程!多个线程对同一对象操作会相互影响。

线程状态转换(生命周期)

基本状态图

 

包含线程同步的状态转换图

包含线程通信的状态转换图

 三种阻塞状态

1)位于对象等待池中:当线程调用了某个对象的wait()方法后,JVM会把线程放入这个对象的等待池中,直到该对象的notify或notifyAll方法在其他线程中被调用,才有可能唤醒本线程。
2)位于对象锁池中:当线程处于运行状态,试图获取某个对象的monitor(锁)时,若该对象锁已经被其他线程占用,则JVM将当前线程放入该对象的锁池中,直到该对象的锁被释放,当前线程才有可能获得该对象锁并解除阻塞状态。
3)其他阻塞状态:当前线程执行sleep方法或者调用其他线程的join方法时。发出I/O请求时也会发生阻塞。

什么是同步?

线程同步:(协同步调)一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

线程互斥:(共享资源访问排他性)当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥是线程同步到一种。

synchronized关键的问题

1)非静态synchronized方法

 1 public class ThreadTest { 2  3     public static void main(String[] args) throws InterruptedException { 4         MyService sv = new MyService(); 5         ThreadDemo demo = new ThreadDemo(sv); 6         Thread th1 = new Thread(demo); 7         th1.start(); 8          9         Thread.sleep(500); //等待th1 进入运行状态10         11         sv.init();12         th1.join();13         System.out.println("main thread finish");14     }15 16     static class ThreadDemo implements Runnable {17         private Object obj;18 19         public ThreadDemo(Object obj) {20             this.obj = obj;21         }22 23         public void run() {24             try {25                 synchronized (obj) {26                     Thread.sleep(10000);27                 }28             } catch (Exception e) {29             }30         }31     }32 33     static class MyService {34         synchronized void init() {35             System.out.println("MyService init");36         }37 38         synchronized void service() {39             System.out.println("MyService service");40         }41 42         void testState() {43             System.out.println("MyService state");44         }45     }46 }
View Code

运行结果是:mian线程等待10000毫秒后才调用sv.init()方法。因为th1里sv对象的monitor被一直持有10000毫秒。

结论:以synchronized修饰的非静态方法成为同步方法。线程需要获得该对象的monitor才能执行其同步方法。

2)静态synchronized方法 

 1 public class ThreadTest2 { 2  3     public static void main(String[] args) throws InterruptedException { 4         ThreadDemo demo = new ThreadDemo(); 5         Thread th1 = new Thread(demo); 6         th1.start(); 7         Thread.sleep(500); // 等待th1 进入运行状态 8  9         MyService.testState();10         System.out.println("main thread finish");11     }12 13     static class ThreadDemo implements Runnable {14 15         public void run() {16             try {17                 MyService.testState();18             } catch (Exception e) {19             }20         }21     }22 23     static class MyService {24         synchronized void init() throws InterruptedException {25             System.out.println("MyService init");26         }27 28         synchronized void service() {29             System.out.println("MyService service");30         }31 32         synchronized static void testState() throws InterruptedException {33             System.out.println("MyService state");34             Thread.sleep(10000);35         }36     }37 }
View Code

wait()/notify()/notifyAll()

 1 public class Test { 2     static Object lock = new Object(); 3  4     public static void main(String[] args) throws InterruptedException { 5         Thread th = new Thread(new MyThread_1()); 6         th.start(); 7  8         Thread th2 = new Thread(new MyThread_2()); 9         th2.start();10 11         th.join();12         th2.join();13         System.out.println("main thread finish");14     }15 16     static class MyThread_1 implements Runnable {17 18         public MyThread_1() {19         }20 21         public void run() {22             System.out.println("MyThread 1 run");23             try {24                 Thread.sleep(1000);25                 synchronized (lock) {26                     System.out.println("MyThread 1 obtain lock, and sleep");27                     Thread.sleep(10000);28                     System.out.println("MyThread 1 awake, and will release lock");29                 }30             } catch (Exception e) {31             }32             System.out.println("MyThread 1 finish");33         }34     }35 36     static class MyThread_2 implements Runnable {37         public void run() {38             System.out.println("MyThread 2 run");39             try {40                 synchronized (lock) {41                     System.out.println("MyThread 2 obtain lock");42                     lock.wait(3000);43                     System.out.println("MyThread 2 re-obtain lock");44                 }45             } catch (Exception e) {46             }47             System.out.println("MyThread 2 finish");48         }49     }50 }
View Code

 输出:

MyThread 1 runMyThread 2 runMyThread 2 obtain lockMyThread 1 obtain lock, and sleepMyThread 1 awake, and will release lockMyThread 1 finishMyThread 2 re-obtain lockMyThread 2 finishmain thread finish

结论:当前线程执行lock.wait()必须拥有ock对象的monitor,而执行lock.wait()方法后,会释放monitor。lock.wait()执行完成后需要重新获取lock的monitor。
notify()/notifyAll()方法同样需要当前线程获得lock对象的monitor,会唤醒等待lock对象monitor的一个(所有)线程,但这(些)线程唤醒后必须重新获取lock的monitor

isAlive() 

当线程调用了start()方法后,isAlive()为true,直到线程死亡。

如何使一个线程让出CPU执行时间?

1)调整各个线程优先级
2)让线程执行sleep方法
3)让线程调用yield方法
4)让线程调用另一个线程的join方法。

线程优先级

setPriority(int)和getPriority()。如果想要保持可移植,应当设置:MAX_PRIORITY, MIN_PRIORITY, NORM_PRIORITY

线程睡眠

当线程运行时,执行sleep方法,会放弃CPU,进入阻塞状态。

线程让步

当线程在运行中执行了Thread类的yield()静态方法时,如果此时具有相同优先级的其它线程处于就绪状态,那么yield()方法将把当前运行的线程放到运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。

sleep与yield的区别

二者都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。区别如下:
1)sleep不考虑线程优先级,可能把CPU让给优先级较低的线程。yield只让给优先级相同或更高的线程执行的机会。
2)执行sleep后,线程进入阻塞状态;执行yield后,线程进入就绪状态。
3)sleep方法抛出InterruptedException;yield方法不抛出任何异常。
4)sleep方法比yield方法有更好的移植性。

join()

等待其它线程的结束。当前线程调用另外一个线程的join方法后,会转入阻塞状态,直到另一个线程运行结束才恢复到就绪状态。

Java的原子操作

原子操作:一个操作中的所有动作要么全做,要么全不做。原子操作是一个不可分割的基本单位,在执行过程中不允许被中断。
Java原子操作
    1.引用类型变量值的读和写。注意这儿是引用值的读写,而不是所引用对象内容的读和写。
    2.除了long和double之外的简单类型的读和写。由于他们是64bit,JVM基本存储单元是32bit,无法在这一个时钟周期操作完成。
    3.所有声明为volatile的变量的读和写,包括long和double类型以及引用类型对基本数据类型的赋值或者返回值操作。 
自增操作(++)不是原子操作,因为涉及一次读和一次写。

临界区:任意时刻只允许一个线程访问的公共资源或者代码块。
同步代码块:以synchronized标记的代码块。能够保证多个线程按照正常逻辑访问临界区。
同步锁每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。

线程同步的特征

1) 如果同步代码块与非同步代码块同时操作共享资源,同样会造成资源争用。
2)每个对象都有唯一的同步锁。
3)静态方法可以加synchronized修饰符,同步锁为该方法所属类的类对象。所有该类的synchronized static方法的同步锁为类的类对象。
4)进入同步块执行的线程并非不间断执行,sleep、yield方法会使得线程将CPU让给其他线程,但不释放锁。而调用同步锁对象的wait方法后同样会让出CPU,且会释放锁。
5)synchronized方法不被继承。子类若覆盖父类的synchronized 方法,则该方法不再是synchronized方法,除非声明为synchronized。

线程安全的类

1)该类的对象可以被多个线程安全访问。
2)每个线程能够正常执行原子操作,并得到正确结果。
3)每个线程的原子操作执行完成后,对象处于合理状态。

释放对象锁

1)线程执行完毕同步代码块(方法)。
2)线程在执行同步代码块过程遇到异常。
3)线程在执行同步代码块过程,调用锁所属对象的wait()方法,会释放锁,线程进入锁所属对象的等待池。

死锁

当一个线程A等待线程B持有的锁,而同时线程B等待线程A持有的锁(或者线程B间接等待线程A的锁),就发生了死锁

 1 public class DeathLock { 2     public static void main(String[] args) throws InterruptedException { 3         Object o1 = new Object(); 4         Object o2 = new Object(); 5         Thread th = new Thread(new MyThread(o1, o2)); 6         Thread th2 = new Thread(new MyThread(o2, o1)); 7  8         th.start(); 9         th2.start();10     }11 12     static class MyThread implements Runnable {13 14         private Object lock;15         private Object target;16 17         public MyThread(Object lock, Object target) {18             this.lock = lock;19             this.target = target;20         }21 22         @Override23         public void run() {24             try {25                 synchronized (lock) {26                     Thread.sleep(1000);27                     System.out.println(String.format("hold %s, wait for %s ",28                             lock, target));29                     synchronized (target) {30                     }31                 }32             } catch (InterruptedException e) {33                 e.printStackTrace();34             }35         }36     }37 38 }
View Code

如何避免死锁?

基本原则:让所有线程按照同样顺序获得一组锁。 
方法:1)将多锁组成一个组放到一个锁下。
         2)标记各个锁对象锁的状态是否已被占用。这样当线程试图获取该某个对象的锁,而且锁已经被占用时,可以延迟进行再次尝试,避免死锁产生。
根本:仔细考虑涉及多线程的整个系统,对关键资源、模块进行深入分析。

线程通信

Java.lang.Object类中提供了两个用于线程通信的方法:

1)wait()方法。线程执行某个对象的wait方法后将释放该对象的锁,并进入到该对象的等待池中。等待其他线程将本线程唤醒。
2)notify/notifyAll方法。执行某个对象的这两个方法后,JVM从该对象的等待池中随机选择一个(全部选择)线程,并将它(们)放入该对象的锁池中。

 

 

多线程