Java:多线程
本文内容:
- 什么是线程
- 线程的生命周期
- Thread实现的多线程
- Runable实现的多线程
- 线程常用函数
- 线程的控制
- 线程同步
- 线程通信
首发日期:2018-05-13
Thread实现的多线程:
实现方法:
- 定义一个类继承Thread。
- 覆盖run方法,将自定义代码写到run方法中。
- 创建子类对象就是创建线程对象。
- 子类调用Thread类中的start方法就可以执行线程,并会调用run方法。
class MyThread extends Thread{ public void run() { for (int i=0;i<10;i++) { System.out.println("子线程拿到执行权"); } } } public class Demo { public static void main(String[] args) { MyThread t=new MyThread(); t.start(); for (int i=0;i<10;i++) System.out.println("主线程运行"); } }
上述代码结果【该结果有随机性,如果想要有明显的抢夺运行权,可以增大i】:
主线程运行 子线程拿到执行权 子线程拿到执行权 子线程拿到执行权 子线程拿到执行权 子线程拿到执行权 主线程运行 主线程运行 主线程运行 主线程运行
补充:
- 执行run与start的区别:执行run仅仅相当于调用函数,并没有创建线程。而start是开启线程,并让开启的线程去执行run方法中的线程任务
Runable实现的多线程:
虽然已经有了继承Thread实现的多线程,但是由于在java中只支持单继承,一个类一旦继承了某个父类就无法再继承Thread类 ,因为这样,所以才有了Runable实现的多线程,这样的多线程是将实现接口Runable的类的对象传入Thread()中来创建线程对象。
- Runable是一个接口,Thread类实现了这个接口
实现方法:
- 定义一个类实现Runnale接口,重写run方法。 【run的权限是public的】
- 将这个类的一个对象传入Thread()中,使用Thread类直接创建线程对象。
- 线程对象调用start()。
class Car implements Runnable{ public void run() { for (int i=0;i<5;i++) { System.out.println("子线程拿到执行权"); } } } public class Demo { public static void main(String[] args) { Car c=new Car(); Thread t=new Thread(c); t.start(); for (int i=0;i<5;i++) System.out.println("主线程运行"); } }
上述代码结果【该结果有随机性,如果想要有明显的抢夺运行权,可以增大i】:
主线程运行 子线程拿到执行权 子线程拿到执行权 子线程拿到执行权 子线程拿到执行权 子线程拿到执行权 主线程运行 主线程运行 主线程运行 主线程运行
两种方式的区别:
- Runnable的多个线程实例可以使用同一个实例变量,而继承Thread实现的线程无法共享同一个实例变量
- Runnable是实现,java允许多实现,不允许多继承,所以使用实现Runnable的同时可以继承其他类。
补充:
- 还有实现callable接口的方式可以创建新线程。
- 上面的两种实现方式都不可以获取返回值,获取返回值应该使用callable的方式来创建新线程
线程常用函数:
- getId():返回该线程的标识符
- getName():返回线程的名字 【在实现Runnable的类中,没有线程对象,所以需要变成Thread.currentThread().getName();】
- setName():给线程设置名字【在new Thread()时可以传入一个参数作为线程的名字】
- currentThread():返回当前执行的线程对象
- getState():返回线程的状态
- isAlive():判断线程是否处于活动状态,返回布尔值
- join(int seconds):等待线程结束,有seconds代表最多等待秒数 【比如某个线程调用join(),主线程会等待这个线程执行完毕才会返回主线程执行】
- sleep(int seconds):让线程暂停指定秒数
- wait():让线程暂停
- interrupt():中断线程。【对于非堵塞线程,那么就会将线程中断;对于可取消的阻塞状态中的线程(Thread.sleep(), Object.wait(), Thread.join(), ),那么可以“吵醒”线程】
- isDaemon():判断线程是否是守护线程
- setDaemon():设置成守护线程
- setPriority():更改线程的优先级
- yield():暂停该线程,让出CPU执行权,重新到队列中竞争CPU权限
线程的控制:
线程等待:
- join():join函数实现的效果是,如果某个线程调用join函数,那么会等到这个线程完全执行完毕才会轮到其他线程继续执行。
- 也可以提供参数,join(n)代表等待线程执行n毫秒
守护线程
默认情况下,主线程会等待其他线程结束才会结束程序运行,设置守护线程的效果是:如果一个线程设置成了守护线程,那么主线程不会等待该线程结束就结束程序运行(如果非主线程就只有这一个的话)
- 线程.setDaemon(true);
- setDaemon必须要在线程start之前调用。
线程睡眠:
- sleep(毫秒数):线程调用sleep函数可以使线程暂停一段时间,让线程进入堵塞状态。
改变线程优先级:
线程让步:
yield():让出自己的CPU权限,重新进入队列中竞争CPU权限【这时候优先级高的占便宜】
线程同步:
为什么需要线程同步:
- 当有多个线程操作同一变量时,如果不能统一的执行完整操作,那么可能会发生线程A未执行完成,线程B过来操作变量,导致后面线程A操作这个变量时已经不是它之前取到的变量值。
- 线程同步的实质:到了需要线程安全的代码,由并行改成串行,只允许一个进程执行。
同步的方式:
- 同步代码块:synchronized(同步锁对象){同步代码} 【对于通过实现Runnable得出的多线程一般同步锁对象是this或者类名.class【或者是一些共享的对象】,理论上也同步锁对象可以任意的对象,但应该避免使用不必要的资源来作为同步锁】 【也因为同步代码块的同步锁对象可以比较随便,所以开放性比较强,使得一个类中不同的同步代码块可以使用不同的锁】
- 同步函数:synchronized 返回值类型 方法名(参数列表){同步代码}
同步锁:
- jdk5提供了新的同步锁对象来显示使用同步锁,对于同步代码块来说,在多个使用时需要使用多个锁,锁的意义并不是很明显,而同步锁类创建的对象就带有同步锁的意义。
- 常见的同步锁类(这几个锁类都实现Lock接口)有:ReentrantLock(可重入锁,递归锁),ReentrantReadWriteLock.ReadLock(读取锁),ReentrantReadWriteLock.WriteLock(写锁) 【一般都使用ReentrantLock】
- 它的操作是在需要锁住的代码之前lock对象.lock(),在操作结束后 lock对象.unlock(),
死锁:
当使用上锁后,可能会发生死锁。A拿了锁1,A想要拿锁2;B拿了锁2,B想要拿锁1;于是就发生了死锁。
解决方法:只能避免。避免相互调用彼此的锁(或者说某种独占资源)
线程通信:
线程通信最经典的例子是生产者-消费者例子:生产者生产完后提醒一下消费者来消费,消费者消费完后提醒生产者生产。【一个盘子时,生产者生产完就得叫消费者;多个盘子时,生产者判断是否没有空盘子再自己进行等待,消费者判断没有东西消费就等待】
传统方式:
- 对于还没轮到的,对象调用wait方法,等待别人唤醒自己 【wait方法调用者不是线程对象,是这个监视器(或者说是锁),如果你的锁是this时不需要前缀,不然需要调用锁对象来调用。】
- 当想唤醒另外一方时,调用notify方法来唤醒。【notify是唤醒任意一个在等待锁的进程,notifyAll是唤醒所有在等待锁的进程】
class RestRoom{ int count=0; boolean panzi=true; public synchronized void produce() { try { if(!panzi) { this.wait(); } count++; System.out.println("我生产了一个面包"+count); panzi=false; this.notify(); }catch(InterruptedException e) { e.printStackTrace(); } } public synchronized void consume() { try { if(panzi) {//有空盘子 this.wait(); } System.out.println("我消费了一个面包"+count); panzi=true; this.notify(); }catch(InterruptedException e) { e.printStackTrace(); } } } class Producer implements Runnable{ RestRoom r; Producer(RestRoom r){ this.r=r; } public void run() { for(int i=0;i<100;i++) { r.produce();//生产100次 } } } class Consumer implements Runnable{ RestRoom r; Consumer(RestRoom r){ this.r=r; } public void run() { for(int i=0;i<100;i++) { r.consume();//生产100次 } } } public class TongbuDemo { public static void main(String[] args) { RestRoom r=new RestRoom(); Producer pro=new Producer(r); Consumer con=new Consumer(r); Thread t1=new Thread(pro); Thread t2=new Thread(con); t1.start(); t2.start(); } }
使用注意:
- 如果只有一个生产者,一个消费者,在等待唤醒的只有可能是“对方”;但如果存在多个生产者或多个消费者时,那么可能会唤醒“本方”,这时可以使用notifyAll(),唤醒所有的本方,然后本方再通过判断条件来wait【一种常用的先同再异的思想】【同时因为有可能已经有线程之前就进入了判断环节,所以需要使用while才能把它留在判断环节中】。
package runable_线程; class RestRoom{ int count=0; boolean panzi=true; public synchronized void produce() { try { while(!panzi) { this.wait(); } count++; System.out.println("我生产了一个面包"+count); panzi=false; this.notifyAll(); }catch(InterruptedException e) { e.printStackTrace(); } } public synchronized void consume() { try { while(panzi) {//有空盘子 this.wait(); } System.out.println("我消费了一个面包"+count); panzi=true; this.notifyAll(); }catch(InterruptedException e) { e.printStackTrace(); } } } class Producer implements Runnable{ RestRoom r; Producer(RestRoom r){ this.r=r; } public void run() { for(int i=0;i<100;i++) { r.produce();//生产100次 } } } class Consumer implements Runnable{ RestRoom r; Consumer(RestRoom r){ this.r=r; } public void run() { for(int i=0;i<100;i++) { r.consume();//生产100次 } } } public class TongbuDemo { public static void main(String[] args) { RestRoom r=new RestRoom(); Producer pro=new Producer(r); Producer pro2=new Producer(r); Consumer con=new Consumer(r); Consumer con2=new Consumer(r); Thread t1=new Thread(pro); Thread t2=new Thread(pro2); Thread t3=new Thread(con); Thread t4=new Thread(con2); t1.start(); t2.start(); t3.start(); t4.start(); } }
新增了lock接口的一系列实现类后,可以使用Condition来进行线程通信:
- 可以使用 同步锁对象.newCondition() 来获取Condition对象
- Condition中等待的方式变成了condition.await();提醒的方式变成了condition.sign()和condition.signAll();
- 新的Condition对象允许一个锁能有多个Condition对象,所以我们可以使用不同的Condition对象来代表不同的身份。
在单一的生产者和消费者时,代码与上面相同大略。
当多个多个生产者或多个消费者时,那么就需要定义不同的Condition对象了,不同的condition对象,唤醒的线程也不一样,比如可以定义一个condition对象专门代表消费者,那么使用这个对象.signal()时就会唤醒消费者。
package runable_线程; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; class RestRoom{ int count=0; boolean panzi=true; ReentrantLock lock=new ReentrantLock(); Condition condition_pro = lock.newCondition(); Condition condition_con = lock.newCondition(); public void produce() { lock.lock(); try { if(!panzi) { condition_pro.await(); } count++; System.out.println("我生产了一个面包"+count); panzi=false; // this.notifyAll(); condition_con.signal();//唤醒消费者 }catch(InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } public void consume() { lock.lock(); try { if(panzi) {//有空盘子 condition_con.await(); } System.out.println("我消费了一个面包"+count); panzi=true; // this.notifyAll(); condition_pro.signal();//唤醒生产者 }catch(InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } } class Producer implements Runnable{ RestRoom r; Producer(RestRoom r){ this.r=r; } public void run() { for(int i=0;i<100;i++) { r.produce();//生产100次 } } } class Consumer implements Runnable{ RestRoom r; Consumer(RestRoom r){ this.r=r; } public void run() { for(int i=0;i<100;i++) { r.consume();//生产100次 } } } public class TongbuDemo { public static void main(String[] args) { RestRoom r=new RestRoom(); Producer pro=new Producer(r); Producer pro2=new Producer(r); Consumer con=new Consumer(r); Consumer con2=new Consumer(r); Thread t1=new Thread(pro); Thread t2=new Thread(pro2); Thread t3=new Thread(con); Thread t4=new Thread(con2); t1.start(); t2.start(); t3.start(); t4.start(); } }
补充:
- 同步中,线程方法的wait和sleep的区别:wait()会释放自己已经拿到的同步锁,而sleep不会释放自己拿到的同步锁。
上一篇: Java多线程--锁的优化