多线程
- 相关理解
- 进程:分配应用程序的内存空间
- 线程:负责进程中内容执行的控制单元,也叫执行路径或执行情景
- 多线程:一个进程可以有多个执行路径。一个进程至少有一个线程
- 任务:每个线程要运行的内容
- CPU切换线程执行可以时间片来切换执行不同进程。随机切换。
- 好处
- 可以让多部分同时运行
- 弊端
- 不同的进程开启了很多的线程,CPU分给每个线程的执行时间变少,因此线程运行变慢,效率变低(可以加CPU)
- JVM的多线程
- JVM启动时启动了多个线程,至少可以分析出两个线程
- 主线程(main函数执行),任务代码都定义在main函数
- 垃圾回收线程(GC),任务代码定义在垃圾回收器内部
- 其他
- 主线程结束,其他线程不一定结束。所有线程结束,jvm结束。
- finalize();子类重写该方法可以执行其他清除,不写的话,直接默认被垃圾回收器清除。Object类的方法
- System.gc(); 运行垃圾回收器,告诉垃圾回收器收垃圾,什么时候回收垃圾是随机的。
- JVM启动时启动了多个线程,至少可以分析出两个线程
- 多线程创建
- windows当中,任务管理器创建需要的进程和线程
- 主线程名是main
- 继承Thread类来创建线程
- 定义一个类继承Thread类
- 覆盖Thread类中的run()方法,run()调用需要执行的代码
- 建一个该类的对象,创建线程
- 调用start()方法启动线程,run()方法自动运行
- 一个类有父类,就不要继承Thread了(不然多继承了)
- 快速创建线程可以使用匿名内部类
- 相关方法
- getName() 获取线程的名称,线程对象创建后,名称就定义了。运行run()方法也可以获取其名称
- currentThread() 返回当前正在执行线程的引用
- start() 开启一个线程
- stop() 停止当前所有线程的运行(危险,慎用)
- sleep() 使当前线程睡眠
- wait() 使当前线程处于冻结状态
- notify() 从线程池中唤醒一个线程
- interrupt() 中断当前的wait或sleep等的状态
- setDaemon(true)设置线程为守护线程,需要在start()前设置。前台线程全部结束,该线程会自动结束,无论是否是冻结状态。守护线程也叫后台线程,true标记为守护线程
- join() 执行该方法就是冻结当前运行线程,执行该线程。该线程运行完,在继续运行刚刚中止的线程。可能抛出异常InterruptedException
- toString() 返回字符串形式的线程名称,优先级(获取CPU执行权的几率,越大获取几率越高(1-10))和线程组(线程的组的划分,可以创建线程的时候指定)
- setPriority(Thread.MAX_PRIORITY) 设置进程执行优先级,MAX_PRIORITY 10 ,MIN_PRIORITY 1,NORM_PRIORITY 5(默认为5)
- yield() 释放当前线程执行权。
/*三个线程,自定义的两个,主线程一个*/
class Test{
public static void main(String[] args){
/*
创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码同时运行
运行的指定代码就是这个执行路径的任务。
jvm创建的主线程的任务都定义在了主函数中
自定义的线程的任务在哪里?
Thread类用于描述线程,线程是需要任务的,所以Thread类也对任务描述
这个任务通过Thread类的run()方法来体现(run方法封装了自定义线程的任务)
run()方法中定义了线程运行的任务代码
*/
DemoThread d1 = new DemoThread("旺财");
DemoThread d2 = new DemoThread("qiang");
d1.start();/*开启线程,调用run()方法*/
for(int x = 0; x < 20; x++){
System.out.println(x+">>>"+Thread.currentThread().getName());
}
d2.start();
}
}
class DemoThread extends Thread{
private String name;
DemoThread(String name){
super(name);/*调用父类构造函数,自动修改进程名称*/
}
/*重写run()就是为了运行自定义任务*/
public void run(){
show();
}
public void show(){
for(int x = 0; x < 10; x++){
for(int y = -999999999; y < 999999999; y++){}
System.out.println(name+"....x="+x+"....name="+Thread.currentThread().getName());/*获取当前运行线程的名字,Thread.currentThread().getName()*/
}
}
}
-
-
- 上述代码运行在栈中的体现
- 每一个线程一个栈。主函数先运行main(),开启一个栈,到了d1.start(),运行run(),开启一个栈,到了d2.start(),运行run(),开启一个栈
- 每个线程中的代码在各自的栈中分别执行
- 如果主线程或其他某个线程出现异常,该线程执行中断,不影响其他线程。
- 上述代码运行在栈中的体现
- 实现Runnable接口创建线程(有父类,但需要创建多线程,可以从API查找,更常用)
- 定义类实现Runnable接口,覆盖run()方法,将线程任务代码封装到run()中
- 创建Thread对象,传入实现了Runnable接口的子类对象,即可调用线程该对象创建的任务
- 向Thread对象传入Runnable接口子类对象是因为它封装了任务代码,传进去,线程开启前才能运行指定任务。
-
class Test{
public static void main(String[] args){
DemoThread d = new DemoThread();/*创建了一个任务*/
Thread t1 = new Thread(d);/*传入实现了Runnable接口的对象,相当于分配了任务*/
Thread t2 = new Thread(d);
t1.start();/*执行任务*/
t2.start();
for(int i = 0; i < 10; i++){
for(int j = -999999999; j < 999999999; j++){}
System.out.println(i+"..."+Thread.currentThread().getName());
}
}
}
class DemoThread implements Runnable{
public void show(){
for(int i = 0; i < 10; i++){
for(int j = -999999999; j < 999999999; j++){}
System.out.println(i+"..."+Thread.currentThread().getName());
}
}
public void run(){
show();
}
}
-
-
- 传入Runnable对象的解释
-
class Thread{
private Runnable r;
Thread(){
}
Thread(Runnable r){
this.r = r;/*传入的Runnable对象相当于在这里将引用赋值给了内部*/
}
public void run(){
if(r != null)
r.run();/*运行的时候看传入自定义的任务没,传入就执行自定义的*/
}
public void start(){
run();
}
}
-
-
- Runnable接口的好处
- 将线程任务从线程子类分离出来,将线程的任务进行对象封装,不必继承Thread所有方法。
- 避免了Java单继承局限性
- 与Thread相比有了思想上的变化,继承Thread是让任务成为他的一部分,实现Runnable是将任务封装成一个对象。
- Thread也实现了Runnable是因为和其他对象可以向上抽取任务的run()方法。
- Runnable接口的好处
-
- 线程的状态
- start():线程从创建到运行
- run():线程从运行到消亡(运行完了线程就消亡了)
- stop():线程从运行到消亡(主动关闭线程,不安全)
- sleep(time):线程从运行到冻结,time(毫秒)时间后运行或临时阻塞
- wait():线程从运行到冻结(一直冻结)
- notify():线程从冻结到运行或临时阻塞
- CPU执行资格:可以被CPU处理,在处理队列中排队
- CPU执行权:正在被CPU处理
- 线程使用
- 同一段代码,好多人都需要同时用,可以封装成线程。
- Runtime异常的情况
- 功能没问题,传值出现问题
- 功能状态发生问题
- Thread t = new Thread(); t1.start();
- 线程已开启,处于一种状态,再开启会出现问题
- 线程安全(可以参看实际问题中的买票代码)
- 下边代码是线程执行的代码
- 因为在线程执行的时候有随机性,假如说现在num=1,有t1和t2两个线程
- CPU执行t1,先判断num为1大于0,然后转换执行t2,此时num不变还是1大于0
- 切换到t1,执行输出,1,切换到t2输出0,因而输出了不应该输出的数据。有安全隐患
- 可以加上sleep()方法观察一下
public void sale(){
while(true){
if(num > 0){
/*sleep可能抛出异常,但是实现的接口没有抛出异常,所以不能声明只能catch*/
/*try{
Thread.sleep(10);这里会释放执行权
}
catch(InterruptedException e){
}*/
System.out.println(Thread.currentThread().getName()+">>>>"+num--);
}
else
break;
}
}
- 线程安全产生的原因
- 多个线程操作共享数据
- 操作共享数据的代码超过一条
- 线程安全问题的解决
- 同步
- 将操作共享数据的多条代码封装成整体(使用同步代码块),有一个线程在执行这个代码,其他线程不能执行,该线程执行完,其他线程继续执行。
- 表现形式
- 同步代码块代码块, 同步函数(synchronized可以修饰函数)
- 同步代码块格式
- 同步
synchronized(对象){/*放一个标记对象,相当于整个任务对象的标记(锁,同步锁),后边用于同步代码监视,不能放到方法里,放到成员变量里*/
/*放需要被同步代码;*/
if(num > 0){
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+">>>>"+num--);
}
else
break;
}
-
-
- 上述代码解释
- 一个线程先判断该对象是否被持有,没有被持有则占用他,执行里边的代码,执行到sleep()的时候相当于释放了执行权
- CPU转而执行其他线程,但是每个线程在执行同步代码的时候,先判断对象持有情况,一直都被持有,则无法执行同步代码
- 直到第一个线程sleep()结束并执行完接下来的同步代码,才会释放对象的持有。这个时候CPU再次挑选线程执行相应代码。
- 好处
- 解决了线程安全问题。
- 弊端
- 相对降低了效率(执行同步代码的线程不会一直占有CPU,执行权转到外部线程时,外部线程会一直判断同步锁,却无法执行代码)
- 前提
- 必须有多个线程使用同一个锁(锁写到方法里,每个线程一个锁,相当于没加,所以写到成员变量里)
- 同步函数格式
- 上述代码解释
-
public synchronized void run(){
while(true){
if(num > 0){
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+">>>"+num--);
}
else{
break;
}
}
}
-
-
-
- 将函数同步,一个线程占有该函数之后,除非将里边的所有代码全部执行完,否则不会放开该函数占有权。就算放开执行权,其他线程无法占有该函数,也无法执行,只能最开始的线程执行。
- 所以只将需要同步的代码封装到函数里即可,然后调用该函数
- 同步函数用的锁是this对象。
-
-
public void run(){
while(true){
show();
}
}
public synchronized void show(){
if(num > 0){
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+">>>"+num--);
}
}
-
-
- 同步函数和同步代码块的区别:
- 同步函数的锁只能是this对象,同步代码块可以用不同的对象。
- 同步代码块用了this,可以简写为同步函数。
- 建议使用同步代码块。
- 下边的代码可以测试出同步函数和同步代码块是否在使用同一个锁
- 当运行run()方法的时候,通过flag来判断当前线程要运行哪一个run()方法,t1默认运行同步代码块
- 为了不让t1.start()和t2.start()运行的太快,中间加一个睡眠,来达到先让t1的线程判断flag,然后再执行main线程后边的方法。
- t1线程运行起来之后,flag被修改为false,然后运行同步函数。
- 同步函数默认调用当前对象锁,同步代码块传入的是this对象,所以两个线程操作的是同一个锁,不会再有线程安全问题。
- 同步函数和同步代码块的区别:
-
class Ticket implements Runnable{
private int num = 100;
boolean flag = true;
public void run(){
if(flag)
while(true){
synchronized(this){
if(num > 0){
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+">>>code>>>"+num--);
}
}
}
else
while(true){
show();
}
}
public synchronized void show(){
if(num > 0){
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+">>>function>>>"+num--);
}
}
}
class Test{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
t.flag = false;
t2.start();
}
}
-
-
- 静态同步函数
- 使用的锁是当前字节码文件所属的对象
- 静态同步函数
-
public static synchronized void show(){
if(num > 0){
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+">>>function>>>"+num--);
}
}
-
-
-
- this.getClass()可以获取当前静态函数所在字节码文件所属的对象
- 类名.class也可以获取当前静态函数所在字节码文件所属的对象(用静态属性获取)
-
-
synchronized(this){
if(num > 0){
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+">>>code>>>"+num--);
}
}
-
-
- 单例模式中懒汉式和饿汉式的线程安全问题。
-
/*饿汉式*/
class Single{
private static final Single s = new Single();
private Single(){
}
public static Single getInstance(){
/*将这个方法放到多线程返回没有问题,因为只有一句话且变量已经是final*/
return s;
}
}
/*懒汉式-同步函数*/
class Single{
private static Single s = null;
private Single(){
}
public static synchronized Single getInstance(){
/*将该方法放到多线程中返回可能会有安全隐患,同时调用可能返回不同的对象,有多句话,执行顺序可能不同,可以加同步函数,但降低了效率*/
if(s == null)
Single = new Single();
return s;
}
}
/*懒汉式-同步代码块*/
class Single{
private static Single s = null;
private Single(){
}
public static Single getInstance(){
/*不能用this.getClass(),因为非静态*/
/*前边s为空的时候会判断锁,不为空则不判断锁,减少了锁资消耗*/
/*加锁解决线程安全问题,多加一个if提高了效率,解决了安全和效率。虽然多加了if,但判断s的时候这个不可少*/
if(s == null){
synchronized(Single.class){
if(s == null)
Single = new Single();
return s;
}
}
}
}
-
-
- 同步中的死锁
- 常见情景
- 同步嵌套:一共两个锁,两个线程,每个线程必须同时拿到两个锁才能进行执行功能,但是每个线程只拿到一个,想用对方的,但对方都没放开,产生死锁。
- 下边的代码在调用的过程中会在执行到某次的时候,每个线程各自拿到一个锁(this或obj),想得到对方那个,却得不到
- 同步嵌套:一共两个锁,两个线程,每个线程必须同时拿到两个锁才能进行执行功能,但是每个线程只拿到一个,想用对方的,但对方都没放开,产生死锁。
- 常见情景
- 同步中的死锁
-
/*死锁程序1*/
class Ticket implements Runnable{
private int num = 100;
Object obj = new Object();
boolean flag = true;
public void run(){
if(flag)
while(true){
synchronized(obj){
show1();
}
}
else
while(true)
show();
}
public synchronized void show(){
synchronized(obj){
if(num > 0){
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+">>>>"+num--);
}
}
}
public synchronized void show1(){
if(num > 0){
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+">>>>"+num--);
}
}
}
class Test{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{
Thread.sleep(10);
}
catch(InterruptedException e){
}
t.flag = false;
t2.start();
}
}
/*死锁程序2*/
class Lock implements Runnable{
private boolean flag;
Lock(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName()+"if>>>locka");
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"if>>>lockb");
}
}
}
else{
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"else>>>lockb");
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName()+"else>>>locka");
}
}
}
}
}
class MyLock{
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
class Test{
public static void main(String[] args){
Lock la = new Lock(true);
Lock lb = new Lock(false);
Thread t1 = new Thread(la);
Thread t2 = new Thread(lb);
t1.start();
t2.start();
}
}
-
-
- 线程间通信
- 多个线程处理同一资源,但是任务不同(四个车同时往进拉煤,三个车同时往外运煤)
- 赋值:input
- 取值:output
- 资源:resource
- 线程间等待唤醒机制
- 下边三个方法(监视器方法)都必须定义在同步中,用于操作线程状态,必须要明确操作的是哪个锁的线程。
- wait():让线程处于冻结状态,会抛出中断异常(InterruptedException)(释放CPU执行权和执行资格,并将该线程存到该锁的线程池)
- notify():唤醒该锁线程池中的一个任意线程(使线程具备执行资格)
- notifyAll():唤醒该锁线程池中所有线程(使线程具备执行资格)
- sleep与wait的异同
- sleep必须指定时间,wait可以不指定时间
- sleep和wait都释放了CPU执行权
- sleep没有释放线程锁,wait释放了线程锁
- 操作的是哪个锁,就用哪个锁的方法来处理该线程
- 锁也叫监视器。
- 线程操作方法放到了Object类中,是因为这些方法都是监视器(锁)的方法,任意对象都可以作为监视器(锁),故而放到里边
- 下边三个方法(监视器方法)都必须定义在同步中,用于操作线程状态,必须要明确操作的是哪个锁的线程。
- 多个线程处理同一资源,但是任务不同(四个车同时往进拉煤,三个车同时往外运煤)
- 线程间通信
-
/*
Input和Output都需要使用同一个对象,但使用单例全局就只能有一个对象
所以可以创建好对象,然后将对象传进来。
*/
/*
线程间等待唤醒机制,适用于单生产消费模式
wait()
notify()
notifyAll()
*/
class Resource{
String name;
String sex;
boolean flag = false;/*用来标记资源是否被赋值。*/
}
class Input implements Runnable{
Resource r;
Input(Resource r){
this.r = r;
}
public void run(){
int x = 0;
while(true){
synchronized(r){
if(r.flag)
try{
r.wait();
}
catch(InterruptedException e){
}
if(x == 0){
r.name = "Easul";
r.sex = "male";
}
else{
r.name = "王文博";
r.sex = "女";
}
r.flag = true;
r.notify();
}
x =(++x) % 2;
}
}
}
class Output implements Runnable{
Resource r;
Output(Resource r){
this.r = r;
}
public void run(){
while(true){
synchronized(r){
if(!r.flag)
try{
r.wait();
}
catch(InterruptedException e){
}
System.out.println(r.name+">>>"+r.sex);
r.flag = false;
r.notify();
}
}
}
}
class Test{
public static void main(String[] args){
/*
创建资源
创建任务
创建线程
开启线程
*/
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
/*
将成员变量私有化之后,变量的赋值在成员方法里执行,所以应该在方法里加锁
同样把等待和唤醒拿过来就可以用了
对于变量的赋值,因为是使用this锁,所以使用同步函数
*/
class Resource{
private String name;
private String sex;
private boolean flag = false;/*用来标记资源是否被赋值。*/
public synchronized void set(String name, String sex){
if(flag)
try{
this.wait();/*wait()醒了之后,往下走,不再判断flag*/
}
catch(InterruptedException e){
}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out(){
if(!flag)
try{
this.wait();
}
catch(InterruptedException e){
}
System.out.println(this.name+"<<<"+this.sex);
flag = false;
this.notify();
}
}
class Input implements Runnable{
Resource r;
Input(Resource r){
this.r = r;
}
public void run(){
int x = 0;
while(true){
if(x == 0){
r.set("Easul", "male");
}
else{
r.set("王文博", "女");
}
x =(++x) % 2;
}
}
}
class Output implements Runnable{
Resource r;
Output(Resource r){
this.r = r;
}
public void run(){
while(true){
r.out();
}
}
}
class Test{
public static void main(String[] args){
/*
创建资源
创建任务
创建线程
开启线程
*/
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
-
-
- 多生产者,多消费者
- 多次生产和死锁原因
- if判断标记,只判断一次,可能导致不该运行的线程运行,数据出现错误。
- notify只能唤醒一个线程,有很大几率无法唤醒对方线程。(加while会导致死锁)
- 解决多次生产和死锁问题
- while判断标记,解决了线程获取执行权后是否执行的问题。
- notifyAll解决了本方线程一定能够唤醒对方线程的问题。(唤醒了所有线程,降低了效率,需要判断本方线程)
- 多次生产和死锁原因
- 多生产者,多消费者
-
/*
多生产者:t0 t1
多消费者:t2 t3
假设t0先拿到CPU,生产烤鸭1,通知线程池其他线程运行(随机运行一个),线程池没有,则从阻塞队列中挑一个。自己wait()
t1拿到CPU,t1判断有烤鸭,wait()
t2拿到CPU,t2判断有烤鸭,消费烤鸭1,通知线程池其他某个线程运行(t0或t1),t0唤醒,自己wait()
t3拿到CPU,t3判断没有烤鸭,wait()
t0拿到CPU,生产烤鸭2,通知线程池其他线程运行(t1 t2或t3),t1唤醒,自己wait()
t1拿到CPU,生产烤鸭3,通知线程池其他线程运行(t0 t2或t3),自己wait()
于是有了烤鸭2和烤鸭3,烤鸭2无法消费
这里问题在于t1生产烤鸭3的时候不知道已经生产了烤鸭2,所以可以在判断一下有没有生产过(while)。
直接while,按上述步骤,最后唤醒t1,然后全部wait()
但是因为无法唤醒某个指定线程所以会死锁.只能全部唤醒。
*/
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name){
while(flag)/*判断自己等待后是否已经有线程做过自己做过的事情*/
try{
wait();
}
catch(InterruptedException e){
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"生产了"+this.name);
this.flag = true;
notifyAll();/*避免没人工作而唤醒所有线程*/
}
public synchronized void out(){
while(!flag)
try{
wait();
}
catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+"消费了"+this.name);
this.flag = false;
notifyAll();
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
while(true)
r.set("烤鸭");
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
public void run(){
while(true)
r.out();
}
}
class Test{
public static void main(String[] args){
Resource r = new Resource();
Producer p = new Producer(r);
Consumer c = new Consumer(r);
Thread t0 = new Thread(p);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
-
-
-
- 锁的升级
- 可以使用java.util.concurrent.locks包中的Lock接口和Condition接口
- Lock接口替代了synchronized,将同步和锁封装成了对象
- Condition接口替代了Object的监视器方法(wait notify notifyAll),将其方法封装成了对象。一个Lock可以挂多个Condition。每个Condition都有一组监视器
- Lock与synchronized的区别
- synchronized对于锁的操作是隐式的,看不到获取与释放锁的过程
- Lock对于锁的操作是可控的
- Lock相关方法
- lock() 开启锁
- unlock() 释放锁
- newCondition() 获取一组监视器对象,类型为Condition,同一个锁可以获取多组监视器
- Condition相关方法
- await() 将线程从运行到冻结
- signal() 将线程从冻结到阻塞
- signalAll() 将线程池中所有冻结线程恢复到阻塞
- 上边的代码升级
- 可以使用java.util.concurrent.locks包中的Lock接口和Condition接口
- 锁的升级
-
-
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();/*创建了锁对象,ReentrantLock是互斥锁*/
/*通过一个锁,获取多个监视器,监视不同线程*/
private Condition producer_con = lock.newCondition();/*获取一个锁上的监视器*/
private Condition consumer_con = lock.newCondition();
public void set(String name){
lock.lock();/*获取锁*/
try{
while(flag)
try{
producer_con.await();/*等待时放到了生产者线程池,用时唤醒生产者线程池即可*/
}
catch(InterruptedException e){
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"生产了"+this.name);
this.flag = true;
consumer_con.signal();/*唤醒消费者监视器,只唤醒一个就可以了,提高了效率*/
}
finally{
lock.unlock();/*释放锁,如果发生异常可以放到finally*/
}
}
public void out(){
lock.lock();
try{
while(!flag)
try{
consumer_con.await();
}
catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+"消费了"+this.name);
this.flag = false;
producer_con.signal();
}
finally{
lock.unlock();
}
}
}
class Producer implements Runnable{
private Resource r;
Producer(Resource r){
this.r = r;
}
public void run(){
while(true)
r.set("烤鸭");
}
}
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r){
this.r = r;
}
public void run(){
while(true)
r.out();
}
}
class Test{
public static void main(String[] args){
Resource r = new Resource();
Producer p = new Producer(r);
Consumer c = new Consumer(r);
Thread t0 = new Thread(p);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
-
-
-
- 同步里只能有一个线程执行,但是活着的可以不止一个
-
-
/*
如果t0 t1 t2全部调用show(),进入wait()状态,t3调用method(),然后notifyAll()
那么show()中的t0 t1 t2都会唤醒,可能会获得执行权,
但是只要没有拿到同步锁,就无法向下执行代码,
所以可以获取执行权,但是不会向下执行代码。谁有锁谁执行
*/
class Demo{
void show{
wait();
}
void method{
wait();
code
notifyAll();
}
}
-
-
- 停止线程
- 中断:一种冻结状态,中止正在运行的状态
- 线程的stop()方法
- 线程的run()方法结束
- 任务中都有循环结构,控制循环就可以结束任务
- 控制循环结束用标记来控制,常用。while(flag){}
- 如果线程处于wait(冻结状态)或sleep,就无法结束线程(无法读取到标记)
- 使用interrupt(),中断当前wait()或sleep()的状态使其具备CPU执行资格
- 因为是强制执行,wait或sleep可能会抛出InterruptedException
- 停止线程
-
实际问题
- 买票
/*
卖票100张
4个窗口(同时卖)
*/
/*
1 用继承,需要创建四个线程,每个线程都执行100次,不符合实际
2 创建一个线程,运行四次。错误方式,一个线程只能开启一次
3 使用静态的num,但如果有多种100张票,num无法实现多种使用
4 使用实现,将任务封装,然后交给线程处理。
*/
class Ticket implements Runnable{
private int num;/*100张票*/
Ticket(int num){
this.num = num;
}
public void sale(){/*买票的代码需要被同时使用*/
while(true){
if(num > 0)
System.out.println(Thread.currentThread().getName()+">>>>"+num--);
else
break;
}
}
public void run(){
sale();
}
}
class Test{
public static void main(String[] args){
Ticket t = new Ticket(100);/*创建线程任务对象*/
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
-
- 内存解释
- 对于这个代码来说,创建了任务对象,放到堆中,创建了四个线程,每个线程所使用的数据都是这个任务对象的数据,从而实现了数据共享,不需要静态。
- 运行后出现的输出不规律
- 多核CPU交替运行可能导致运算的顺序不同
- 输出和运算顺序不一样(参考异常输出和其他线程共同运行的情况)
- 内存解释
- 如果错误,发生在哪一行
- 错误在第一行,因为没有重写run()方法,相当于把接口的run()方法继承了过来,是抽象类,但是没有abstract关键字
class Demo implements Runnable{
public void run(Thread t){
}
}
输出1还是2
- 输出2,因为下边是子类对象,传的任务对象相当于是Thread对象的重写的run(),但是子类对象又重写了,所以运行子类对象的run().子类为主。
new Thread(new Runnable(){
public void run(){
System.out.println(1);
}
}){
public void run(){
System.out.println(2);
}
}.start();