Java 多线程
一. 基本名词概念
1. 进程:一个运行的程序
2. 线程:是进程中的单个顺序控制流,是一条执行路径
(1)单线程:一个进程只有一条执行路径(记事本中的点开设置)
(2)多线程:一个进程有多条执行路径(扫雷中的计时器和游戏)
二. Java实现多线程
1. 继承Thread类的方式实现多线程
(1)定义类MyThread继承Thread类
(2)在MyThread中重写run()方法
(3)创建MyThread对象,启动线程
2. 设置和获取线程名称
(1)如果不设置线程名称默认名为"Thread-0, Thread-1..."
(2)setName("xx")设置名称,getName()获取名称
(3)设置名称也可以重写构造函数,调用父类Thread的带参构造函数,传入name
(4)获取当前正在执行的线程对象的引用:静态方法Thread.currentThread()
public class MyThread extends Thread{ public MyThread() { super(); } public MyThread(String name) { super(name); } @Override public void run() {//快捷键Ctrl+O for(int i=0;i<1000;i++){ System.out.println(getName()+":"+i); } } }
public class MyThreadDemo { public static void main(String[] args) { MyThread my1=new MyThread("hh1"); MyThread my2=new MyThread("hh2"); //run()封装线程执行的代码 //my1.setName("Hello1"); //my2.setName("Hello2"); my1.start();//启动线程,由JVM调用run()方法 my2.start(); System.out.println(Thread.currentThread().getName()); } }
3. 线程调度
线程调度有两种模型:
• 分时调度模型:所有线程轮流使用CPU,平均分配每个线程占用CPU的时间片
• 抢占式调度模型:优先让优先级高的线程使用CPU,如果优先级相同则随机选择一个
Java使用的是抢占式调度模型
Thread类中提供的方法:
public final void setPriority(int newPriority)//更改此线程的优先级 public final int getPriority()//返回此线程的优先级。
MIN_PRIORITY=1 MAX_PRIORITY=10 NORM_PRIORITY=5//默认
4. 线程控制
public static void sleep(long millis)//当前正在执行的线程休眠(暂停执行)为指定的毫秒数 public final void join()//等待这个线程死亡,等价于join(0) public final void setDaemon(boolean on)//标志着该线程是守护线程,如果运行的线程都只剩下守护线程是,Java虚拟机退出
//Java中除了用户进程就是守护进程,守护进程是后台服务进程,比如垃圾回收进程
5. 线程生命周期
6. 实现Runnable接口的方式实现多线程
(1)定义一个类MyRunnable实现Runnable接口
(2)在Runnable类中重写run()方法
(3)创建MyRunnable对象
(4)创建Thread类的对象,把MyRunnable对象作为构造方法的参数
(5)启动线程
好处:
• 避免了Java单继承的局限性,MyRunnable可以再继承其他父类
• 适合多个相同的程序的代码去处理同一个资源的情况,较好的体现面向对象思想
public class MyRunnable implements Runnable { @Override public void run() { for(int i=0;i<1000;i++){ System.out.println(Thread.currentThread().getName()+":"+i);//不能直接getName() } } }
public class MyRunnableDemo { public static void main(String[] args) { MyRunnable my=new MyRunnable(); Thread t1=new Thread(my); Thread t2=new Thread(my,"hh");//带设置名的构造函数 t1.start(); t2.start(); } }
三. 线程同步
1. 卖票案例
电影院出售1000张票,三个窗口同时卖票,每次出票时间10ms
非同步出现的问题:
• 相同的票出现了多次(三个线程都打印"第100张票正在出售")
• 出现了负数的票
问题原因:
• 线程执行的随机性导致的
2. 安全问题的解决
解决方式:把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
(1)同步代码块:
synchronized (任意对象){ 多条语句操作共享数据的代码 }
synchronized相当于给代码加锁,任意对象就可以看成一把锁
同步的好处和弊端:
• 好处:解决了多线程的数据安全问题
• 弊端:线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中降低了程序运行效率
public class SellTicket implements Runnable{ private int ticket=1000; private Object obj=new Object();//三个线程要用同一把锁,所以放一个obj变量在这 @Override public void run() { while(true){ synchronized (obj){//同步代码块 //t1进来后,就会把这段代码锁起来 if(ticket>0){ try { Thread.sleep(10);//快捷键Ctrl+Alt+T //即使t1在休息,t2抢到CPU执行权,也无法执行这段代码 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票"); ticket--; } } } } }
public class SellTicketDemo { public static void main(String[] args) { SellTicket st=new SellTicket(); Thread t1=new Thread(st,"窗口1"); Thread t2=new Thread(st,"窗口2"); Thread t3=new Thread(st,"窗口3"); t1.start(); t2.start(); t3.start(); } }
(2)同步方法:
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(参数){}
同步方法的锁对象:this
同步静态方法:就是把synchronized关键字加到静态方法上
修饰符 static synchronized 返回值类型 方法名(参数){}
同步静态方法的锁对象:类名.class
public class SellTicket implements Runnable{ // private int ticket=1000; private static int ticket=1000; private Object obj=new Object();//三个线程要用同一把锁,所以放一个obj变量在这 private int x=0; @Override public void run() { while(true){ if(x%2==0){ // synchronized (obj){ // synchronized (this){//同步方法的锁对象:this synchronized (SellTicket.class){//同步静态方法的锁对象:该类的字节码文件对象 if(ticket>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票"); ticket--; } } }else{ sellTicket(); } x++; } } private static synchronized void sellTicket() { if(ticket>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票"); ticket--; } } }
3. 几个线程安全的类
(1)StringBuffer
• 线程安全,可变的字符序列,源码中的方法都加了synchronized
• 如果不需要同步,应使用StringBuilder替代
(2)Vector
• 改进了List接口,使其成为Java Collections Framework的成员
• 如果不需要线程安全,应使用ArrayList替代
(3)Hashtable
• 实现了一个哈希表,任何非null值可作为键或值
• 如果不需要同步,建议使用Hashmap替代(Hashmap允许键或值均可为null,而Hashtable均不可)
(4)工具类转为线程安全
举例:
List<String> synList = Collections.synchronizedList(new ArrayList<>());
4. Lock锁
为了体现更为清晰而广泛的加锁释放锁过程,JDK5之后提供了一个新的锁对象Lock
void lock() //获得锁 void unlock() //释放锁
Lock是接口不能直接实例化,可以采用它的实现类ReentrantLock来实例化
public class SellTicket implements Runnable{ private int ticket=1000; private Lock lock =new ReentrantLock(); @Override public void run() { while (true){ try {//常用try-finally块,即使代码出问题也会释放锁 lock.lock(); if(ticket>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票"); ticket--; } } finally { lock.unlock(); } } } }
5. volatile关键字
(1)volatile是一种轻量级的同步机制,相比于synchronized和Lock(synchronized通常称为重量级锁),volatile更轻量级;
(2)volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性,所以不能用于非原子操作(赋值是原子操作);
(3)volatile用于修饰类变量或成员变量,被修饰的变量在缓存中的修改会被立即写入主存(一般变量只是先写入线程私有的本地内存);
(4)volatile在操作系统底层是内存屏障的原语,用于解决可见性和有序性问题;
(5)应用场景:单例模式
public class TestInstance{ private volatile static TestInstance instance; public static TestInstance getInstance(){ //1 if(instance == null){ //2 synchronized(TestInstance.class){ //3 if(instance == null){ //4 instance = new TestInstance(); //5 } } } return instance; //6 } }
如果不用volatile,第五行代码在多线程情况下会出现问题,因为instance = new TestInstance(); 事实上有三个执行步骤
a. memory = allocate() //分配内存 b. ctorInstanc(memory) //初始化对象 c. instance = memory //设置instance指向刚分配的地址
由于可能存在指令重排,如果一个进程1先执行了ac,另一个线程b判断instance == null就会认为是false,产生错误。
三. 生产者消费者模型
Java在Object类提供了一些方法,体现生产和消费过程中的等待和唤醒:
void wait() //使当前线程等待,直到另一个线程调用此对象的notify()或notifyAll()方法 void notify() //唤醒一个在这个对象的监视器上等待的单个线程 void notifyAll() //唤醒正在等待此对象监视器上的所有线程
• 这些方法需要在同步的条件下,一般配合synchronized使用
• 如果等待的线程有多个,notify方法只会唤醒其中一个,唤醒哪一个取决于操作系统对于多线程的管理
• notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况
生产者消费者代码示例:
public class Box { //成员变量,表示第x件商品 private int good; //表示箱内是否有商品 private boolean state=false; public synchronized void put(int good) {//注意要加synchronized关键字,否则wait()抛异常 if(state){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.good = good; System.out.println("生产者将第"+this.good+"件商品放入箱中"); state=true; notifyAll(); } public synchronized void get() { if(!state){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("消费者拿到第"+good+"件商品"); state=false; notifyAll(); } }
public class Producer implements Runnable{ private Box b; public Producer(Box b){ this.b=b; } @Override public void run() { for(int i=1;i<=5;i++){ b.put(i); } } }
public class Customer implements Runnable{ private Box b; public Customer(Box b){ this.b=b; } @Override public void run() { while(true){ b.get(); } } }
public class BoxDemo { public static void main(String[] args) { Box b=new Box();//存储箱类 Producer p=new Producer(b);//生产者对象 Customer c=new Customer(b);//消费者对象 Thread t1=new Thread(p);//生产者线程 Thread t2=new Thread(c);//消费者线程 t1.start();//启动线程 t2.start(); } }
输出: