一. 多线程基础
1.1 线程
多线程的意义
- 多线程的存在,不是提高程序的执行速度,而实为了提高应用程序的使用率
- 程序的执行本质上是在抢CPU的执行权,多个进程中如果某个进程的执行路径比较多,那么就会有更高的几率抢到CPU的执行权
- 我们不能保证哪一个线程能在哪个时间点抢到执行权,所以线程的执行具有随机性
Java程序的运行原理
- 由java命令启动JVM,JVM启动就相当于启动了一个进程,接着该进程创建一个主线程去调用main方法,所以main方法运行在主线程中
- java程序的进程里面至少包含两个线程,主线程就是main()方法线程,另一个是垃圾回收机制线程,每当使用java命令执行一个类时,实际上都会启动一个JVM,每一个JVM实际上就是在操纵系统中启动了一个进程,java本身具备了垃圾的手机机制,所以在java运行时至少启动两个线程
线程的两种创建方式
Thread类
创建一个线程匿名内部类实例,并重写其run()方法,此时t只是一个实例,只有执行t.start()方法时才会创建一个新的线程
Thread t = new Thread("Custom-Thread"){ @Override public void run(){ ......... } }.start();
- 创建一个extends Thread的子类实例,并重写其run()方法
Runnable接口
实现Runnable接口,并重写run()方法,将Runnable接口实例作为参数传递给Thread类实例
MyRunnable mr = new MyRunnable(); Thread t = new Thead(mr); t.start();
为什么有了方式1还要有方式2
- 可以避免由于Java单继承单来的局限性
- 适合多个相同程序的代码去处理同一个资源的情况(而不需要使用static来保证每个线程使用同一份数据),将可执行单元和线程控制分离开来,使用策略设计模式的思想
run()方法和start()方法的区别
- run()方法封装了被线程执行的代码,直接调用仅仅是普通方法的调用
- start()封装了run()方法,并且会启动线程
线程的生命周期
- 新建(new):创建线程对象
- 可执行(Runnable):有执行资格,没执行权---start()方法后
运行(Running):有执行权
- 阻塞:由于一些操作让线程处于该状态,没有执行资格,没有执行权---sleep(),wait()
- 唤醒:由于一些操作把线程激活,激活后处于就绪状态---notify()或时间到
- 死亡:线程对象变成垃圾,等待被回收---run()方法结束或interrupt()方法后![1586771889927]
![]()
解决线程安全问题
要想解决问题,就要知道哪些原因会导致这些问题,这些原因也是判断一个程序是否会有线程安全问题的标准
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据(操作是否为原子性操作)
解决问题的思想就是把多条操作共享数据的代码包成一个整体,让某个线程在执行的时候,别人不能来执行
- 同步代码块:
Synchronized(对象){ 需要同步的代码; } 注意:同步可以解决安全问题的根本原因在于那个对象,该对象如同锁的功能,多个线程必须是同一把锁
2. 同步方法 1. 把同步关键字加到方法上 2. 同步方法的锁对象为当前类对象:this ```java private synchronized void sellTicket(){ .... } ``` 3. 静态同步方法 1. 同步关键字加到方法上 2. 静态同步方法的锁对象是类的字节码文件对象 ```java private synchonized void sellTicket(){ .... } ``` 4. Lock锁
1.2 Thread
Thread的构造方法---Thread(ThreadGroup group , Runnable target , String name , long stackSize)
线程名称:name
当构造方法未传入name参数时,默认会使用null,其中nextThreadNum()调用ThreadInitNumber自增,ThreadInitNumber是一个static的自增常量,所以当没有指定线程名称时,其线程名称为Thread-ThreadInitNumber
public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); }
目标函数接口:target
- 当构造函数未传入target参数时,默认会使用null,在其int()方法中会将target存起来,当调用start()方法的时候会调用run()方法,如果target为null时,则不会执行任何内容
所以当没有传target参数时,需要重写run()方法
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { .... this.target = target; .... } public void run() { if (target != null) { target.run(); } }
线程组:ThreadGroup
当构造函数未传入ThreadGroup参数时,就会使用父线程的线程组,在main线程租中有三个线程
- main线程
- monitor线程
- Thread-0线程
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { .... Thread parent = currentThread(); /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } .... }
栈大小:stackSize
影响Java线程数量的因素:
- Java虚拟机本身:-Xms,-Xmx,-Xss;
系统限制:
- /proc/sys/kernel/pid_max : 32080
- /proc/sys/kernel/thread-max
- max_user_process(ulimit -u)
- /proc/sys/vm/max_map_count
- 增大堆内存(-Xms,-Xmx)会减少可创建的线程数量,增大线程栈内存(-Xss,32位系统中此参数值最小为60K)也会减少可创建的线程数量
- 一个进程可以开启的线程受可用内存限制,如果是32位的机器,那么默认一个进程有2G的可用内存,而每个线程默认分析1M的栈空间,所以这种情况下理论最线程数在2000多个。一个解决办法是创建线程时减少线程栈的大小或是使用64位的系统。64位系统应该可以忽略这个问题了。
1.3 Thread的API
1.3.1 Daemon(守护)线程
- 当新开的线程不是Daemon线程时,main线程结束后,Application根据ThreadGroup判断ActiveCount,仍然会继续执行子线程
- 当新开的线程是Daemon线程时,主线程结束时,Daemon线程也会立即结束
守护线程只能在start方法之前执行
public static void main(Stirng[] args){ Thread t = new Thread(){ @Override public void run(){ try{ Thread.sleep(100_000); }catch(InterruptedException e){ e.printStackTrace(); } } } t.setDaemon(true); t.start(); Thread.sleep(5_000); }
1.3.2 Join
join方法在start方法之后:运行完join线程后才执行join()方法接下来的代码
- 以下代码的执行顺序为: t1 -> t2 -> main
public static void main(String[] args){ Thread t1 = new Thread(() - > intStream.range(1,100).forEach(i -> System.out.println(Thread.currentThread().getName() +"---"+ i))) }; Thread t2 = new Thread(() - > intStream.range(1,100).forEach(i -> System.out.println(Thread.currentThread().getName() +"---"+ i))) }; t1.start(); t1.join(); t2.start(); t2.join(); Optional.of("finish...").ifPresent(System:out.println);
- 注:防止主线程停止:Thread.currentThread().join():在主线程上执行"我要等到主线程结束,当前线程才能结束",进入死循环
有一些应用将服务设置为守护进程,防止应用结束后,其服务仍然占用资源
- JettyHttpServer.start()
- activeMqListener
public static void main(String[] args){ Thread t1 = new Thread(); t1.start(); //自我阻塞,永远不会停止 Thread.currentThread().join() }
1.3.3 interrupt
interrupt()方法:中断线程
当中断的线程中不存在阻塞方法,如sleep(),wait(),join()等方法时,线程中isInterrupt()方法会变为true,但是线程不会结束,因为不能通过阻塞方法捕获InterruotedException异常
当使用interrupt()方法打断任务时,其内部把flag标识进行修改,但是线程仍然继续执行,可以通过以下两种方式查看中断标识:
- t.isInterrupted();
- Thread.interrupted();
当中断的线程中存在阻塞方法时,会通过阻塞方法来捕获interruotException异常来结束线程
sleep()方法阻塞
public static void main(String[] args){ Thread t = new Thread(){ @Override public void run(){ while(true){ try{ Thread.sleep(10L); System.out.println("---"+this.isInterrupted()); }catch(InterruptedException e){ System.out.println("收到打断信号" + e); e.printStackTrace(); } } } }; t.start(); Thread.sleep(1000L); System.out.println(t.isInterrupted());//false t.interrupt(); System.out.println(t.isInterrupted());//ture }
wait()方法阻塞
public static void main(String[] args){ Thread t = new Thread(){ @Override public void run(){ while(true){ sychronized (this){ try{ this.wait(10L); System.out.println("---"+this.isInterrupted()); }catch(InterruptedException e){ System.out.println("收到打断信号" + e); e.printStackTrace(); } } } } }; t.start(); Thread.sleep(1000L); System.out.println(t.isInterrupted());//false t.interrupt(); System.out.println(t.isInterrupted());//ture }
join()方法阻塞:join()方法中封装的时wait()---由Object提供,并且join阻塞的为非join线程(main线程)
public static void main(String[] args){ Thread t1 = new Thread(){ @Override public void run(){ while(true){ try{ Thread.sleep(1000L); System.out.println("---"+this.isInterrupted()); }catch(InterruptedException e){ System.out.println("收到打断信号" + e); e.printStackTrace(); } } } }; t1.start(); Thread main = Thread.currentThread(); Thread t2 = new Thread(() - > { @Override public void run(){ try{ Thread.sleep(10000L); }catch(InterruptedException e){ e.printStackTrace(); } main.interrupt(); } }) t2.start(); try{ t1.join(); }catch(InterruptedException e){ e.printStackTrace } }
1.3.4 优雅的方式结束线程
设置判断标识
public static classWoker extends Thread{ private volatile boolean start = true; @Override public void run(){ while(start){ ... } } public void shutdown(){ this.start = false; } } public static void main(String[] args){ Worker worker = new Worker(); worker.start(); try{ Thread.sleep(10000L); }catch(InterruptedException e){ e.printStackTrace(); } worker.shutdown(); }
通过阻塞方法中断
public class Worker extends Thread{ @Overrride public void run(){ while(true){ try{ Thread.sleep(10L); }catch(InterruptedException e){ break;//return } } //............... } } public static void main(String[] args){ Worker worker = new Worker(); worker.start(); try{ Thread.sleep(100L); }catch(InterruptedException){ e.printStackTrace(): } worker.interrupt(); }
通过Thread中断标识判断
public class Worker extends Thread{ @Overrride public void run(){ while(true){ if(Thread.interrupted()){ break; } } //............... } } public static void main(String[] args){ Worker worker = new Worker(); worker.start(); try{ Thread.sleep(100L); }catch(InterruptedException){ e.printStackTrace(): } worker.interrupt(); }
1.3.5 强制性中断线程
当子线程不是执行循环逻辑时,并无校验中断标识和执行阻塞方法的逻辑,并且方法的执行时间很长,需要在方法的执行其进行中断开-----替代stop()方法
public static void main(String[] args){ ThreadService ts = new ThreadService(); long start = System.currentTimeMillis(); service.execute(() - > { //load a very heavy resource // while(true){ //... //} try{ Threads.sleep(5000L); }catch(InterruptedException e){ e.printStackTrace(); } }); service.shutDown(10000L); System.out.println(System.currentTimeMillis() - start); } ------------------------------------------------------------------ public class ThreadService{ private Thread executeThread; private boolean finished = false; public void execute(Runnable task){ executeThread = new Thread(){ @Override public void run(){ Thread t = new Thread(task); t.setDaemon(true); t.start(); try{ t.join(); finished = true; }catch(InterruptedException e){ } } }; executeThread.start(); } public void shutDown(long miilis){ long startTime = System.currentTimeMillis(); while(!finished){ if(System.currentTimeMillis() - startTime >= millis){ System.out.println("超时中断..."); executeThread.interrupt(); break; } try{ executeThread.sleep(1000L); }catch(InterruptedException){ System.out.println("执行线程被打断..."); break; } } } }
1.4 同步和锁的问题
1.4.1 Synchronize串行化
同步方法:this锁
public static void main(String[] args){ ThisLock lock = new ThisLock(); new Thread(){ @Override public void run(){ lock.method1(); } }.start(); new Thread(){ @Override public void run(){ lock.method2(); } }.start(); } ------------------------------------------------------------------ class ThisLock{ public synchronized void method1(){ try{ System.out.println(Thread.currentThread().getName()); Thread.sleep(5_000L); }catch(InterruptedException e){ e.printStackTrace(); } } public void method2(){ synchronized(this){ try{ System.out.println(Thread.currentThread().getName()); Thread.sleep(5_000L); }catch(InterruptedException e){ e.printStackTrace(); } } } }
静态同步方法:Class锁
class StaticLock{ public static synchronized void method1(){ try{ System.out.println(Thread.currentThread().getName()); Thread.sleep(5_000L); }catch(InterruptedException e){ e.printStackTrace(); } } public static void method2(){ synchronized(StaticLock.class){ try{ System.out.println(Thread.currentThread().getName()); Thread.sleep(5_000L); }catch(InterruptedException e){ e.printStackTrace(); } } } }
死锁:通过jps和jstack查看日志
class DeadLock{ private OtherService otherService; private final Object lock = new Object; private DeadLock(OtherService otherService){ this.otherService = otherService; } public void m1(){ synchronized(lock){ otherService.s1(); } } public void m2(){ synchronized(lock){ otherService.s2(); } } } class OtherService{ private DeadLock deadLock; private final Object lock = new Object; private OtherService(DeadLock deadLock){ this.deadLock = deadLock; } public void s1(){ synchronized(lock){ deadLock.m1(); } } public void s2(){ synchronized(lock){ deadLock.m2(); } } } ------------------------------------------------------------------ public static void main(String[] args){ OtherService otherService = new OtherService(); DeadLock deadLock = new DeadLock(otherService); otherService.setDeadLock(deadLock); new Thread(){ @Override public void run(){ while(true){ deadLock.m1(); } } }.start(); new Thread(){ @Override public void run(){ while(true){ otherService.s2(); } } }.start(); }
1.4.2 线程间的通讯
通过wait()和notify(),notifyAll()方法进行线程间的通信
- wait():线程进入阻塞,调用的同时会释放锁
notify():随机唤醒一条阻塞的线程,线程执行逻辑从wait()处开始,使线程处于runnable状态,仍需要抢执行执行权
- 当有多消费者和生产者时,会产生假死的问题,原因是由于同类线程之间的唤醒,使得所有线程进入wait状态
notifyAll():唤醒所有阻塞的线程
public static void main(String[] args){ ProducerConsumerService service = new ProducerConsumerService(); Stream.of("P1","P2","P3").forEach(n -> { new Thread(n){ @Override public void run(){ service.produce(); try{ Thread.sleep(10L); }catch(InterruptedException e){ e.printStackTrace(); } } }.start(); }) Stream.of("C1","C2","C3").forEach(n -> { new Thread(n){ @Override public void run(){ service.consume(); try{ Thread.sleep(10L); }catch(InterruptedException e){ e.printStackTrace(); } } }.start(); }) } ------------------------------------------------------------------ class ProducerConsumerService{ private final Object LOCK = new Object(); private Boolean isProduced = false; private Integer i = 0; public void produce(){ synchronized(LOCK){ while(isProduced){ try{ LOCK.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } i++; System.out.println("P -> "+i); LOCK.notifyAll(); isProduced = true; } } public void consume(){ synchronized(LOCK){ while(!isProduced){ try{ LOCK.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } System.out.println("C -> "+i); LOCK.notifyAll(); isProduced = false; } } }
wati()和sleep()的区别
- sleep()方法是属于Thread的方法,而wait()是属于Object的方法
sleep()方法不会释放锁(Object monitor),而wait()方法会释放锁
public static void main(String[] args){ Stream.of("t1","t2").forEach(n -> { new Thread(n){ @Override public void run(){ m1();//线程之间间隔20s m2();//几乎同时 } }.start(); }) ------------------------------------------------------------------ public static void m1(){ synchronized(LOCK){ try{ System.out.println(Thread.currentThread().getName()); Thread.sleep(20_000L); }catch(InterruptedException e){ e.printStackTrace(); } } } ------------------------------------------------------------------ public static void m2(){ synchronized(LOCK){ try{ System.out.println(Thread.currentThread().getName()); LOCK.wait(20_000L); }catch(InterruptedException e){ e.printStackTrace(); } } } }
- sleep()方法不需要依赖Synchronize,而wait()方法必须要和Synchronize关键字一起使用
- sleep()方法不需要被唤醒,而wait()方法必须要被唤醒(除wait(int times)外)
1.4.3 案例:控制最大效率线程切换数
public class CpatureService{ private final static LinkedList<Object> list = new LinkedList<>(); private final static int MAX_Thread = 5; public static void main(String[] args){ List<Thread> threads = new ArrayList<>(); Arrays.asList("M1","M2","M3","M4","M5","M6","M7","M8","M9").stream() .map(CaptureService::createCaptureThread) .forEach(t -> { t.start(); threads.add(t); } }); threads.stream().forEach(t -> { try{ t.join(); }catch(InterruptedException e){ e.printStackTrace(); } }); Optional.of("All of Threads Have Finished...").ifPresent(System.out::println); } ------------------------------------------------------------------ public static Thread createCaptureThread(String name){ return new Thread({ Optional.of(Thread.currentThread().getName() + "begin") .ifPresent(System.out::println); synchronized(list){ while(list.size > MAX_THREAD){ try{ list.wait() }catch(InterruptedException e){ e.printStackTrace(); } } list.addLast(new Object()); } Optional.of(Thread.currentThread().getName() + "working") .ifPresent(System.out::println); }); try{ Thread.sleep(10_000L); }catch(InterruptedException e){ e.printStackTrace(); } synchronized(list){ Optional.of(Thread.currentThread().getName() + "working") .ifPresent(System.out::println); }); list.removeFirst(); list.notifyAll(); } } }
1.4.4 自定义Lock锁
当synchronized同步时,执行过程很长,调用者进入阻塞状态,也不能进行打断和立即返回,即使打断也不会释放锁,从而抢不到锁,仍然处于阻塞状态
Lock和synchronized区别
- Lock是一个接口,而synchronized是关键字
- synchronized会自动释放锁,而lock必须手动释放锁
- Lock可以让等待锁的线程响应中断并且释放锁,而synchronized不会,线程会一直等待下去(Lock的定时功能)
- 通过Lock可以知道线程有没有拿到锁,而synchronized不能
- synchronized能所著类,方法和代码块,而Lock是块方位内的
- Lock能提高多个线程读操作的效率
自定义锁
public static void main(String[] args){ BooleanLock lock = new BooleanLock(); Stream.of("T1","T2","T3","T4").forEach(t -> { new Thread(t){ @Override public void run(){ try{ lock.lock(); Optional.of(Thread.currentThread().getName() + "get lock") .ifPresent(System.out::println); work(); }catch(InterruptedException e){ e.printStackTrace(); }finllly{ lock.unlock(); } } }.start(); }) } private static void work(){ Optional.of(Thread.currentThread().getName() + "is working") .ifPresent(System.out::println); Thread.sleep(10_000L); } ------------------------------------------------------------------ public class BooleanLock implement Lock{ private boolean initValue; private Collection<Thread> collection = new ArrayList<>(); private Thread currentThread; public BooleanLock(){ this.initValue = false; } //非定时锁... @Override public synchronized void lock() throws interruptedException{ while(initValue){ collection.add(Thread.currentThread()); this.wait(); } collection.remove(Thread.currentThread()); this.initValue = ture; this.currentThread = Thread.currentThread(); } //定时锁... @Override public synchronized void lock(Long millis) throws interruptedException{ if(millis <= 0){ lock(); return; } long hasRemain = millis; long endTime = System.currentTimeMillis() + millis; while(initValue){ if(hasRemain < 0){ throw new TimeOutException("Time out"); } collection.add(Thread.currentThread()); this.wait(millis); hasRemain = System.currentTimeMillis() - endTime; } collection.remove(Thread.currentThread()); this.initValue = true; this.currentThread = Thread.currentThread(); } @Override public synchronized void unlock(){ if(this.currentThread() == Thread.currentThread()){ this.initValue = false; Optional.of(Thread.currentThread().getName() + "release the lock") .ifPresent(System.out::println); this.notifyAll(); } } @Override public Collection<Thread> getBlockThread(){ return this.collection; } }
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。