Java 多线程
1. 多线程
进程:正在运行的程序,是系统进行资源和调度的独立单位,每一进程都有独立的内存空间和系统资源
多进程的意义:单进程的计算机只能做一件事情,多进程可以执行多个进程 ,提供CPU的使用率,实际是CPU在不同进程之间的高效切换
线程:在同一个进程内可以执行多个任务,而每一个任务都可以看成是一个线程,是程序的执行单元,执行路径,是程序使用CPU的最基本单位,多线程有多条执行路径
多线程的意义:提供程序的使用率,程序的执行都是在抢CPU的执行权,线程的执行具有随机性
Java程序的运行原理:由java命令启动JVM,JVM启动相当于启动了一个进程
JVM虚拟机的启动时单线程的,垃圾回收线程也要启动,最少需要启动两个 线程
2. 多线程实现
2.1 继承Thread类,该子类重写run()方法,创建对象,启动线程
run()和start()的区别:
run():仅仅是封装被线程执行的代码,调用时普通方法。
start():首先启动了线程,然后再由JVM去调用线程的run()方法
public static void main(String[] args){ MyThread my1 = new MyThread(); MyThread my2 = new MyThread();
my1.setName("xiaojignzi");
my2.setName("xiaojunzi");
my1.start(); //my1.start(); IllegalThreadStateException:非法的线程状态异常,相当于一个线程被调用了两次 my2.start(); }
获取线程对象的名称
public class MyThread extends Thread { @Override public void run(){ for(int x = 0; x < 100; x++){ System.out.println(getName() + "-----" + x); } } }
如何获取main方法所在的线程对象的名称
Thread.currentThread().getName()
线程的调度
计算机只有一个CPU时,CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。
线程具有两种调度模型
1. 分时调度模型:所有的线程轮流调用CPU的使用权,平均分配给每个线程占用CPU的时间片
2. 抢占式调度模型,优先让优先级高的线程使用CPU,如果线程的优先级相同会随机选择一个,优先级高的线程获取CPU时间片相对多一些
java使用抢占式调度模型:
int getPriority():获取线程的优先级
setPriority(int):设置线程的优先级
默认的优先级为5,可以设置的优先级范围是1-10
线程的控制:
线程休眠:public static void sleep(long millis):在指定的毫秒内让当前正在执行的线程休眠(暂停实现)。此操作受到系统计时器和调度程序的精确度和准确性的影响
线程加入:public final void join():等待本线程终止,程序再继续向后面执行
线程礼让:public static void yield():暂停当前正在执行的线程对象,并执行其他线程,不能靠它保证每个线程轮流依次指向
线程守护:public final void setDaemon(boolean on):将该线程标记为守护线程或者用户线程,当正在运行的线程都是守护线程时,java虚拟机退出,该方法必须在启动线程前调用,写在main函数时,main主线程执行完,则退出
线程结束:public void stop:让线程停止,已经过时的函数。
public void interrupt:中断线程
线程的生命周期:
新建:创建线程对象
就绪:有执行资格,没有执行权
运行:有执行资格,有执行权
阻塞:由于一些操作可以让线程处于该状态,没有执行资格,没有执行权,而另一些操作可以把它激活,激活后处于就绪状态
死亡:线程对象变成垃圾,等待被回收
2.2 实现Runnable接口
1. 自定义类MyRunnable实现Runnable接口
2. 重写run()方法
3. 创建MyRunnable类的对象
4. 创建Thread类的对象,并把3步骤的对象作为构造函数传递
public static void main(String[] args){ MyRunnable my = new MyRunnable(); Thread my1 = new Thread(my); Thread my2 = new Thread(my); my1.setName("wangwang"); my2.setName("hahah"); my1.start(); my2.start(); } public class MyRunnable implements Runnable{ @Override public void run() { for(int x = 0; x < 100; x++){ System.out.println(Thread.currentThread().getName() + x); } } }
好处:可以避免由于java单继承带来的局限性
适合多个相同程序的代码去处理一个资源的情况,把线程通程序的代码,数据有效的分离,较好的体现了面向对象的设计思想
线程的安全性问题:
A:是否是多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
public class SellTicket implements Runnable { private int tickets = 100; @Override public void run() { // TODO Auto-generated method stub while(true){ if(tickets > 0){ System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票"); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } public class SellTicketsDemo { public static void main(String[] args){ SellTicket st = new SellTicket(); Thread t1 = new Thread(st); Thread t2 = new Thread(st); Thread t3 = new Thread(st); t1.start(); t2.start(); t3.start(); } }
上述的操作存在线程的安全性问题:
解决方法:把多条语句的共享数据包装成一个整体,当有线程在执行时,别的线程不能执行
同步代码块:synchronized(obj){}
public class SellTicket implements Runnable { private int tickets = 100; private Object obj = new Object(); @Override public void run() { // TODO Auto-generated method stub while (true) { synchronized (obj) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票"); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } }
同步代码块的锁是任意的对象。
同步方法的格式以及锁的问题:this
静态方法的锁对象:对象名.class
线程安全的类:
StringBuffer,Vector,Hashtable
JDK5后出现的Lock锁的应用:
Lock(接口):
void lock():获取锁
void unlock():释放锁
ReentrantLock是Lock的实现类
例火车票卖票:
public class SellTickesDemo { public static void main(String[] args){ SellTicket st = new SellTicket(); Thread t1 = new Thread(st); Thread t2 = new Thread(st); Thread t3 = new Thread(st); t1.start(); t2.start(); t3.start(); } } public class SellTicket implements Runnable { private int tickets = 100; private Lock lock = new ReentrantLock(); @Override public void run() { // TODO Auto-generated method stub while(true){ try{ lock.lock(); if(tickets > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票"); } }finally{ lock.unlock(); } } } }
死锁问题的概述和使用:
同步的弊端:效率低,如果出现了同步嵌套,就容易出现死锁的问题
死锁的问题和代码:
指两个或者两个以上的线程在执行的过程中,因争夺资源而产生的一种互相等待的现象
public class MyLock { // 创建两把锁对象 public static final Object objA = new Object(); public static final Object objB = new Object(); }
public class DieLock extends Thread { private boolean flag; public DieLock(boolean flag){ this.flag = flag; } @Override public void run(){ if(flag){ synchronized(MyLock.objA){ System.out.println("if objA"); synchronized(MyLock.objB){ System.out.println("if objB"); } } }else{ synchronized(MyLock.objB){ System.out.println("else objB"); synchronized(MyLock.objA){ System.out.println("else obja"); } } } } }
public class DeadDemo { public static void main(String[] args){ DieLock d1 = new DieLock(true); DieLock d2 = new DieLock(false); d1.start(); d2.start(); } }
有可能产生死锁现象:此时的输出为:else objB if objA
多线程之间的通信问题:同步锁解决。
等待唤醒机制
生产者:是否有数据,如果有就等待,没有就生产,生产完通知消费者来消费
消费者:是否有数据,如果有就消费,没有就等待,通知生产者生产数据
Object提供了三个方法
wait():等待
notify():唤醒单个线程
notifyAll():唤醒所有线程,所以定义在object中
这些方法的调用必须通过锁对象来调用
锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中
notify和notifyAll的区别
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
循环里调用 wait 和 notify,不是在 If 语句
现在你知道wait应该永远在被synchronized的背景下和那个被多线程共享的对象上调用,下一个一定要记住的问题就是,你应该永远在while循环,而不是if语句中调用wait。因为线程是在某些条件下等待的——在我们的例子里,即“如果缓冲区队列是满的话,那么生产者线程应该等待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,导致即使条件没被满足,你的线程你也有可能被错误地唤醒。所以如果你不在线程被唤醒后再次使用while循环检查唤醒条件是否被满足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者开始小号数据。所以记住,永远在while循环而不是if语句中使用wait!
出处:https://blog.csdn.net/djzhao/article/details/79410229
举例:生产者和消费者:
public class Student { String name; int age; boolean flag; } public class GetThread implements Runnable { private Student s; public GetThread(Student s) { this.s = s; } @Override public void run() { // TODO Auto-generated method stub while (true) { synchronized (s) { while (!s.flag) { try { s.wait(); // 等待以后立即释放锁,上面使用while的区别在于执行完这个语句以后仍然会去判断条件是否满足 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(s.name + "-----" + s.age); s.flag = false; s.notify(); } } } } public class SetThread implements Runnable { private Student s; private int x = 0; public SetThread(Student s) { // TODO Auto-generated constructor stub this.s = s; } @Override public void run() { // TODO Auto-generated method stub while(true){ synchronized (s) { while(s.flag){ try { s.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (x % 2 == 0) { s.name = "xiaojignz"; s.age = 20; } else { s.name = "xiaoxiao"; s.age = 100; } x++; s.flag = true; s.notify(); } } } }
public static void main(String[] args){ Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); }
生产者和消费者改进版
public class Student { private String name; private int age; private boolean flag; public synchronized void set(String name, int age){ while(this.flag){ try{ this.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } this.name = name; this.age = age; this.flag = true; this.notify(); } public synchronized void get(){ while(!this.flag){ try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(this.name + "------" + this.age); this.flag = false; this.notify(); } } public class GetThread implements Runnable { private Student s; public GetThread(Student s) { this.s = s; } @Override public void run() { // TODO Auto-generated method stub while (true) { s.get(); } } } public class SetThread implements Runnable { private Student s; private int x = 0; public SetThread(Student s) { // TODO Auto-generated constructor stub this.s = s; } @Override public void run() { // TODO Auto-generated method stub while (true) { if (x % 2 == 0) { s.set("ha", 30); } else { s.set("wang", 20); } x++; } } } public class StudentDemo { public static void main(String[] args){ Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); } }
线程组:Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类和管理,Java允许程序直接对线程组进行控制,默认情况下,所有的线程都属于主线程组
获取当前线程组:public final ThreadGroup getThreadGroup()
设置线程组:Thread(ThreadGroup group, Runnable target, String name)
public class ThreadGroupDemo { public static void main(String[] args) { MyRunnable my = new MyRunnable(); ThreadGroup tg = new ThreadGroup("新组"); Thread t1 = new Thread(tg, my, "xiaojingzi"); Thread t2 = new Thread(tg, my, "xiaoxiao"); ThreadGroup t3 = t1.getThreadGroup(); ThreadGroup t4 = t2.getThreadGroup(); System.out.print(t3.getName() + "-----" + t4.getName() + "-----"); System.out.println(Thread.currentThread().getThreadGroup().getName()); tg.setDaemon(true); //没有明显的效果 t1.setDaemon(true); t2.setDaemon(true); t1.start(); t2.start(); } }
新组-----新组-----main xiaojingzi----0 xiaoxiao----0 xiaojingzi----1 xiaoxiao----1 xiaojingzi----2 xiaoxiao----2 xiaojingzi----3 xiaoxiao----3 xiaoxiao----4 xiaoxiao----5 xiaoxiao----6
线程池
程序器启动一个新的线程的成本是很高的,因为它设计到要与操作系统 进行交互,而使用线程池可以很高的提高性能,尤其是当程序中要创建大量的生存周期很短的线程时,更应该考虑使用线程池
JDK5新增Executors工厂类来产生线程池:
public static ExecutorService newCachedThreadPool();
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
这些方法的返回值都是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程,它提供了如下的方法:
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
public class ExecutorsPoolDemo { public static void main(String[] args){ ExecutorService pool = Executors.newFixedThreadPool(2); pool.submit(new MyRunnable()); pool.submit(new MyRunnable()); pool.shutdown(); //用完以后等待可以回收继续使用,shutdown结束 } }
2.3 实现Callable接口(带泛型,依赖于线程池存在)
public class MyCallable implements Callable<Integer> { private int number; public MyCallable(int number){ this.number = number; } @Override public Integer call() throws Exception { // TODO Auto-generated method stub int sum = 0; for(int x = 0; x <= number; x++){ sum += x; } return sum; } } public class MyCallabelDemo { public static void main(String[] args) throws InterruptedException, ExecutionException{ ExecutorService pool = Executors.newFixedThreadPool(2); Future<Integer> f1 = pool.submit(new MyCallable(10)); Future<Integer> f2 = pool.submit(new MyCallable(20)); Integer i1 = f1.get(); Integer i2 = f2.get(); System.out.println(i1 + "-----" + i2); pool.shutdown(); } }
匿名内部类方式实现多线程程序
public class ThreadDemo { public static void main(String[] args){ new Thread(){ @Override public void run(){ for(int x = 0; x < 100; x++){ System.out.println(Thread.currentThread().getName() + "----" + x); } } }.start(); new Thread(new Runnable(){ @Override public void run(){ for(int x = 0; x < 100; x++){ System.out.println(Thread.currentThread().getName() + "----" + x); } } }){}.start(); } }
定时器:定时器是一个十分应用广泛的线程工具,可以用于调度多个定时任务以后台线程的方式执行,在java中,可以通过Timer和TimerTask类来实现定义调度的功能
Timer:
public Timer()
public void schedule(TimerTask task, long delay):安排在指定延迟后执行指定的任务
public void shcedule(TimerTask task, Date time):安排在指定的时间执行指定的任务
public void schedule(TimerTask task, long delay, long period):安排在指定的时间后指定的周期执行任务
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period):安排指定的任务在指定的时间开始重复的固定延迟执行
public void cancel():终止此计数器,丢弃当前所有已经安排的任务
TimerTask:
public abstract void run()
public boolean cancel():终止此计时器,丢弃任何当前计划的任务。
public class TimerDemo { public static void main(String[] args){ Timer t = new Timer(); Timer m = new Timer(); // t.schedule(new MyTask(t), 3000); // t.schedule(new MyTask(), 3000, 2000); m.scheduleAtFixedRate(new MyTask(m), new Date(), 3000); } } class MyTask extends TimerTask{ private Timer t; public MyTask() { super(); // TODO Auto-generated constructor stub } public MyTask(Timer t) { super(); this.t = t; } @Override public void run(){ System.out.println("GG"); // t.cancel(); } }
3. 单例模式的饿汉式
public class RuntimeDemo { public static void main(String[] args) throws IOException{ Runtime r = Runtime.getRuntime(); r.exec("calc"); // 执行dos命令 } }