一、有关操作系统基础知识
程序:
程序=数据结构+算法---->一段静态代码
硬盘:
永久存储ROM,程序永远存在的地方。
内存:
所有应用程序都需要进入到内存中执行,临时存储RAM。点击应用程序执行,程序进入到内存中占用一些内存执行。进入到内存的程序叫继进程。
CPU:
中央处理器,对数据进行计算,指挥电脑软硬件干活,CPU中的XX线程指的是可以同时执行XX个任务。CPU分类:AMD,Inter
1.单核心单线程CPU:
CPU在多线程之间做高速切换,轮流执行多个线程,效率低,切换速度(1/n)
2.N核心X线程:
有X个线程,可以同时执行X个线程,X个线程在多个任务之间做高速切换,速度是单线程CPU的X陪(每个任务执行到的概率均提高了X倍)
进程:
1.进程的定义:
当程序加载到内存中开始运行则程序转变为一个进程,即进程是程序的一次执行过程,或是正在运行的一个程序。即一个内存中运行的应用程序。
2.进程的生命周期:
从加载到内存开始执行到执行结束整个周期。
3.程序与进程的关系:
程序是静态的,进程是动态的,其中进程是资源分配的单位。
4.进行与线程关系:
进程可以细化为多个线程。
(在java中)每个线程拥有自己独立的栈和程序计数器,多个线程,共享同一个进程中的结构:方法区,堆。
线程:
1.线程定义:
线程是由进程细化而来,是程序内部的一条执行路径。即线程是进程中的一个执行单元,负责当前进程中程序的执行。一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
2.线程的生命周期(结合java语言):
A.在jdk中用Thread.State类定义了线程的几种状态:
1.New状态(新建状态):当一个Thread类及其子类的对象被声明并创建时,新生的线程对象处于新建状态。
用java语言描述则:new Thread();或者new Thread子类();
2.就绪:处于新建状态的线程被star()后,将进入线程队列等待CPU时间片,此时已经具备运行条件,只是没有分配到CPU资源
3.Runnable状态(运行状态):当就绪线程被调度并获得CPU资源时便进入运行状态,run()方法定义了线程的操作和功能。new多个线程,这多个线程谁抢到CPU执行权,谁就是运行状态.
4.阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出cpu并临时终止自己的行为,进入阻塞状态。阻塞状态是一种临时状态,不可以作为最终状态。
①:blocked状态(锁阻塞状态):new多个线程,这多个线程谁没有抢到CPU执行权,谁就是阻塞状态,阻塞状态具有CPU的执行资格,但它和其他线程一起抢夺CPU来执行(阻塞与运行两状态转换看多个线程抢夺CPU的执行时间)
②:time_waiting状态(休眠状态/计时等待/冻结状态):当运行状态中出现sleep(long)方法或者wait(long)方法时线程进入休眠状态当休眠结束,先看CPU是否空闲,若空闲则进入运行状态、若不空闲则进入阻塞状态。主动放弃CPU的执行资格,CPU空闲也不执行
③:waiting状态(无限等待状态/冻结状态):运行状态中调用Object.wait();方法,则线程进入无限等待状态,此状态只有调用Object.notify();方法才能唤醒。唤醒后线程看CPU是否空闲,若空闲则进入运行状态、若不空闲则进入阻塞状态。主动放弃CPU的执行资格CPU空闲也不执行
5.terminated状态(死亡状态):线程完成了它的全部工作或线程被提前强制性地终止或出现异常导致结束。线程的最终状态为死亡状态
用java语言描述:在运行状态出现run();方法结束或者调用stop();方法或者线程中出现异常(run方法中出现异常)则线程进入死亡状态。
tip1:关于阻塞状态:阻塞状态是一种临时状态,不可以作为最终状态。
tip2:线程的最终状态为死亡状态。
tip3:针对单核CPU来说实现的多线程是一种假的多线程
B.在java中线程的集中状态间的转换:
新建->就绪:调star();
就绪->运行:获得CPU执行权
运行->就绪:失去CPU执行权 或 yield()
运行->阻塞:sleep(Long time);join();等待同步锁;wait();suspend(){挂起,使用后会出现死锁};
阻塞->就绪:sleep(Long time)时间到;join()结束;获取同步锁;notify()/notifyAll();resume()
运行->死亡:执行完run();调用线程的stop();出现Error/Exception且没有处理
C.生命周期需要关注:状态,方法
在线程状态进行转换时,某些方法可以自动执行,这些方法称为回调方法;
有些方法执行会改变线程的状态;
3.线程的调度策略:
时间片(分时调度):CPU的执行时采用时间片的方式分配给不同的线程,即所有线程轮流使用CPU的使用权,平均分配给每个线程占用CPU的时间
抢占式(抢占式调度):CPU在切换是有先考虑高优先级的线程进行执行,即优先让优先级高的线程使用CPU,若线程的优先级相同,则随机选择一个(线程的随机性),java使用为抢占式调度,java程序属于抢占式调度,优先级高的线程优先执行;同一个优先级,随机选择一个执行。
4.并行与并发
并行:
多个cpu同时执行多个任务。即指两个或者多个事件在同一时刻发生(同时发生),同时执行。
(一个人一次吃一个馒头,总共两个人,每人吃一个馒头)
并发:
一个CPU(采用时间片)同时执行多个任务。即指两个或者多个事件在同一时间段内发生,CPU交替执行任务。
(一个人一次吃一个馒头,总共吃两个馒头)
5.异步与同步:
同步:某个时间段只有一个线程在工作;其他线程进入阻塞状态
异步:某个时间段多个线程在一起工作
6.java中的线程分类:
守护线程:如垃圾回收线程
用户线程:例如主线程main线程,将用户线程变成一个守护线程可使用thread.setDaemon(true)
守护线程依赖于用户线程,当用户线程执行完毕则守护线程也必须执行完毕,若JVM中都是守护线程,则当前的JVM将退出
电脑软件APP,点击运行,会进入到内存中,就是一个进程。点击APP中的某一个功能(比如音乐APP中放歌功能或者歌词显示功能)就会开启一条应用程序到CPU的执行路径,CPU就可以通过这个路径执行功能,此路径成线程。线程属于进程,是进程中的一个执行单元,负责程序的执行。
二、java语言与线程:
1.在JVM中{
每一份线程,都会被分配一套虚拟机栈(运行栈)和程序计数器
每一份进程,都会被分配一套方法区和堆
一份进程中含有多个线程,因此产生了多个线程共享一个方法区和堆
}
一个java应用程序java.exe,其实至少有三个线程:
线程一:主线程:执行主方法(main方法)的线程。{JVM执行main方法,main方法会进入到栈内存,JVN会找操作系统开辟一条main方法同向CPU的执行路径。CPU就可以通过这个路径来执行main方法,此路径称main(主)线程}
线程二:gc()垃圾回收线程,
线程三:异常处理线程
2.多线程的引入:
一个进程可以细分为多个线程,自然而然将多线程的概念引导出来。多线程意味着速度快。
1.使用多线程的优点:
A.效率高:提高用于程序的响应。提高CPU利用率。
B.多个线程之间互不影响:改善程序结构,将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
2.合适需要多线程:
A.程序需要同时执行两个以上的任务;
B.程序需要实现一些需要等待的任务;
C.需要一些后台运行的程序
3.判断单线程:
java程序中只有一个线程,执行从main方法开始,从上到下依次执行则此程序为单线程程序。
简单判断单线程也可以看在主程序中有没有利用Thread类开启新线程
4.多线程存在的隐患:
A.多线程存在的隐患:多个线程操作共享的系统资源可能带来安全隐患,解决方法使用线程同步
B.解决多线程存在的隐患:
利用同步技术{
同步中的线程,没有执行完毕不会释放锁(使用了一个锁对象,这个锁对象叫同步锁(对象锁/对象监视器)),同步外的线程没有锁无法进入同步。同步保证了只有一个线程在同步中执行共享数据,保证了安全,但程序平凡的判断锁,获取锁,释放锁,使得程序效率降低
}
3.java中的Thread类简单介绍:
1.Thread类中的常用方法
1.start():启动当前线程;调欧诺个当前线程的run()方法2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法此方法中3.currentThread():静态方法,返回执行当前代码的线程4.getName():获取当前线程的名字5.setName():设置当前线程的名字{void setName(String name)改变线程名称,使之与参数name相同创建一个带参数的构造方法,参数传递线程名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起名字}6.yield():释放当前CPU的执行权,不会释放锁,线程被挂起也不会释放锁7.join():在线程A中调用线程B的join()方法,此时线程A就接入阻塞状态,直到线程B完全执行完后,线程A才结束阻塞状态8.stop():此方法已经过时,表示强制结束一个线程9.sleep(long militime):让当前线程“睡眠”指定的militime毫秒,在指定的militime毫秒时间之内,当前线程是阻塞状态,虽然线程阻塞但不会释放锁,即不会释放锁,线程被挂起也不会释放锁10.isAlive():判断当前线程是否存活11.wait():方法的线程进入阻塞状态,并且释放锁
2.Thread类中线程优先级等级:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 --->默认优先级
获取和设置当前线程的优先级
getPriority():返回线程优先值
setPriority(int newPriority):改变线程优先级。
但这种不改变知识从概率上改变了线程被执行的可能性,这表示当优先级高的线程并不一定优先会被执行
tip1:方法里面调方法均是省略了this.
三、java中多线程的创建:
在java中JVM允许程序运行多个线程,它通过java.lang.Thread类来体现;多线程:集合只要是不安全的,它就是多线程的,多线程意味着速度快。
1.多线程的创建:
方式一:继承于Thread类,即创建Thread类的子类
java.long.Thread类是描述线程的类,想要实现多线程程序,就必须继承Thread类
继承步骤:
1.创建一个继承于Thread类的子类
2.在Thread类的子类中重写Thread类中的run方法-->将此线程执行的操作声明在run()方法中,即设置线程任务(开启线程要做什么)
3.创建Thread类的子类对象
4.通过此对象调用继承自Thread类的start();方法开启新线程,执行run方法
void start()使该线程开始执行,java虚拟机调用该线程的run方法,结果是两个线程并发地运行;当前线程(main线程)和另外一个线程(创建的新线程,执行其run方法)。多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
star();的两个作用:
1)启动当前线程;
2)调用当前线程的run();方法
注:
1.不能通过直接调用run()方法的方式启动线程,如果单单调用run方法就只是表示调用方法而非开启新线程
2.一个线程的start()方法在其生命周期中只能执行一次
方式二:实现Runnable接口
java.long.Runnable:
Runnable接口应该由哪些打算通过某一线程执行其实列的类来实现。类必须定义一个称为run的无参方法
java.long.Thread类的构造方法:
Thread(Runnable target)分配新的thread对象。
Thread(Runnable target,String name)分配新的thread对象。
实现步骤:
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法run(),即在实现类中重写Runnable接口的run方法,设置线程任务
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start(),开启新的线程执行run方法
其中start()方法的作用:
启动线程;
调用当前线程的run方法-->{
调用了Runnable类型的target的run(),
而target的值正是实现类对象,
而实现类对象去实现Runnable中的抽象方法run()
}
比较创建线程的继承于实现方式:
开发中优先选择实现Runnable接口的方式
原因:
1.实现的方式没有类的单继承性的局限性。避免了单继承的局限性:{一个子类只能继承一个父类,继承了Thread类就不能继承其他类。实现了Runnable接口 ,还可以继承其他类,实现其他接口。}
2.实现的方式更适合来处理多个线程共享数据的情况
3.增强了程序的扩展性,降低了程序的耦合性(解耦){实现了Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦),实现类中能够重写了run方法,用于设置线程任务。创建Thread对象,调用start方法,用于开启新线程}
联系:其实Thread类也实现了Runnable接口
相同点:两种方式均需要重写run方法,将线程要执行的逻辑声明在run()方法中
方式三:实现Callable接口----jdk5.0新增
1.创建一个实现callable接口的实现类
2.实现call方法,将次线程需要执行的操作声明在call()方法中
3.创建Callable接口实现类的对象
4.将次Callable接口实现类对象作为参数传到FutureTask构造器中,创建FutureTask对象
5.将FutureTask对象作为参数传递到Thread类的构造器中创建thread对象,并调用start()方法
6.获取Callable中call方法中的返回值
如何理解Callable接口的方式创建多线程比Runnable接口的方式创建多线程更强大:
1.call方法可以有返回值
2.call方法可以抛出异常
3.Callable支持泛型
实现Callable接口实例:
//1.创建一个实现callable接口的实现类
class NumThread implements Callable {//2.实现call方法,将次线程需要执行的操作声明在call()方法中@Overridepublic Object call() throws Exception {int sum=0;//基本数据类型不属于任何类for (int i = 0; i <= 50; i++) {if(i%2==0){System.out.println(i);sum+=i;}}return sum;//自动装箱}
}
方式四:使用线程池
使用一个线程就要创建一个线程,在面对高并发多线程时,每一个线程都是执行一个时间很短的任务就结束了,若频繁创建线程就会大大降低系统效率。因为频繁创建,销毁线程很费时间。为解决这种问题,java提供了线程池的原理
线程池:
1.线程池(容器)-->集合(ArrayList,HashSet,LinkedList<Thread>,HashMap)
2.当程序第一次启动时,创建多个线程保存到一个集合中,当我们想要使用一个线程时,就可以从集合中取出使用
3.Thread t=linked.remove(0):返回的是被移除的元素(线程只能被一个任务使用)
4.Thread t=linked.removeFist():当使用完毕线程,需要把线程归还给线程池list.add(t);linked.addLast(t)
6.jdk1.5之后JDK内置了线程池,可以直接使用
线程池使用步骤:
1.通过指定线程数量的线程池ExecutorService。即使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
2.执行指定线程的操作,需要提供一个实现Runnable接口或Callable接口实现类的对象。即创建一个类,实现Runnable接口,重写run方法,设置线程任务;调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
3.关闭连接池,调用close方法。或者调用ExecutorService中的方法shutdown销毁线程池(一般不建议执行)
合理使用线程池的好处:
1.提高相应速度(减少了创建线程池的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于管理线程
4.设置线程池的属性
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止。
java.util.concurrent.Executors:线程池的工厂类,用于生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池
参数:
int nThreads:创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,可以使用ExecutorService接口接收(面向接口编程)
java.util.concurrent.Executors:线程池接口
用于从呈现出中得到线程,调用star方法执行线程任务。
submit(Runnable task)提交一个Runnable任务用于执行
关闭/销毁线程池的方法:
void shutdown()
使用线程池实例一:
public class ThreadPool {public static void main(String[] args) {//1.通过指定线程数量的线程池ExecutorServiceExecutorService service = Executors.newFixedThreadPool(10);//设置属性
// ThreadPoolExecutor service1= (ThreadPoolExecutor) service;
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();//2.执行指定线程的操作,需要提供一个实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread01());//适合使用于Runnableservice.execute(new NumberThread02());//适合使用于Runnable
// service.submit(Callable callable);//适合使用于Callable//3.关闭连接池service.shutdown();}
}class NumberThread01 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 50; i++) {if(i%2==0){System.out.println(Thread.currentThread().getName()+":"+i);}}}
}class NumberThread02 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 50; i++) {if(i%2!=0){System.out.println(Thread.currentThread().getName()+":"+i);}}}
}
使用线程池实例二:
ublic class ThreadPool {public static void main(String[] args) {使用线程池的工厂类Executors里面提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池ExecutorService es = Executors.newFixedThreadPool(2);调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新的线程执行es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行调用ExecutorService中的方法shutdown销毁线程池(不建议执行)es.shutdown();es.submit(new RunnableImpl());//抛出异常,线程池都挂逼了,就不能获取线程了}
}
创建多线程有4中方式:线程池,实现Callable接口,lock锁,同步代码块和同步方法(继承Thread,实现Runnable接口)
匿名内部类实现线程的创建
匿名:无名
内部类:写在其它类内部的类
匿名内部类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合成一步完成
把实现类实现类接口,重写,重写接口中能够的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而此类无名
格式:
new 父类/接口(){
重写父类/接口中的方法
}
多线程程序执行的结果每次都不一样原因在于:
JVM执行main方法,找os开辟一条main方法通向CPU的路径(开辟主线程),CPU通过这个main线程可以执行main方法,运行到创建新线程代码段时,下一步调用start();并执行run方法,此时又开辟一条通向CPU的新路径用于执行run方法,对于CPU而言就有了两条执行的路径,CPU有了选择权利,则CPU进行随机执行,人工无法干预,因此在没有设置线程等级的情况下程序执行的结果每次都不一样,两个默认同等级线程:main线程,新线程一起抢夺CPU执行权(执行时间),谁抢到执行谁。并且每个线程的执行是不以结构为单位,而是以语句为单位,只要语句间存在一定的时间间隔那么多个线程就会在这有限的时间内争夺CPU执行权从而执行相应的线程代码。这也对线程安全产生了很大的影响。
多线程实例分析:
{class MyThread00 extends Thread{@Overridepublic void run(){}}class MyThread01 extends Thread{@Overridepublic void run(){}}public static void main(String[] args) {MyThread00 mt = new MyThread00();mt.run();mt.start();new MyThread01();}对于此程序而言,main方法先压栈执行,当执行MyThread00 mt = new MyThread00();时,new MyThread00()对象于堆内存中占用空间。当栈空间执行mt.run();时,run方法进入main所在的栈进行压栈执行.此时程序是单线程程序;当执行到mt.start();时,开辟新的栈空间执行run方法,此时程序线程由单线程变为双线程(多线程)程序;当执行new MyThread01();时,程序又开辟了一个新的栈空间,执行run方法,此时程序又从双线程变为多线程程序。当程序执行到new MyThread();时,CPU有了对三个线程进行选择执行的权利,可以是main的run方法也可以执行另外两run方法。多线程好处多个线程互不影响(在不同栈空间执行)
}
四、java多线程中的线程安全问题:
1.问题的引出:
在jvm中,每份线程享有一份独立的虚拟机栈(运行栈)和程序计数器;每份进程享有一份独立的方法区和堆。然而一份进程可以细分为多份线程,则出现这样的情况:在同一份进程下的多份线程共享此进程拥有的方法区和堆。即一份进程下的方法区和堆成为了此进程下多个线程共享的资源。于是引出这么一个问题:不同的线程在操作共享数据时,使得共享数据在不停变化,最糟糕的情况下所有线程均得不到自己想要的理想数据,在得不到理想数据后,线程的执行结果也不是用户或是程序员向要等到的结果,从而使得此程序是一个“废物”程序。
2.问题的实例与解决:
创建三个窗口卖票,总票数为100张。
存在线程安全问题:
卖票过程中,卖出了不存在和重复的票;
问题出现的原因:
当某个线程操作车票过程中,尚未完成操作时,其他线程参与进来,也操作车票
问题的解决办法:
线程安全问题是不能产生的,可以让一个线程在访问共享数据时,无论是否失去了CPU的执行权;让其它的线程只能等待,当等待前线程卖完票,其他线程再进行卖票。即当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket的时候其他线程才可以开始操作ticket,这种情况即使线程a出现了阻塞,也不能被改变。
即必须保证:使用一个线程再卖票。
3.java中解决线程安全问题的方式:
在java中通过同步机制来解决线程安全问题
方式一:同步代码块
格式:
synchronized(同步监视器){需要被同步的代码,可能出现线程安全问题的代码(访问了共享数据的代码)}
注:
1.操作共享数据的代码即为需要被同步的代码
2.共享数据:多个线程共同操作的数据,如卖票问题中的票
3.同步监视器,俗称:锁。任何一个类的对象都可以充当锁。对同步监视器的要求,要求多个线程都共用一把锁。即但是必须保证多个线程的锁对象是同一个
4.同步监视器的作用:把同步代码块所住,只让一个线程在同步代码块中执行
补充:在实现Runnable接口创建多线程的方法中,可以不考虑使用this充当监视器
在继承Thread类创建多线程的方式中慎用this充当同步监视器,可以考虑使用当前类充当同步监视器
此方式的执行原理:
当多个线程一起抢夺CPU的执行权,谁抢到了谁就执行run方法进行卖票。第一个线程抢到了CPU的执行权,执行run方法,遇到synchronized代码块,这时第一个线程会检测synchronized代码块中是否有锁对象,发现有,就会获取锁对象,进入到同步中执行。第二个线程抢到了CPU的执行权,执行run方法,遇到synchronized代码块,这时第二个线程会检测synchronized代码块中是否有锁对象,发现没有,第二个线程就会进入到阻塞状态,会一直等到第一个线程归还锁对象,一直到第一个线程执行完同步中的代码,第一个线程会把锁对象归还给同步代码块,第二个线程才有可能能获取到锁对象进入到同步中执行
方式二:同步方法
若操作共享数据的代码完整的声明在一个方法中,则可以将此方法声明为同步方法。其使用使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){可能出现线程安全问题的代码(访问了共享数据的代码)}
关于同步方法的小节:
1.同步方法也会把方法内部的代码锁住,只让一个线程执行。
2.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
3.非静态的同步方法,同步监视器是this--(同步方法的锁对象是实现类对象本身也就是this)
静态的同步方法,同步监视器是:当前类本身,即当前类.class--(静态方法对象不是this,因为this是创建对象之后产生的,静态方法优先于对象,静态方法的对象是本类的class属性-->class文件对象(反射))
同步的方式的好处与局限性:
好处:解决了安全问题。
局限性:操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。
方式三:Lock --- jdk5.0新增
java.util.concurrent.locks.lock接口,lock实现提供了比实验synchronized方法和语句可获得的更广泛的锁定操作
lock接口中的方法:
void lock()获取锁void unlock()释放锁java.util.concurrent.locks.ReentrantLock lock接口
使用步骤:
1.在成员位置创建一个ReentrantLock对象,即实例化ReentrantLock
2.在可能出现安全问题的代码前调用lock接口中的lock获取锁,即调用lock()方法
3.在可能出现安全问题的代码后调用lock接口中的lock释放锁,即调用解锁方法unlock()
4.其中调用lock()方法和调用解锁方法unlock()要配和try...finally一起使用,即
try{ lock() }finally{ unlock() }
synchronized和lock的异同:
同:都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码后自动地释放同步监视器;lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
优先使用顺序:lock->同步代码块(已经进入了方法体,分配了相应资源)->同步方法(在方法体之外)
4.死锁:
死锁概念:
不同线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成死锁.死锁只是表示所有线程都处于阻塞状态,无法继续,程序本身不会抛出异常
解决方法:
专门算法原则;
尽量减少同步资源定义;
尽量避免嵌套同步
5.分析线程安全问题:
首先分析是否有多线程存在,在确定存在多线程后,确定是否有共用数据操作,如果存在共用数据则一定存在线程安全问题,此时再来考虑利用三种同步机制解决解决线程安全问题。
五、java中线程间的通信:
等待唤醒机制/线程间通信:
多线程处理同一资源,并且多线程处理任务不同
处理线程间通信原因:
多线程并发执行时,默认情况下CPU随机执行线程。当在进行多线程共同处理同一资源时,希望线程由规律执行,因此线程间需要协同通信,以此达到多线程共同处理一份资源
利用等待唤醒机制保证线程间通信可以有效利用资源。
进入到TimeWaiting(计时等待)有两种方法
1.使用sleep(long m)方法,此方法的作用是使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)毫秒结束后,线程继续执行。虽然线程进行睡眠但不会释放锁在毫秒值结束后,线程睡醒进入Runnable/Block状态,
2.使用wait(long m)方法,调用wait方法的线程进入阻塞状态,并且释放锁,wait方法如果在毫秒值结束之后还没有被notify唤醒,就会自动醒来,线程睡醒进入Runnable/Block状态,即在其它线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
3.使用Thread.yield()方法,让线程从执行台转变为就绪态
tip:Thread.yield()和Thread.sleep一样不会释放锁,线程被挂起也不会释放锁
常用线程通信的三个方法:
wait():一旦执行此方法当前线程就进入阻塞状态,并释放同步监视器
唤醒的方法:
notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait就唤醒优先级高的线程。即唤醒在此监视器上等待的单线程。即唤醒在此对象监视器上等待的单个线程。会继续执行wait方法之后的代码
notifyAll():一旦执行此方法,就会唤醒被wait的所有线程。即唤醒在此监视器上等待的所有线程
说明:
1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常
3.wait(),notify(),notifyAll()三个方法调用在java.lang.Object类中
sleep()和wait()的异同:{
同:一旦执行该方法,都可以使得当前线程进入阻塞状态
异:1)两个方法声明的位置不同:Thread类中声明静态的sleep()方法;在Object类中声明wait()方法
2)调用的范围不同:sleep()是可以在任何需要的场景下调用;wait()方法必须使用在同步代码块或者同步方法中
3)关于是否释放同步监视器:
若两个方法都使用在同步代码块或者同步方法中sleep()方法是不会释放同步监视器,而wait()是会释放同步监视器
}
对同步代码块中同步监视器和共享数据的理解和各自的要求:
同步监视器:锁,任何一个类的对象都可以当锁,多个线程共用同一把锁
共享数据:多个线程共同操作的数据,即共享数据,需要使用同步机制将操作共享数据的代码包起来。
以上是本篇小节,不喜勿喷,感谢理解
相关链接:
【JAVASE(5)】JAVASE学习--时间篇_lixxkv的博客-CSDN博客
【JAVASE(3)】JAVASE学习--异常篇_lixxkv的博客-CSDN博客