多线程
java.exe至少有三个线程:一个main()主线程,一个GC线程(守护线程),一个异常处理线程
一、创建多线程的三种方式
1、通过继承Thread类(Thread实现了Runnable接口),重写run()方法;
2、通过实现Runnable接口的run()方法;
3、通过实现Callable接口的call()方法,支持泛型的返回值和抛出异常,要借助FutureTask来封装,既可以封装Callable,也可以封装Runnable;
FutureTask实现了Runnable接口,支持异步获取线程执行结果,取消执行任务,异步获取执行结果会阻塞主线程;
可以将FutureTask放入线程池中
线程池:通过线程池来创建的优点:先创建好线程再放入线程池,可以避免频繁的创建、销毁线程,提高响应速度,减小资源消耗,便于线程管理;
主要分为任务部分和线程部分,通过一个生产者消费者模型来将二者解耦
任务调度过程分三步:第一步查看是否还有空闲的核心线程;第二步查看缓存队列是否满了;第三步查看最大线程的数量
线程调度过程:抢占式调度(由系统来按优先级来决定下一个线程执行) 和 协同式调度(由线程来通知下一个线程执行)
execute():没有返回结果,只能接收Runnable类型的任务
submit():返回一个Future类型的对象,用来异步获取Callable任务的返回结果,可以接收Runnable和Callable任务
shutdown()和shutdownNow()的区别
前面三个都是通过Thread的start()方法来运行:start()启动线程,调用run()方法
继承Thread类创建多个线程要用多个对象,一个对象不能多次调用start()
public class MyTreadTest { public static void main(String[] args){ MyThread mt = new MyThread("新线程1--"); //mt.run(); mt.start(); //开启多线程,开辟一个新的栈执行run() // MyThread2 mt2 = new MyThread2(); new Thread(new MyThread2(), "新线程2--").start();; for(int i=0; i<3; i++) System.out.println(Thread.currentThread().getName() +" "+ i); //匿名内部类实现多线程 new Thread(){ @Override public void run(){ System.out.println("匿名内部类1"); } }.start(); new Thread(new Runnable(){ @Override public void run(){ System.out.println("匿名内部类2"); } }).start();; } } // 创建线程的第一种方式,继承Thread类 class MyThread extends Thread{ public MyThread(){} public MyThread(String name){ super(name); } @Override public void run(){ for(int i=0; i<3; i++) System.out.println(getName() + i); } } // 创建线程的第二种方式,实现Runable接口 // 避免了单继承的局限性;增强了程序的扩展性 class MyThread2 implements Runnable{ @Override public void run(){ for(int i=0; i<3; i++) System.out.println(Thread.currentThread().getName() +" "+ i); } }
/** * 1、Callable支持泛型 * 2、call()可以抛出异常,后面捕获 * 3、call()有返回值 * */ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; class CallableThread implements Callable<Integer>{ // 支持泛型 @Override public Integer call() throws Exception{ int s = 0; for(int i=0; i<=100; i++){ Thread.sleep(10); s += i; } return s; } } public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { CallableThread ct = new CallableThread(); FutureTask<Integer> ft = new FutureTask<Integer>(ct); new Thread(ft).start(); new Thread(ft).start(); // FutureTask只会执行一次call() if(!ft.isDone()){ System.out.println("主线程执行其他任务"); } System.out.println(ft.get()); // 获得call()的返回值,会阻塞主线程 System.out.println("12345"); } }
import java.util.concurrent.*; class NumberRun implements Runnable{ private int s = 0; @Override // 不能抛出异常,没有返回结果 public void run() { for(int i=0; i<100; i++) { s += i; } System.out.println(Thread.currentThread().getName()+": "+s); } } class NumberCall implements Callable<Integer>{ private int s = 0; @Override // 可以抛出异常,有返回结果 public Integer call() throws Exception { for(int i=0; i<100; i++){ s += i; } System.out.println(Thread.currentThread().getName()+": "+s); return s; } } public class ThreadPool { public static void main(String[] args) throws ExecutionException, InterruptedException { // 线程池一:Executors.newCachedThreadPool(); // 核心线程数为0,最大线程数为Integer.MAX_VALUE,空闲线程60s后被回收,允许创建大量线程,不添加任务 // 手动设置线程池参数 new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); // 线程池二:Executors.newFixedThreadPool(5); // 核心线程数和最大线程数一致,允许添加大量任务 new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); // 线程池三:Executors.newSingleThreadExecutor(); // 核心线程数和最大线程数都为1,将等待的任务放入队列,队列的最大长度为Integer.MAX_VALUE,允许添加大量任务 new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); // 线程池四:Executors.newScheduledThreadPool(); // 也是通过ThreadPoolExecutor()实现的,可以传入一个ThreadFactory对象 // 创建有10个线程的线程池 ExecutorService serviceInterface = Executors.newFixedThreadPool(10); // ThreadPoolExecutor可以设置线程池属性 ThreadPoolExecutor service = (ThreadPoolExecutor)serviceInterface; // 使用Runnable提交任务 service.execute(new NumberRun()); System.out.println("(Runnable无返回值): " + service.submit(new NumberRun()).get()); // service.execute(new NumberCall()); // 不能提交Callable任务 service.execute(new FutureTask<>(new NumberRun(), null)); // 使用Callable提交任务 service.submit(new NumberCall()); // FutureTask即可传入Callable,也可传入Runnable service.submit(new FutureTask<Integer>(new NumberCall())); // execute不能提交Callable任务,但可执行经过FutureTask封装的Callable任务,因为FutureTask实现了Runnable接口 service.execute(new FutureTask<Integer>(new NumberCall())); // 使用Callable提交任务,并获取返回值 System.out.println("获取返回值: " + service.submit(new NumberCall()).get()); // 不再接收新任务,等线程池中的任务执行完之后关闭线程池, service.shutdown(); // 不再接收新任务,不等待,直接尝试关闭线程池 // service.shutdownNow(); } }
线程的方法:
1、Thread.currentThread():获取当前线程
2、Thread.yield():释放当前CPU的执行权,线程进入就绪状态(下一次仍然有可能抢到执行权),不会抛出异常
3、Thread.sleep():阻塞当前线程,线程进入阻塞状态,抛出异常
4、join():线程a里面调用线程b的join(),则线程a进入阻塞状态,直到线程b执行完
5、线程的优先级:getPriority()、setPriority(int p)、MIN_PRIORITY=1、MAX_PRIORITY=10、NORM_PRIORITY=5(默认)
二、线程安全
1、同步代码块:通过synchronized来实现,注意Thread和Runnable的同步监视器区别
public class ThreadSecurityDemo { public static void main(String[] args){ ThreadSecutiry ts = new ThreadSecutiry(); new Thread(ts).start(); new Thread(ts).start(); new Thread(ts).start(); } } class ThreadSecutiry implements Runnable{ private int ticket = 100; Object obj = new Object(); // 所有对象只能拥有一把锁 @Override public void run(){ while(true){ synchronized(obj){ // 传入一个锁对象,可以是任意类型,this也可以 if(ticket>0){ // try{ // Thread.sleep(10); // } // catch(InterruptedException e){ // e.printStackTrace(); // } System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票"); ticket--; } } } } }
class TicketThread extends Thread { private static int ticket = 100; private static Object obj = new Object(); // 保证锁唯一 @Override public void run(){ while(true){ synchronized(TicketThread.class){ if(ticket>0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" 正在卖第 " +(101-ticket)+" 张票"); ticket--; }else break; } } } } public class TicketThreadTest{ public static void main(String[] args) { TicketThread tt1 = new TicketThread(); TicketThread tt2 = new TicketThread(); tt1.start(); tt2.start(); } }
2、同步方法(写一个synchronized修饰的方法)
public class ThreadSecurityDemo { public static void main(String[] args){ ThreadSecutiry ts = new ThreadSecutiry(); System.out.println(ts); new Thread(ts).start(); new Thread(ts).start(); new Thread(ts).start(); } } class ThreadSecutiry implements Runnable{ private int ticket = 100; @Override public void run(){ System.out.println("this: "+this); while(true){ sellTicket(); } } // 锁对象是this public synchronized void sellTicket(){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票"); ticket--; } } }
public class ThreadSecurityDemo { public static void main(String[] args){ ThreadSecutiry ts = new ThreadSecutiry(); new Thread(ts).start(); new Thread(ts).start(); new Thread(ts).start(); } } class ThreadSecutiry implements Runnable{ private static int ticket = 100; @Override public void run(){ while(true){ sellTicket(); } } // 锁对象是class文件对象 public static synchronized void sellTicket(){ if(ticket>0){ System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票"); ticket--; } } }
3、锁机制:
java.util.cocurrrent.locks提供了Lock和ReadWriteLock接口,ReentrantLock实现了Lock接口,ReentrantReadWriteLock实现了ReadWriteLock接口,都是通过内部类Sync继承AQS实现;
等待可中断:没有获取到锁的线程不必一直等待获取锁,可以先执行其他任务;
可实现公平锁:线程按申请锁的顺序来获取锁,直接进入队列排队等待;非公平锁:先尝试获取锁,再进入队列排队等待,synchronized是非公平锁;
可绑定多个条件?:
Condition condition = lock.newCondition();
可以使用condition.await()和condition.signal()/signalAll()来阻塞或唤醒条件上线程,而不是像Object的notify()一样随机唤醒
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ThreadSecurityDemo { public static void main(String[] args){ ThreadSecutiry ts = new ThreadSecutiry(); new Thread(ts).start(); new Thread(ts).start(); new Thread(ts).start(); } } class ThreadSecutiry implements Runnable{ private int ticket = 100; Lock lock = new ReentrantLock(); // 所有线程只能有一把锁,如果有多个对象,这里就要设为static @Override public void run(){ while(true){ lock.lock(); if(ticket>0){ try{ Thread.sleep(10); System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票"); ticket--; } catch(InterruptedException e){ e.printStackTrace(); } finally{ lock.unlock(); } } } } }
4、ThreadLocal:
当多个线程使用一个共享变量时,ThreadLocal为每个使用这个变量的线程维护一个独立的变量副本,ThrealLocal依靠ThreadLocalMap来存储数据,key是ThreadLocal,value是存储的值,每个线程只有一个ThreadLocalMap
import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.*; public class ThreadLocalTest { private static int a = 500; public static void main(String[] args) { ThreadLocal threadLocal = new InheritableThreadLocal(); // 用于信息共享 threadLocal.set("老王"); ThreadLocal threadLocal2 = new ThreadLocal(); threadLocal2.set("老王"); System.out.println(threadLocal.get().equals(threadLocal2.get())); // true new Thread(() -> { System.out.println(threadLocal.get()); // "老王" System.out.println(threadLocal2.get()); // null System.out.println(threadLocal.get().equals(threadLocal2.get())); // false }).start(); new Thread(()->{ ThreadLocal<Integer> local = new ThreadLocal<Integer>(); while(true){ local.set(++a); //子线程对a的操作不会影响主线程中的a, 为什么这里是从22开始? try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程:"+local.get()); } }).start(); a = 22; ThreadLocal<Integer> local = new ThreadLocal<Integer>(); local.set(a); while(true){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("主线程:"+local.get()); } } }
只能用来修饰多个线程共享的字段,无法修饰方法或者代码块
volatile可以实现可见性、有序性,不能实现原子性
ReentrantLock的原理:
ReentrantLock位于java.util.concurrent包中,可实现公平锁和非公平锁,默认是非公平锁;
内部有个抽象的静态内部类:Sync,Sync继承了AbstractQueuedSynchronizer(AQS)
lock()和tryLock()的区别:lock()在获取不到锁的时候会一直等待,而tryLock()在获取不到锁的时候不会等待
synchronized和Lock的区别?
1、synchronized属于JVM层面,Lock属于api层面(具体的类来实现)
2、synchronized在代码执行完之后会自动的释放锁;Lock要手动的加锁和释放锁
3、Lock等待可中断、可实现公平锁、可绑定多个条件
并发编程中的原子性、可见性、有序性:
原子性:要么执行完,要么不执行
可见性:一个线程修改变量之后,其他线程可立刻知道变量被修改了
有序性:执行的顺序按照代码的先后顺序执行(会存在指令重排)
线程的六种状态(生命周期):
1、新建(NEW)
2、可运行(RUNNABLE):就绪(调用start()方法,等待CPU使用权) 和 运行中(获得CPU的使用权) yield()
3、锁阻塞(BLOCKED):等待同步锁
4、无限等待(WAITING):wait(),join(),LockSupport.park(),等待其他线程的唤醒
5、计时等待(TIMED_WAITING):sleep(long time),wait(long time),join(long time)
6、被终止(TERMINATED)
public class WaitAndNotify { public static void main(String[] args){ Object obj = new Object(); // 消费者 new Thread(){ @Override public void run(){ synchronized(obj){ try{ System.out.println(Thread.currentThread().getName()+"进入waitting状态,释放锁对象"); obj.wait(); //进入无限等待状态 } catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"被唤醒"); } } }.start(); // 生产者 new Thread(){ @Override public void run(){ try{ Thread.sleep(5000); } catch(InterruptedException e){ e.printStackTrace(); } synchronized(obj){ System.out.println(Thread.currentThread().getName()+"获取到锁对象,调用notify方法,释放锁对象"); obj.notify(); // 唤醒等待的线程 } } }.start(); } }
线程通性wait()/notify()
wait():使线程进入阻塞状态,并且释放锁,sleep不会释放锁; wait()和sleep的区别? 一个来自Object和一个来自Thread
notify():随机唤醒一个阻塞的线程,被唤醒的线程不会立即拿到锁,而是去竞争锁
notifyAll():唤醒全部阻塞线程
只能在同步代码块或同步方法中配套使用,并且调用者要和同步代码块或同步方法的同步监视器一致,否则导致 "IllegalMonitorStateException" 异常
class Number implements Runnable{ private int i = 100; @Override public void run(){ while(true){ synchronized (this) { // 实现交互打印,线程通信? // notify(); this.notifyAll(); // synchronized是this,这里也要是this,wait也要是this if (i > 0) { try { Thread.sleep(10); // 不会释放锁 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ": " + i--); try { this.wait(); // 会释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } } } public class CommunicationTest { public static void main(String[] args) { Number number = new Number(); new Thread(number,"n1").start(); new Thread(number,"n2").start(); new Thread(number,"n3").start(); } }
线程中断:
interrupt():不会中断运行中的线程(除非线程循环检测中断标志位),但是会改变中断标志位为false,有调用(sleep、wait、join等)时会抛出InterruptedException
Thread.interrupted():判断线程是否被中断,并改变中断标志位为false,后面再调用时返回false;
isInterrupted():只是判断线程是否已经中断;
并不是所有的阻塞都能中断,synchronized、I/O操作等不能被中断
try{ while(!Thread.interrupted()){ // 非阻塞过程中断 // do something } }catch (InterruptedException e){ // 阻塞过程中断 }
public class InterruptTest2 { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(new MyThread()); thread.start(); Thread.sleep(1000); // 注意:要循环检测中断状态才能中断线程;interrupt()只是发出中断请求,如果不检测不能退出 thread.interrupt(); // main线程阻塞,阻塞时中断线程会抛出异常 thread.join(); System.out.println("end"); } } class MyThread implements Runnable{ @Override public void run(){ Thread hello = new Thread(new HelloThread()); hello.start(); try { hello.join(); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("线程响应中断异常,立即中断"); } // 中断hello线程,不中断仍然会运行 hello.interrupt(); } } class HelloThread implements Runnable{ @Override public void run(){ int n = 0; // 注意 Thread.interrupted() 和 Thread.currentThread().isInterrupted()的区别 // Thread.interrupted() 第一次返回true并清除中断标志位,后面调用都是返回false // Thread.currentThread().isInterrupted()只是查询中断标志位判断是否中断 while(!Thread.interrupted()){ n++; System.out.println("*****hello world" + n + "*****"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); break; } } } }
设置中断标志中断线程:
public class InterruptTest3 { public static void main(String[] args) throws InterruptedException { HelloTest thread = new HelloTest(); thread.start(); Thread.sleep(1); thread.flag = false; } } class HelloTest extends Thread { public volatile boolean flag = true; @Override public void run(){ int i = 0; while(flag){ System.out.println("通过设置标志来中断当前线程" + i++); } System.out.println("end"); } }
java的死锁:不同的线程分别占用对方的资源,都在等待对方释放资源
死锁检测:
1、jps确定java进程id
2、jstack -pid 可以检查死锁 (此处还没成功实验)