首页 > 代码库 > 多线程
多线程
多线程
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 }
运行结果是: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 }
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 }
输出:
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 }
如何避免死锁?
基本原则:让所有线程按照同样顺序获得一组锁。
方法:1)将多锁组成一个组放到一个锁下。
2)标记各个锁对象锁的状态是否已被占用。这样当线程试图获取该某个对象的锁,而且锁已经被占用时,可以延迟进行再次尝试,避免死锁产生。
根本:仔细考虑涉及多线程的整个系统,对关键资源、模块进行深入分析。
线程通信
Java.lang.Object类中提供了两个用于线程通信的方法:
1)wait()方法。线程执行某个对象的wait方法后将释放该对象的锁,并进入到该对象的等待池中。等待其他线程将本线程唤醒。
2)notify/notifyAll方法。执行某个对象的这两个方法后,JVM从该对象的等待池中随机选择一个(全部选择)线程,并将它(们)放入该对象的锁池中。
多线程