并发编程
Posted zqlovesym
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程相关的知识,希望对你有一定的参考价值。
1、线程基础、线程之间的共享和协作
基础概念
CPU核心数和线程数的关系
核心数:线程数=1:1 ;使用了超线程技术后---> 1:2
CPU时间片轮转机制
又称RR调度,会导致上下文切换
什么是进程和线程
进程:程序运行资源分配的最小单位,进程内部有多个线程,会共享这个进程的资源
线程:CPU调度的最小单位,必须依赖进程而存在。
澄清并行和并发
并行:同一时刻,可以同时处理事情的能力
并发:与单位时间相关,在单位时间内可以处理事情的能力
高并发编程的意义、好处和注意事项
好处:充分利用cpu的资源、加快用户响应的时间,程序模块化,异步化
问题:
线程共享资源,存在冲突;
容易导致死锁;
启用太多的线程,就有搞垮机器的可能
认识Java里的线程
新启线程的方式
三种
1.继承Thread类,2.实现runable接口(无返回值) 3.实现callable接口 (有返回值)
举例:
package com.xiangxue.ch1; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** *@author * *类说明:如何新建线程 */ public class NewThread { /*扩展自Thread类*/ /*实现Runnable接口*/ private static class UseRun implements Runnable{ @Override public void run() { System.out.println("I am implements Runnable"); } } /*实现Callable接口,允许有返回值*/ private static class UseCall implements Callable<String>{ @Override public String call() throws Exception { System.out.println("I am implements Callable"); return "CallResult"; } } public static void main(String[] args) throws InterruptedException, ExecutionException { UseRun useRun = new UseRun(); new Thread(useRun).start(); Thread t = new Thread(useRun); t.interrupt(); UseCall useCall = new UseCall(); FutureTask<String> futureTask = new FutureTask<>(useCall); new Thread(futureTask).start(); System.out.println(futureTask.get()); } }
java本身就是多线程:
package com.xiangxue.ch1; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; /** *@author * *类说明:java的多线程无处不在 */ public class OnlyMain { public static void main(String[] args) { //虚拟机线程管理的接口 ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false); for(ThreadInfo threadInfo:threadInfos) { System.out.println("["+threadInfo.getThreadId()+"]"+" " +threadInfo.getThreadName()); } } } "C:Program FilesJavajdk1.8.0_73injava" "-javaagent:D:DownloadsIntelliJ IDEA 2018.1libidea_rt.jar=52049:D:DownloadsIntelliJ IDEA 2018.1in" -Dfile.encoding=UTF-8 -classpath "D:a_享学享学代码ppt1、第一期课程 ppt 资料(01)并发编程 1-2018.05.06-第一节课vip-concurrentin;C:Program FilesJavajdk1.8.0_73jrelibcharsets.jar;C:Program FilesJavajdk1.8.0_73jrelibdeploy.jar;C:Program FilesJavajdk1.8.0_73jrelibextaccess-bridge-64.jar;C:Program FilesJavajdk1.8.0_73jrelibextcldrdata.jar;C:Program FilesJavajdk1.8.0_73jrelibextdnsns.jar;C:Program FilesJavajdk1.8.0_73jrelibextjaccess.jar;C:Program FilesJavajdk1.8.0_73jrelibextjfxrt.jar;C:Program FilesJavajdk1.8.0_73jrelibextlocaledata.jar;C:Program FilesJavajdk1.8.0_73jrelibext ashorn.jar;C:Program FilesJavajdk1.8.0_73jrelibextsunec.jar;C:Program FilesJavajdk1.8.0_73jrelibextsunjce_provider.jar;C:Program FilesJavajdk1.8.0_73jrelibextsunmscapi.jar;C:Program FilesJavajdk1.8.0_73jrelibextsunpkcs11.jar;C:Program FilesJavajdk1.8.0_73jrelibextzipfs.jar;C:Program FilesJavajdk1.8.0_73jrelibjavaws.jar;C:Program FilesJavajdk1.8.0_73jrelibjce.jar;C:Program FilesJavajdk1.8.0_73jrelibjfr.jar;C:Program FilesJavajdk1.8.0_73jrelibjfxswt.jar;C:Program FilesJavajdk1.8.0_73jrelibjsse.jar;C:Program FilesJavajdk1.8.0_73jrelibmanagement-agent.jar;C:Program FilesJavajdk1.8.0_73jrelibplugin.jar;C:Program FilesJavajdk1.8.0_73jrelib esources.jar;C:Program FilesJavajdk1.8.0_73jrelib t.jar;D:a_享学1--并发编程2.线程的并发工具类 (1)commons-lang3-3.9commons-lang3-3.9.jar;D:a_享学1--并发编程2.线程的并发工具类 (1)commons-lang3-3.9-bin.zip" com.xiangxue.ch1.OnlyMain [6] Monitor Ctrl-Break [5] Attach Listener [4] Signal Dispatcher [3] Finalizer [2] Reference Handler [1] main Process finished with exit code 0
怎么样才能让Java里的线程安全停止工作呢
线程自然终止:自然执行完或抛出未处理异常
stop(),resume(),suspend()已不建议使用,stop()会导致线程不会正确释放资源,suspend()容易导致死锁。
java线程是协作式,而非抢占式
调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。
isInterrupted() 判定当前线程是否处于中断状态。
static方法interrupted() 判定当前线程是否处于中断状态,同时中断标志位改为false。
方法里如果抛出InterruptedException,线程的中断标志位会被复位成false,如果确实是需要中断线程,要求我们自己在catch语句块里再次调用interrupt()。
举例:
package com.xiangxue.ch1.safeend; import java.text.SimpleDateFormat; import java.util.Date; /** *@author Mark老师 享学课堂 https://enjoy.ke.qq.com * *类说明:抛出InterruptedException异常的时候,要注意中断标志位 */ public class HasInterrputException { private static SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss_SSS"); private static class UseThread extends Thread{ public UseThread(String name) { super(name); } @Override public void run() { String threadName = Thread.currentThread().getName(); while(!isInterrupted()) { try { System.out.println("UseThread:"+formater.format(new Date())); Thread.sleep(3000); } catch (InterruptedException e) { System.out.println(threadName+" catch interrput flag is " +isInterrupted()+ " at " +(formater.format(new Date()))); interrupt(); e.printStackTrace(); } System.out.println(threadName); } System.out.println(threadName+" interrput flag is " +isInterrupted()); } } public static void main(String[] args) throws InterruptedException { Thread endThread = new UseThread("HasInterrputEx"); endThread.start(); System.out.println("Main:"+formater.format(new Date())); Thread.sleep(800); System.out.println("Main begin interrupt thread:"+formater.format(new Date())); endThread.interrupt(); } }
package com.xiangxue.ch1.safeend; /** *@author Mark老师 享学课堂 https://enjoy.ke.qq.com * *类说明:如何安全的中断线程 */ public class EndThread { private static class UseThread extends Thread{ public UseThread(String name) { super(name); } @Override public void run() { String threadName = Thread.currentThread().getName(); while(true) { System.out.println(threadName+" is run!"); } //System.out.println(threadName+" interrput flag is "+isInterrupted()); } } public static void main(String[] args) throws InterruptedException { Thread endThread = new UseThread("endThread"); endThread.start(); Thread.sleep(20); endThread.interrupt(); } }
package com.xiangxue.ch1.safeend; /** *@author Mark老师 享学课堂 https://enjoy.ke.qq.com * *类说明:中断Runnable类型的线程 */ public class EndRunnable { private static class UseRunnable implements Runnable{ @Override public void run() { String threadName = Thread.currentThread().getName(); while(Thread.currentThread().isInterrupted()) { System.out.println(threadName+" is run!"); } System.out.println(threadName+" interrput flag is " +Thread.currentThread().isInterrupted()); } } public static void main(String[] args) throws InterruptedException { UseRunnable useRunnable = new UseRunnable(); Thread endThread = new Thread(useRunnable,"endThread"); endThread.start(); Thread.sleep(20); endThread.interrupt(); } }
对Java里的线程再多一点点认识
线程常用方法和线程的状态
线程只有5种状态。整个生命周期就是这几种状态的切换。
run()和start() :run方法就是普通对象的普通方法,只有调用了start()后,Java才会将线程对象和操作系统中实际的线程进行映射,再来执行run方法。
yield() :让出cpu的执行权,将线程从运行转到可运行状态,但是下个时间片,该线程依然有可能被再次选中运行。
线程的优先级
取值为1~10,缺省为5,但线程的优先级不可靠,不建议作为线程开发时候的手段
守护线程
和主线程共死,finally不能保证一定执行
package com.xiangxue.ch1; import java.util.concurrent.ExecutionException; /** * @author * * 类说明:守护线程的使用和守护线程中的finally语句块 */ public class DaemonThread { private static class UseThread extends Thread { @Override public void run() { try { while (!isInterrupted()) { System.out.println(Thread.currentThread().getName() + " I am extends Thread."); } System.out.println(Thread.currentThread().getName() + " interrupt flag is " + isInterrupted()); } finally { System.out.println("...........finally"); } } } public static void main(String[] args) throws InterruptedException, ExecutionException { UseThread useThread = new UseThread(); useThread.setDaemon(true); useThread.start(); Thread.sleep(5); //useThread.interrupt(); } }
线程间的共享
synchronized内置锁
对象锁,锁的是类的对象实例。
类锁 ,锁的是每个类的的Class对象,每个类的的Class对象在一个虚拟机中只有一个,所以类锁也只有一个。
举例:
package com.xiangxue.ch1.syn; import com.xiangxue.tools.SleepTools; /** *@author *Synchronized 关键字 *类说明:演示对象锁和类锁 */ public class SynClzAndInst { //使用类锁的线程 private static class SynClass extends Thread{ @Override public void run() { System.out.println("TestClass is running..."); synClass(); } } //使用对象锁的线程 private static class InstanceSyn implements Runnable{ private SynClzAndInst synClzAndInst; public InstanceSyn(SynClzAndInst synClzAndInst) { this.synClzAndInst = synClzAndInst; } @Override public void run() { System.out.println("TestInstance is running..."+synClzAndInst); synClzAndInst.instance(); } } //使用对象锁的线程 private static class Instance2Syn implements Runnable{ private SynClzAndInst synClzAndInst; public Instance2Syn(SynClzAndInst synClzAndInst) { this.synClzAndInst = synClzAndInst; } @Override public void run() { System.out.println("TestInstance2 is running..."+synClzAndInst); synClzAndInst.instance2(); } } //锁对象 private synchronized void instance(){ SleepTools.second(3); System.out.println("synInstance is going..."+this.toString()); SleepTools.second(3); System.out.println("synInstance ended "+this.toString()); } //锁对象 private synchronized void instance2(){ SleepTools.second(3); System.out.println("synInstance2 is going..."+this.toString()); SleepTools.second(3); System.out.println("synInstance2 ended "+this.toString()); } //类锁,实际是锁类的class对象 private static synchronized void synClass(){ SleepTools.second(1); System.out.println("synClass going..."); SleepTools.second(1); System.out.println("synClass end"); } public static void main(String[] args) { SynClzAndInst synClzAndInst = new SynClzAndInst(); Thread t1 = new Thread(new InstanceSyn(synClzAndInst)); //SynClzAndInst synClzAndInst2 = new SynClzAndInst(); //Thread t2 = new Thread(new Instance2Syn(synClzAndInst)); t1.start(); //t2.start(); SynClass synClass = new SynClass(); synClass.start(); SleepTools.second(1); } }
volatile关键字
适合于只有一个线程写,多个线程读的场景,因为它只能确保可见性。
package com.xiangxue.ch1.vola; import com.xiangxue.tools.SleepTools; /** *@author * *类说明:演示violate无法提供操作的原子性 */ public class VolatileUnsafe { private static class VolatileVar implements Runnable { private volatile int a = 0; @Override public void run() { String threadName = Thread.currentThread().getName(); a = a++; System.out.println(threadName+":======"+a); SleepTools.ms(100); a = a+1; System.out.println(threadName+":======"+a); } } public static void main(String[] args) { VolatileVar v = new VolatileVar(); Thread t1 = new Thread(v); Thread t2 = new Thread(v); Thread t3 = new Thread(v); Thread t4 = new Thread(v); t1.start(); t2.start(); t3.start(); t4.start(); } }
ThreadLocal
线程变量。可以理解为是个map,类型 Map<Thread,Integer>
package com.xiangxue.ch1; /** *@author Mark老师 享学课堂 https://enjoy.ke.qq.com * *类说明:演示ThreadLocal的使用 */ public class UseThreadLocal { //可以理解为 一个map,类型 Map<Thread,Integer> static ThreadLocal<Integer> threadLaocl = new ThreadLocal<Integer>(){ @Override protected Integer initialValue() { return 1; } }; /** * 运行3个线程 */ public void StartThreadArray(){ Thread[] runs = new Thread[3]; for(int i=0;i<runs.length;i++){ runs[i]=new Thread(new TestThread(i)); } for(int i=0;i<runs.length;i++){ runs[i].start(); } } /** *类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响 */ public static class TestThread implements Runnable{ int id; public TestThread(int id){ this.id = id; } public void run() { System.out.println(Thread.currentThread().getName()+":start"); Integer s = threadLaocl.get();//获得变量的值 s = s+id; threadLaocl.set(s); System.out.println(Thread.currentThread().getName()+":" +threadLaocl.get()); //threadLaocl.remove(); } } public static void main(String[] args){ UseThreadLocal test = new UseThreadLocal(); test.StartThreadArray(); } }
线程间协作
轮询:难以保证及时性,资源开销很大,
等待和通知
wait() 对象上的方法
notify/notifyAll 对象上的方法
等待和通知的标准范式
等待方:
1、 获取对象的锁;
2、 循环里判断条件是否满足,不满足调用wait方法,
3、 条件满足执行业务逻辑
通知方来说
1、 获取对象的锁;
2、 改变条件
3、 通知所有等待在对象的线程
notify和notifyAll应该用谁?
应该尽量使用notifyAll,使用notify因为有可能发生信号丢失的的情况
等待超时模式实现一个连接池
假设 等待时间时长为T,当前时间now+T以后超时
long overtime = now+T;
long remain = T;//等待的持续时间
while(result不满足条件&& remain>0){
wait(remain);
remain = overtime – now;//等待剩下的持续时间
}
return result;
package com.xiangxue.ch1.pool; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.*; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import com.xiangxue.tools.SleepTools; /** *@author Mark老师 享学课堂 https://enjoy.ke.qq.com * *类说明:实现了数据库连接的实现 */ public class SqlConnectImpl implements Connection{ /*拿一个数据库连接*/ public static final Connection fetchConnection(){ return new SqlConnectImpl(); } @Override public boolean isWrapperFor(Class<?> arg0) throws SQLException { // TODO Auto-generated method stub return false; } @Override public <T> T unwrap(Class<T> arg0) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void abort(Executor arg0) throws SQLException { // TODO Auto-generated method stub } @Override public void clearWarnings() throws SQLException { // TODO Auto-generated method stub } @Override public void close() throws SQLException { // TODO Auto-generated method stub } @Override public void commit() throws SQLException { SleepTools.ms(70); } @Override public Array createArrayOf(String arg0, Object[] arg1) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Blob createBlob() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Clob createClob() throws SQLException { // TODO Auto-generated method stub return null; } @Override public NClob createNClob() throws SQLException { // TODO Auto-generated method stub return null; } @Override public SQLXML createSQLXML() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Statement createStatement() throws SQLException { SleepTools.ms(1); return null; } @Override public Statement createStatement(int arg0, int arg1) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Statement createStatement(int arg0, int arg1, int arg2) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Struct createStruct(String arg0, Object[] arg1) throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean getAutoCommit() throws SQLException { // TODO Auto-generated method stub return false; } @Override public String getCatalog() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Properties getClientInfo() throws SQLException { // TODO Auto-generated method stub return null; } @Override public String getClientInfo(String arg0) throws SQLException { // TODO Auto-generated method stub return null; } @Override public int getHoldability() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public DatabaseMetaData getMetaData() throws SQLException { // TODO Auto-generated method stub return null; } @Override public int getNetworkTimeout() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public String getSchema() throws SQLException { // TODO Auto-generated method stub return null; } @Override public int getTransactionIsolation() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public Map<String, Class<?>> getTypeMap() throws SQLException { // TODO Auto-generated method stub return null; } @Override public SQLWarning getWarnings() throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean isClosed() throws SQLException { // TODO Auto-generated method stub return false; } @Override public boolean isReadOnly() throws SQLException { // TODO Auto-generated method stub return false; } @Override public boolean isValid(int arg0) throws SQLException { // TODO Auto-generated method stub return false; } @Override public String nativeSQL(String arg0) throws SQLException { // TODO Auto-generated method stub return null; } @Override public CallableStatement prepareCall(String arg0) throws SQLException { // TODO Auto-generated method stub return null; } @Override public CallableStatement prepareCall(String arg0, int arg1, int arg2) throws SQLException { // TODO Auto-generated method stub return null; } @Override public CallableStatement prepareCall(String arg0, int arg1, int arg2, int arg3) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String arg0) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String arg0, int arg1) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String arg0, int[] arg1) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String arg0, String[] arg1) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String arg0, int arg1, int arg2) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String arg0, int arg1, int arg2, int arg3) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void releaseSavepoint(Savepoint arg0) throws SQLException { // TODO Auto-generated method stub } @Override public void rollback() throws SQLException { // TODO Auto-generated method stub } @Override public void rollback(Savepoint arg0) throws SQLException { // TODO Auto-generated method stub } @Override public void setAutoCommit(boolean arg0) throws SQLException { // TODO Auto-generated method stub } @Override public void setCatalog(String arg0) throws SQLException { // TODO Auto-generated method stub } @Override public void setClientInfo(Properties arg0) throws SQLClientInfoException { // TODO Auto-generated method stub } @Override public void setClientInfo(String arg0, String arg1) throws SQLClientInfoException { // TODO Auto-generated method stub } @Override public void setHoldability(int arg0) throws SQLException { // TODO Auto-generated method stub } @Override public void setNetworkTimeout(Executor arg0, int arg1) throws SQLException { // TODO Auto-generated method stub } @Override public void setReadOnly(boolean arg0) throws SQLException { // TODO Auto-generated method stub } @Override public Savepoint setSavepoint() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Savepoint setSavepoint(String arg0) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setSchema(String arg0) throws SQLException { // TODO Auto-generated method stub } @Override public void setTransactionIsolation(int arg0) throws SQLException { // TODO Auto-generated method stub } @Override public void setTypeMap(Map<String, Class<?>> arg0) throws SQLException { // TODO Auto-generated method stub } }
package com.xiangxue.ch1.pool; import java.sql.Connection; import java.util.LinkedList; /** *@author Mark老师 享学课堂 https://enjoy.ke.qq.com * *类说明:实现一个数据库的连接池 */ public class DBPool { //数据库池的容器 private static LinkedList<Connection> pool = new LinkedList<>(); public DBPool(int initalSize) { if(initalSize>0) { for(int i=0;i<initalSize;i++) { pool.addLast(SqlConnectImpl.fetchConnection()); } } } //在mills时间内还拿不到数据库连接,返回一个null public Connection fetchConn(long mills) throws InterruptedException { synchronized (pool) { if (mills<0) { while(pool.isEmpty()) { pool.wait(); } return pool.removeFirst(); }else { long overtime = System.currentTimeMillis()+mills; long remain = mills; while(pool.isEmpty()&&remain>0) { pool.wait(remain);//注意:等待remain时间后,执行wait,wait会释放锁 remain = overtime - System.currentTimeMillis(); } Connection result = null; if(!pool.isEmpty()) { result = pool.removeFirst(); } return result; } } } //放回数据库连接 public void releaseConn(Connection conn) { if(conn!=null) { synchronized (pool) { pool.addLast(conn); pool.notifyAll(); } } } }
package com.xiangxue.ch1.pool; import java.sql.Connection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; /** *@author Mark老师 享学课堂 https://enjoy.ke.qq.com * *类说明: */ public class DBPoolTest { static DBPool pool = new DBPool(10); // 控制器:控制main线程将会等待所有Woker结束后才能继续执行 static CountDownLatch end; public static void main(String[] args) throws Exception { // 线程数量 int threadCount = 50; end = new CountDownLatch(threadCount); int count = 20;//每个线程的操作次数 AtomicInteger got = new AtomicInteger();//计数器:统计可以拿到连接的线程 AtomicInteger notGot = new AtomicInteger();//计数器:统计没有拿到连接的线程 for (int i = 0; i < threadCount; i++) { Thread thread = new Thread(new Worker(count, got, notGot), "worker_"+i); thread.start(); } end.await();// main线程在此处等待 System.out.println("总共尝试了: " + (threadCount * count)); System.out.println("拿到连接的次数: " + got); System.out.println("没能连接的次数: " + notGot); } static class Worker implements Runnable { int count; AtomicInteger got; AtomicInteger notGot; public Worker(int count, AtomicInteger got, AtomicInteger notGot) { this.count = count; this.got = got; this.notGot = notGot; } public void run() { while (count > 0) { try { // 从线程池中获取连接,如果1000ms内无法获取到,将会返回null // 分别统计连接获取的数量got和未获取到的数量notGot Connection connection = pool.fetchConn(1000); if (connection != null) { try { connection.createStatement(); connection.commit(); } finally { pool.releaseConn(connection); got.incrementAndGet(); } } else { notGot.incrementAndGet(); System.out.println(Thread.currentThread().getName() +"等待超时!"); } } catch (Exception ex) { } finally { count--; } } end.countDown(); } } }
join()方法
面试点
线程A,执行了线程B的join方法,线程A必须要等待B执行完成了以后,线程A才能继续自己的工作
调用yield() 、sleep()、wait()、notify()等方法对锁有何影响?
面试点
线程在执行yield()以后,持有的锁是不释放的,Cpu也是会有可能选择当前线程进行执行。
sleep()方法被调用以后,持有的锁是不释放的,休眠的时候CPU不会选择改线程进行执行。
调用方法之前,必须要持有锁。调用了wait()方法以后,锁就会被释放,当wait方法返回的时候,线程会重新持有锁
调用方法之前,必须要持有锁,调用notify()方法本身不会释放锁的
一般将notify,notifyall放在同步代码块的最后。
/* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/ public synchronized void changeKm(){ this.km = 101; notifyAll(); //其他的业务代码 这样写不合理 }
以上是关于并发编程的主要内容,如果未能解决你的问题,请参考以下文章