咱们认真研究如何才能构建一个正确、健壮而且高效的并行系统。java
进程(Process):是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操做系统结构的基础。数据库
进程是线程的容器。程序是指令、数据和其组织形式的描述,进程是程序的实体。进程中能够容纳若干个线程。安全
进程和线程的关系:线程就是轻量级的进程,是程序执行的最小单位。为何咱们使用多线程而不是多进程?由于线程间的切换调度成本远远小于进程,因此咱们使用多线程而不是多进程。多线程
线程的生命周期并发
线程的全部状态都在Thread中的State枚举中定义。ide
public enum State{ NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED; }
NEW状态表示刚刚建立的线程,这种线程还没开始执行。start()方法调用时,线程开始执行。当线程执行时,处于RUNABLE状态,表示线程所需的一切资源都已经准备好了。函数
若是线程在执行过程当中遇到了synchronized
同步块,就会进入BLOCKED阻塞状态,这时线程就会暂停执行,直到得到请求的锁。优化
waiting
和time_waiting
都表示等待状态,它们的区别是waiting
会进入一个无时间限制的等待,time_waiting
会进行有时间限制的等待状态。通常说,waiting的线程是在等待一些特殊的事件。好比,经过wait()方法等待的线程在等待notify()方法,而经过join()方法等待的线程则会等待目标线程的终止。一旦等到了指望的事件,线程就会再次执行,进入runnable
状态。当线程执行完毕后,则进入terminated状态,表示结束。this
注意:从new状态出发后,线程不能再回到NEW状态,同理,处以TERMINATED的线程也不能再回到RUNNABLE状态。操作系统
这节了解一下java为线程操做提供的一些API。
新建线程很简单,一种可使用继承Thread,重载run()方法来自定义线程,下面是匿名内部类,也是重载了run()方法:
Thread t1 = new Thread(){ @Override public void run() { System.out.println("Hello, I am t1"); } }; t1.start();
start()后,线程Thread,有一个run()方法,start()方法会新建一个线程并让这个线程执行run()方法。
t1.start()和t1.run()两个方法的区别:start()会开启新的线程,并调用run()执行线程;直接调用run()方法也能经过编译,却不能新建线程,而是在当前线程中调用run()方法(不要尝试这样开启新线程,它只会在当前线程中,串行执行run()中的代码)。
Thread t1 = new Thread(); t1.run();
第二种是使用Runnable接口来实现一样的操做。这种方法解决了java单继承实现多线程的缺点:
public class CreateThread implements Runnable { @Override public void run() { System.out.println("Hi!I am Runnable"); } public static void main(String args[]) { Thread thread = new Thread(new CreateThread()); thread.start(); } }
通常,线程在执行完毕就会结束,无需手动关闭。特殊状况,须要手动关闭。
使用stop关闭
可使用stop关闭,可是不推荐,为何?缘由是stop()太过于暴力,强行把执行到一半的线程终止,可能会引发一些数据不一致的问题。举个例子:
记录1:ID=1,name=小明 记录2:ID=2,name=小王
上面数据库中要么是存记录1,要么存记录2,不然说明数据被损坏了,在单线程中不会出现这种状况,单在多线程中则会出现这样的状况。
Thread.stop()方法在结束线程时,会直接终止线程,而且会当即释放这个线程所持有的锁。而此时,线程写到一半便终止了。因为锁被释放,等待该锁的读线程也能够读到这个不一致的数据。以下图:
代码模拟:
public class StopThreadUnsafe { public static User user = new User(); public static class User { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public User() { id = 0; name = "0"; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", id=" + id + '}'; } } public static class ChangeObjectThread extends Thread { public void run() { while (true) { synchronized (user) { int v = (int) (System.currentTimeMillis() / 1000); user.setId(v); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } user.setName(v + ""); } Thread.yield(); } } } public static class ReadObjectThread extends Thread { public void run() { while (true) { synchronized (user) { if (user.getId() != Integer.parseInt(user.getName())) { System.out.println(user.toString()); } } Thread.yield(); } } } public static void main(String args[]) throws InterruptedException { new ReadObjectThread().start(); while (true) { Thread thread = new ChangeObjectThread(); thread.start(); Thread.sleep(150); thread.stop(); } } }
上面程序原本应该输出id和name的值都相同,可是却输出以下的错误数据,这种错误没有报错,很难查找。
User{name='1565947644', id=1565947645} User{name='1565947644', id=1565947645}
如何解决上面的问题?须要由咱们自行决定线程什么时候退出。仍然用本例说明,只须要将ChangeObjectThread线程增长一个stopMe()便可:
public static class ChangeObjectThread extends Thread { volatile boolean stopme = false; public void stopMe(){ stopme = true; } public void run() { while (true) { //手动中止线程 if (stopme){ System.out.println("exit by stop me"); break; } synchronized (user) { int v = (int) (System.currentTimeMillis() / 1000); user.setId(v); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } user.setName(v + ""); } Thread.yield(); } } }
线程中断能够和stop()同样起到退出线程的做用,可是它不会当即退出,而是给线程发送一个通知,告知目标线程,有人但愿你退出啦!至因而否退出由目标线程自行决定。
与线程中断的三个方法:
public void interrupt() //中断线程 public boolean Thread.isInterrupted() //判断是否被中断 public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态
若是不手动加入中断处理的逻辑,即便对线程中断,这个中断也不会起任何做用。
public class T1 { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(){ @Override public void run() { while(true){ if (Thread.currentThread().isInterrupted()){ System.out.println("Interruted!"); break; } try { Thread.sleep(2000); } catch (InterruptedException e) { System.out.println("Interrupted when Sleep"); Thread.currentThread().interrupt(); //1 } Thread.yield(); } } }; t1.start(); Thread.sleep(1000); t1.interrupt(); } }
若是去掉上例代码run方法中的Thread.sleep(2000),那么看起来和以前的stopme的方案很类似,可是中断的功能更强大。若是在循环体中,相似于wait()或者sleep()这样的操做,只能经过中断来识别。
若在线程休眠期间发生中断,它会抛出一个InterruptedException中断异常,而且清除中断标记。在上例代码中,1处是在捕获异常后(此时已清除了中断标记)从新设置中断标志,使其在下一次循环进入if语句中断循环。
public final void wait() throws InterruptedException public final native void notify()
当一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。直到等到其余线程调用了obj.notify为止。显然,这个对象成为了多个线程之间的有效通讯手段。
wait()和notiry()如何工做?
若是一个线程调用了object.wait(),那么它就会进入object对象的等待队列。当object.notify()被调用时,它就会从这个等待队列中,随机选择一个线程将其唤醒。这个选择彻底是随机的。而object.notifyAll()会将这个等待队列中全部等待的线程唤醒,而不会随机一个。
Object.wait()方法不是随便调用的,必须包含在对应synchronzied语句中,不管是wait()或者notify()都须要首先得到目标对象的一个监视器。下图展现了wait()和notify()的工做流程。
下面代码简单的使用了wait()和notify(),T1执行了object.wait()方法,这时立马释放对象锁。此时正在等待对象锁的T2捕获到后,在2处执行object.notify()方法,可是此时和object.wait()方法不一样,不立马释放,而是执行完synchronized块的代码后才释放。T2释放后,T1再次捕获,执行T2接下来的程序。
public class SimpleWN { final static Object object = new Object(); public static class T1 extends Thread{ public void run() { synchronized (object) { System.out.println(System.currentTimeMillis()+":T1 start! "); try { System.out.println(System.currentTimeMillis()+":T1 wait for object "); object.wait(); //1 wait()后,立刻释放对象锁 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis()+":T1 end!"); } } } public static class T2 extends Thread{ public void run() { synchronized (object) { System.out.println(System.currentTimeMillis()+":T2 start! notify one thread"); object.notify(); //2 notify()后,没有立刻释放对象锁,而是执行完synchronized块的代码后释放 System.out.println(System.currentTimeMillis()+":T2 end!"); try { Thread.sleep(3000); } catch (InterruptedException e) { } System.out.println(System.currentTimeMillis()+":T2 after sleep!"); } try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println(System.currentTimeMillis()+":T2 after synchronized!"); } } public static void main(String[] args) { Thread t1 = new T1() ; Thread t2 = new T2() ; // Thread t1_1 = new T1() ; // t1_1.start(); t1.start(); t2.start(); } } /** 1566280793124:T1 start! 1566280793125:T1 wait for object 1566280793127:T2 start! notify one thread 1566280793127:T2 end! 1566280796127:T2 after sleep! 1566280796128:T1 end! 1566280797127:T2 after synchronized! */
Object.wait()
与Thread.sleep()
的区别:Object.wait()和Thread.sleep()方法均可以让线程等待若干时间。除了wait()能够被唤醒外,另一个主要区别就是wait()方法会释放对象的锁,而Thread.sleep不会。
被挂起的线程,必需要等待resume()后,才能继续执行。
这对方法已经不推荐使用了。不推荐的缘由是由于suspend()在致使线程暂停的同时,并不会释听任何锁资源,直到等到resume()才释放。若是resume()操做意外出如今suspend()前面,就可能致使永久挂起。。此时,任何其余线程想要访问被它占用的锁时,都会受到牵连。甚至整个系统运行不正常。
public class BadSuspend { public static Object u = new Object(); static ChangeObjectThread t1 = new ChangeObjectThread("t1"); static ChangeObjectThread t2 = new ChangeObjectThread("t2"); public static class ChangeObjectThread extends Thread { public ChangeObjectThread(String name){ super.setName(name); } @Override public void run() { synchronized (u) { System.out.println("in "+getName()); Thread.currentThread().suspend(); //1 } } } public static void main(String[] args) throws InterruptedException { t1.start(); Thread.sleep(100); t2.start(); t1.resume(); System.out.println("t1 resume!"); t2.resume(); System.out.println("t2 resume!"); t1.join(); t2.join(); } }/** in t1 t1 resume! t2 resume! in t2 */
上面程序中,t1.start()后线程走到1处被挂起,此时不释放对象锁。t2.start()后,t2须要等待t1释放的对象锁。在t2等待锁的过程当中,t2.resume()已经发生了(经过打印结果能够看出),随后才发生t2的Thread.suspend()。这时,t2被永久挂起。
那若是须要一个比较可靠的suspend()函数,该怎么作呢?能够利用wait()和notify(),在应用层面实现suspend()和resume():
public class GoodSuspend { public static Object u = new Object(); public static class ChangeObjectThread extends Thread { // 标记变量,表示当前线程是否被挂起 volatile boolean suspendme = false; //1 // 挂起线程 public void suspendMe(){ suspendme = true; } // 继续执行线程 public void resumeMe(){ suspendme = false; synchronized (this){ notify(); } } @Override public void run(){ while (true){ synchronized (this){ //2 while (suspendme){ try { wait(); }catch (InterruptedException e){ e.printStackTrace(); } } } synchronized (u){ System.out.println("in ChangeObjectThread"); } Thread.yield(); } } } public static class ReadObjectThread extends Thread{ @Override public void run(){ while (true){ synchronized (u){ System.out.println("in ReadObjectThread"); } Thread.yield(); } } } public static void main(String[] args) throws InterruptedException { ChangeObjectThread t1 = new ChangeObjectThread(); ReadObjectThread t2 = new ReadObjectThread(); t1.start(); t2.start(); Thread.sleep(1000); t1.suspendMe(); System.out.println("suspend t1 2 sec"); Thread.sleep(2000); System.out.println("resume t1"); t1.resumeMe(); } }
在1处,给出了一个标记suspendme,表示当前线程是否被挂起,同时,增长了suspendMe()(经过执行wait()方法实现挂起)和resumeMe()(经过执行notify()通知继续执行,并清除挂起标记),注意,2处给本身加锁。
join
在多线程中,一个线程的输入可能很是依赖于另一个或者多个线程的输出,此时,这个线程就须要等待依赖线程执行完毕,才能继续执行,jdk提供了join()
来实现这个功能。有2个join方法
public final void join() throws InterruptedException public final synchronized void join(long millis) throws InterruptedException
第一个join()表示无限等待,它会阻塞当前线程,直到目标线程执行完毕。第二个方法给出了一个最大等待时间,若是超过给定时间目标线程还在执行,当前线程就会无论继续往下执行。
join()的本质是让调用线程wait()在当前线程对象实例上。下面是JDK中join()实现的核心代码片断:
while(isAlive()){ wait(0); }
它让调用线程在当前线程对象上等待。当线程执行完成后,会在推出前调用notifyAll()通知全部等待线程继续执行。
Thread.yield()
public static native void yield();
这是一个静态方法,它会让当前线程让出CPU。在让出CPU后,还会进行CPU资源的争夺,至因而否能再次分配,就不必定了。它的调用好像是在说:我已经完成了一些重要的工做了,我能够休息一下,给其余线程一些工做机会。
以前提到过:java内存模型围绕原子性、有序性和可见性展开。
为了在适当的场合,确保线程间的有序性、可见性和原子性。java使用了一些特殊的操做或者关键字来申明、告诉虚拟机,这个地方要特别注意,不能随意变更优化目标指令。关键字volatile
就是其中之一。
使用volatile去申明一个变量,能够保证这个变量的可见性的特色。在以前的例子MultiThreadLong中,long型的 t 改成volatile,使其保证了原子性。
public class MultiThreadLong { public volatile static long t=0; public static class ChangeT implements Runnable{ ......
volatile对于保证操做的原子性有很是大的帮助,可是volatile并不能替代锁,它没法保证一些复合操做的原子性。以下例中,没法保证i++的原子操做
public class PlusTask implements Runnable { public volatile static Integer j = 0; //1 public void add(){ for (int i = 0; i < 10000; i++) { j++; } } @Override public void run() { // synchronized (PlusTask.class) { add(); // } } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[10]; PlusTask task = new PlusTask(); for (int i = 0; i < 10; i++) { threads[i] = new Thread(new PlusTask()); threads[i].start(); } for (int i = 0; i < 10; i++) { threads[i].join(); } System.out.println(j); } }
上面的代码中,最终的值应该是100000,但实际老是会小于指望值。
volatile除了能够保证原子性,也能保证数据的可见性和有序性。下面看一个例子:
public class NoVisibility { private static boolean ready; private static int number; private static class ReaderThread extends Thread { public void run() { while (!ready); System.out.println(number); } } public static void main(String[] args) throws InterruptedException { new ReaderThread().start(); Thread.sleep(1000); number = 42; ready = true; Thread.sleep(10000); } }
因为系统优化的结果,ReaderThread线程可能没法“看到”主线程的修改,致使ReaderThread永远没法退出,这是一个典型的可见性问题。可是,只要简单使用volatile来申明ready变量,告诉java虚拟机,这个变量可能会在不一样的线程中修改。就能够解决问题了。
在一个系统中,若是线程数量不少,并且功能分配明确,就能够将相同功能的线程放置在一个线程组中,方便管理。
线程组的使用很简单:
public class ThreadGroupName implements Runnable { public static void main(String[] args) { ThreadGroup tg = new ThreadGroup("PrintGroup"); //1 创建名为“PrintGroup”的线程组 Thread t1 = new Thread(tg, new ThreadGroupName(), "T1"); Thread t2 = new Thread(tg, new ThreadGroupName(), "T2"); t1.start(); t2.start(); System.out.println(tg.activeCount()); //2 tg.list(); //3 } @Override public void run() { String groupAndName = Thread.currentThread().getThreadGroup().getName() + "-" + Thread.currentThread().getName(); while (true) { System.out.println("I am " + groupAndName); try { Thread.sleep(3000); } catch (Exception e) { e.printStackTrace(); } } } }
代码1处,创建一个“PrintGroup”的线程组,并将T1和T2两个线程加入这个组中。二、3处,activeCount()能够得到活动线程的数量,list()打印线程组中全部线程信息。
线程组有一个stop(),它会中止线程组中全部的线程,可是和Thread.stop()会有相同的问题,要格外谨慎使用。
守护线程是一种特殊的线程,它是系统的守护者,在后台默默完成一些系统性的服务,好比垃圾回收线程、JIT线程就能够理解为守护线程。与之相应的是用户线程,它完成业务操做。若是用户线程所有结束,守护线程的对象不存在了,那么整个应用程序就应该天然结束。所以,在java引用内,只有守护线程时,java虚拟机会天然退出。
下面是一个简单的守护线程:
public class DaemonDemo { public static class DaemonT extends Thread{ public void run(){ while(true){ System.out.println("I am alive"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { Thread t=new DaemonT(); t.setDaemon(true); //1 t.start(); Thread.sleep(2000); //当主线程执行完毕后,守护线程t也随之结束。 } }
1处将t设置成守护线程,当主线程执行完毕后,守护线程也随之结束。若不把t设置成守护线程,那么程序永远不会结束。
java中线程能够有本身的优先级。优先级高的线程在竞争资源时会更有优点,更可能抢占资源,固然,这是一个几率问题。这种优先级产生的后果不容易预测,优先级低的线程可能会致使饥饿现象(即便是优先级低,可是也不能饿死它啊)。所以,在要求严格的场合,仍是须要本身在应用层解决线程调度问题。
在java中,使用1到10表示线程优先级。通常可使用内置的三个静态变量标量表示:
public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10;
数字越大则优先级越高,但有效范围在1到10之间。例子:
public class PriorityDemo { public static class HightPriority extends Thread{ static int count=0; public void run(){ while(true){ synchronized(PriorityDemo.class){ count++; if(count>10000000){ System.out.println("HightPriority is complete"); break; } } } } } public static class LowPriority extends Thread{ static int count=0; public void run(){ while(true){ synchronized(PriorityDemo.class){ count++; if(count>10000000){ System.out.println("LowPriority is complete"); break; } } } } } /** * HightPriority先完成的次数多,可是 不保证 * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { Thread high=new HightPriority(); LowPriority low=new LowPriority(); high.setPriority(Thread.MAX_PRIORITY); //1 low.setPriority(Thread.MIN_PRIORITY); //2 low.start(); high.start(); } }
上述代码中一、2处设置了线程的优先级,因此老是高优先级的线程执行得会快些。
并发程序开发的一大关注重点就是线程安全。程序并行化是为了得到更高的执行效率,同时保证程序的正确性。所以,线程安全是并行程序的根本和根基。
public class AccountingVol implements Runnable { static AccountingVol accountingVol = new AccountingVol(); static volatile int i = 0; public static void increase() { i++; } @Override public void run() { for (int j = 0; j < 10000000; j++) { increase(); } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(accountingVol); Thread t2 = new Thread(accountingVol); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } }
上述代码中,线程t一、t2可能同时读取i为0,并各自计算获得i=1,并前后写入这个结果,所以,虽然i++被执行了2次,但实际i的值只增长了1。
要解决这个问题,咱们就要保证多个线程对i进行操做时彻底同步。就是说,当线程A在写入时,B不只不能写,也不能读。java中,提供了一个重要的关键字synchronized
来实现这个功能。
synchronized
做用是实现线程间的同步。它的工做是对同步的代码加锁,使得每次只能有一个线程进入同步块。
用法:
指定加锁对象
下面程序中,将synchronized做用于给定对象instance。每次进入被synchronized包裹的代码段,都会请求instance的锁。如有其余线程占用,则必须等待。
public class AccountingSync implements Runnable{ static AccountingSync instance=new AccountingSync(); static int i=0; @Override public void run() { for(int j=0;j<10000000;j++){ synchronized(instance){ i++; } } } //main程序见上例代码 }
直接做用于实例方法
public class AccountingSync2 implements Runnable{ static AccountingSync2 instance=new AccountingSync2(); static int i=0; public synchronized void increase(){ i++; } @Override public void run() { for(int j=0;j<10000000;j++){ increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync2 i1=new AccountingSync2(); // AccountingSync2 i2=new AccountingSync2(); Thread t1=new Thread(i1); Thread t2=new Thread(i1); t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); } }
上例代码中,synchronized关键字做用于一个实例方法,这就是说在进入increase()方法前,线程必须得到当前对象实例的锁。在本例中就是instance对象。在此例中,线程t1和t2须要用到相同的Ruanable实例i1,这样才能关注到同一个对象锁上。若两个线程使用不一样的两个Runnable实例t1,t2,即两个线程使用了两把不一样的锁。
可是,咱们能够把increase()方法改为static的,这样方法块请求的是当前类的锁,而不是当前实例的,所以,线程能够同步。以下:
public class AccountingSync2 implements Runnable{ static AccountingSync2 instance=new AccountingSync2(); static int i=0; public static synchronized void increase(){ //3 i++; } @Override public void run() { for(int j=0;j<10000000;j++){ increase(); } } public static void main(String[] args) throws InterruptedException { AccountingSync2 i1=new AccountingSync2(); AccountingSync2 i2=new AccountingSync2(); Thread t1=new Thread(i1); //1 Thread t2=new Thread(i2); //2 t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); } }
1和2处使用了两个不一样的Runable实例,可是3处的同步方法为static的,此方法须要的是当前类的锁而非当前实例的锁,所以线程间能够正确同步。
除了用于线程同步、确保线程安全外,synchronized还能够保证线程间的可见性和有序性。被synchronized限制的多个线程死串行执行的。
有异常的异常堆栈好修复,可是,没有异常、没有日志、没有堆栈的异常,就很让人抓狂了。
若是你运行下面的程序,会发现一个隐藏的错误:
int v1 = 1073741827; int v2 = 1431655768; int ave = (v1+v2)/2;
把上面ave打印出来,会发现ave的值是-894784850,一个负数。那是由于溢出。这种隐形的错误很难找,和幽灵通常。
ArrayList是一个线程不安全的容器。若是在多线程中使用ArrayList,可能会致使程序出错,那会出现哪些问题呢?
public class ArrayListMultiThread { static ArrayList<Integer> al = new ArrayList<Integer>(10); public static class AddThread implements Runnable{ @Override public void run() { for (int i=0;i<10000000;i++){ al.add(i); } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new AddThread()); Thread t2 = new Thread(new AddThread()); t1.start();t2.start(); t1.join(); t2.join(); System.out.println(al.size()); //抛出异常 返回小于2000的数值。 } }
在上面这段错误的代码中,本来输出的应该是20000000,可是因为ArrayList不支持,咱们可能会获得3中不一样的结果:
第一,程序正常结束,概率极小
第二,程序抛出异常:
Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 1823230 at java.util.ArrayList.add(ArrayList.java:459) at geym.ch2.ArrayListMultiThread$AddThread.run(ArrayListMultiThread.java:11) at java.lang.Thread.run(Thread.java:745)
这是由于ArrayList在扩容过程当中,内部一致性被破坏,但因为没有锁的保护,另外一个线程访问到了不一致的内部状态,致使了越界问题。
第三,出现一个很是隐蔽的问题,打印的值小于指望值20000000。
!!改进的方法很简单,使用线程安全的Vector代替ArrayList便可。
HashMap一样不是线程安全的。
public class HashMapMultiThread { static Map<String, String> map = new HashMap<String, String>(); public static class AddThread implements Runnable { int start = 0; public AddThread(int start) { this.start = start; } @Override public void run() { for (int i = 0; i < 100000; i += 2) { map.put(Integer.toString(i), Integer.toBinaryString(i)); } } public static void main(String[] args) throws InterruptedException { // 根据你的电脑CPU核数来配置 两核启两个线程就行 Thread t1 = new Thread(new HashMapMultiThread.AddThread(0)); Thread t2 = new Thread(new HashMapMultiThread.AddThread(1)); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(map.size()); } } }
咱们指望获得100000,可是,实际可能会有三种状况:
前面两种状况和ArrayList相似,对于第三种状况,因为多线程的冲突,HashMap中的Entry<K,V>
链表的结构遭到破坏,链表成环了!当链表成环时,HashMap.put()方法中的迭代就等于死循环。如图,展现了最简单的环状结构,key1和key2互为对方的next元素。
public class BadLockOnInteger implements Runnable { public static Integer i = 0; public static BadLockOnInteger instance = new BadLockOnInteger(); @Override public void run() { for (int j = 0; j < 10000000; j++) { synchronized (i){ //1 i++; } } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start();t2.start(); t1.join();t2.join(); System.out.println(i); } }
上面代码1处,把锁加在了i上,彷佛并无什么问题,然而,咱们运行程序,却获得了比预期值20000000要小的数,这是为何呢?由于Integer属于不可变对象。就是说Integer的值不能被修改,若是要修改,就要新建一个Integer对象。这样在多个线程间,并不必定可以看到同一个i对象(i一直在变),每次加锁都加在了不一样的对象实例上,从而致使对临界区代码控制出现问题。
修正这个问题,只要把锁加在instance上就能够了:
synchronized (i){
改成下面代码便可:
synchronized (instance){