多线程
一多线程
一.概念的相关问题
1.理解概念 线程和进程的区别?
所有的操作系统都支持进程的概念,所有的程序都会对应一个进程每个运行的程序相当于一个进程,每一个进程一定是独立存在的。
1.进程具有以下特点:
a.独立性:进程是操作系统独立存在的实体,它拥有自己独立的资源 每一个进程都拥有自己的私立地址空间,如果没有本进程的允许求他的进程是不能访问其他私立的进程。
b.动态性:进程与程序的区别 在于动态性 程序是一个静态指令的集合,进程是一个正在程序汇总运行是指令的集合 进程有自己的生命周期
c.并发性: 多个进程可以在单个处理器上并发执行 多个进程不会相互影响。
注意:并发性和并行性 两个概念
并行性:指在同一个时刻内 有多个指令在多个处理器上同时执行
并发性:指在同一个时刻内只能有一个指令执行 但是多个进程指令快速切换执行
2.线程
一个进程中包含了多个线程
多线程的优势:
a.进程之间不能共享资源,但是线程之间可以共享内存
b.系统创建进程需要为每个进程分配系统资源,但是创建线程代价很小
c.java语言内置多线程的支持 不是单纯的操作系统的调度 而是简化了java多线程的编程
二.线程的创建
线程有两种创建方式
第一种: 继承Thread类 重写run方法
第二种: 实现Runnable接口 重写run方法
面试题: 线程有几种创建方式? 这两种创建方式分别有什么优势你比较倾向于哪个创建方式
采用实现Runnable接口的方式
***>线程类只实现了Runnable接口 还可以继承其他实现类
***>*******如果实现Runnable接口 多个线程可以共享一个Target对象 非常适合多个相同的线程共同处理同一个资源,
可以将cpu,代码,数据分开,从而形成一个清晰的模型 比较好的体现了面向对象的思想
**>劣势;编程稍微复杂一些, 如果需要访问当前线程 必须使用Thread.currentThread方法
采用是继承Thread类
优势:编程比较简单 如果需要访问当前线程 无需使用Thread.currentThread方法 直接使用this来获得当前线程
劣势: 因为继承了Thread类无法再继承其他的类 不能进行相应的扩展
推荐使用实现Runnable接口
三.线程的生命周期
面试题: 线程的生命周期分为几种分别介绍一下
当线程创建完毕后 它经历了五种状态
当线程运行的时候 不能一直占有cpu的时间片 cpu会在多个线程之间相互调度 线程进入阻塞和运行状态进行切换
新建
当线程 对象被创建出来 就进入到了新建状态 和其他java对象一样的 仅仅是由java虚拟机为其分配了内存地址
就绪
当调用start方法 仅仅是进入就绪状态不是运行状态 至于什么时候进入运行状态是由jvm虚拟机进行调度决定的
运行
如果线程就绪状态 同时获得了cpu的执行权 这个线程就进入到运行状态
线程由阻塞状态进入到运行状态
a.调用阻塞方法已经返回
b.调用sleep方法已经到期
c.线程获取同步监视器成功
d.调用的suspend方法已经恢复
阻塞
线程什么时候进入阻塞状态:
a.线程调用一个阻塞的方法,在该方法返回之前一直阻塞
b.线程调用sleep方法进入阻塞状态
c.线程尝试获取同步监视器 ,但该同步监视器被其他线程所持有
d.线程调用suspend方法
死亡
a.run方法执行完毕
b.线程抛出异常
c.直接调用线程的stop方法
四.线程的控制
1.join线程
先start后join
public class JoinThreadDemo { public static void main(String[] args) { JoinThreadDemo jtd =new JoinThreadDemo(); jtd.init(); } public void init(){ MainThread mt =new MainThread(); //对当前线程设置名称 mt.setName("主线程"); mt.start(); } /* * 要求; * 主线程打印到30的时候 停止 先去打印join加塞线程 打印 1-30 然后继续执行主线程 */ class MainThread extends Thread { @Override public void run() { for (int i = 0; i < 50; i++) { if(i==30){ //执行加塞线程 JoinThread jt =new JoinThread(); jt.setName("加塞进程"); //是先join然后star还是先start然后join jt.start(); try { jt.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(this.getName()+","+i); } } } class JoinThread extends Thread { @Override public void run() { for (int i = 0; i < 30; i++) { //获得当前线程名字 每个线程都有自己的默认的名称 System.out.println(this.getName()+","+i); } } } }
2.线程的让步
线程的让步(yield())和线程睡眠(sleep(long millis))差不多 但是有一个重要的区别
线程的让步是让当前线程暂停执行 会放弃cpu的执行权 然后操作系统重新调用重新分配资源 所有相同优先级的线程都有可能抢到CPU的执行权 由于当前线程还在运行,所以不允许低优先级的线程获得运行机会
线程的睡眠是让线程暂停执行,但是不放弃cpu的执行权,允许较低优先级的线程获得运行机会,时间一到就开始继续执行
3.线程的优先级
每个线程都有一个优先级 优先级越高 得到cpu执行的权利越高
优先级在windows系统有时无用的 在linux系统是完全可以用的
4.线程的睡眠
让线程休息一下 可以使用Thread类的静态方法实现
五.************线程的安全
线程的安全 概念定义?
《Java并发编程实践》,作者做出了 如下定义: 多个线程访问一个类的对象 如果不考虑这些线程在运行环境下的调度和交替执行
并且不需要额外的同步以及在掉队方法代码不需要做其他的协调 那么这个类的行为如果仍然正常 那么这个类就是线程安全的
synchronized 这个加到方法上意味着整个方法都加了一把大锁
同步代码块 就是定义一组原子行为所谓的原子行为 只允许一个线程既进入直到这个线程执行完毕 下一个线程才可以进入
synchronized(obj){
}
synchronized(this){ 代表这个类的方法
}
锁住类的字节码
synchronized(PrintString.class){
}
public class ThreadSafe { public static void main(String[] args) { ThreadSafe ts = new ThreadSafe(); ts.init(); } // 全局的 PrintString ps = new PrintString(); public void init() { // 开启两个线程 new Thread(new Runnable() { @Override public void run() { while (true) { ps.showString("AAAAAAAAAAAAAAAAAAAAAAAA"); } } }).start(); new Thread(new Runnable() { @Override public void run() { while (true) { ps.showString("BBBBBBBBBBBBBBBBBBBBBBBB"); } } }).start(); } // 这个是一个普通的内部类 class PrintString { // 打印指定的字符串 public void showString(String s) { synchronized (PrintString.class) { for (int i = 0; i < s.length(); i++) { System.out.print(s.charAt(i));//charAt返回指定索引处的char值 } System.out.println(); } } } }
同步监视器
比如A线程想执行showString方法 需要尝试获取同步监视器 但是这个同步监视器被B线程所持有 所以A线程需要等待 -->阻塞状态
执行B线程执行完毕 释放同步监视器 A获得同步监视器然后执行showString方法 也就是运行状态
完成两个程序同时运行 每个程序自加到10000 两个程序总和必须是20000
public class TwoAdd { /** * 思路: 1.两个线程同时访问一个方法 一定会有并发问题 必须考虑加锁 * 2.如果做到 保证在main方法执行完毕之前 其他两个线程都执行完毕 */ public int i = 0; public static void main(String[] args) { TwoAdd ta =new TwoAdd(); ta.init(); } // 保证操作的是同一个类的对象 AddDemo ad = new AddDemo(); public void init() { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10000; i++) { ad.add(); } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10000; i++) { ad.add(); } } }).start(); //根据线程的活跃数 来确定线程是否执行完毕 while(Thread.activeCount()!=1){ //继续卡在这里继续执行其他程序 } //在打印i之前必须保证两个线程都运行完毕 System.out.println(i); } class AddDemo { public synchronized void add() { i++; } } }
六.wait notify
A线程在运行的过程中不满足条件 则需要等待 等待B线程执行完毕才能执行 ----死锁
面试题: wait和sleep有什么区别?
a. sleep必须指定时间 而wait不需要 可以不指定
b.sleep和wait都可以让线程处于冻结状态(阻塞状态)----释放了执行权(相同点)
c.持有锁的线程执行sleep 不释放锁 不释放cpu的执行权 wait 释放锁
d.sleep到时间后会自动唤醒 wait没有指定时间必须通过notfiy或者notifyAll唤醒这个线程
练习: 假设有三个线程同时执行 分别每个线程执行一次
A B C A B C
public class ThreadWaitDemo { /* * 两个线程A和B 让两个线程交替执行 A B A B A B 思路: 1.A和B线程是同时争抢cpu的执行权 A A设置一个条件 * 让a必须等待b的执行完毕才能执行 wait B 线程执行完毕才唤醒A线程 */ public static void main(String[] args) { ThreadWaitDemo tw =new ThreadWaitDemo(); tw.init(); } // 同一个对象 PrintString ps = new PrintString(); public void init() { new Thread(new Runnable() { @Override public void run() { while (true) { ps.f1(); } } }).start(); new Thread(new Runnable() { @Override public void run() { while (true) { ps.f2(); } } }).start(); } class PrintString { // 通过一个标志来判断谁执行 boolean flag = true; public synchronized void f1() { while (flag) { try { wait();// 等待其他线程唤醒 } catch (InterruptedException e) { e.printStackTrace(); } } flag = true; notifyAll();// 唤醒所有线程 System.out.println("我是线程AAAAAAAAAAAAAAAAAAAAAA"); } public synchronized void f2() { while (!flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } flag = false; notifyAll();// 唤醒所有的线程 System.out.println("我是线程BBBBBBBBBBBBBBBBBBBBBB"); } } }
public class ThreadWaitDemo2 { public static void main(String[] args) { ThreadWaitDemo2 tw2 = new ThreadWaitDemo2(); tw2.init(); } PrintString ps = new PrintString(); public void init() { new Thread(new Runnable() { @Override public void run() { while (true) { ps.f1(); } } }).start(); new Thread(new Runnable() { @Override public void run() { while (true) { ps.f2(); } } }).start(); new Thread(new Runnable() { @Override public void run() { while (true) { ps.f3(); } } }).start(); } class PrintString { public int i = 1; public synchronized void f1() { while (i != 1) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } i = 2; notifyAll(); System.out.println("AAAAAAAAAAAAAAAAAAAAAAAAAA"); } public synchronized void f2() { while (i != 2) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } i = 3; notifyAll(); System.out.println("BBBBBBBBBBBBBBBBBBBBBBBBB"); } public synchronized void f3() { while (i != 3) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } i = 1; notifyAll(); System.out.println("CCCCCCCCCCCCCCCCCCCCCC"); } } }
死锁:是指两个或两个以上线程在执行的过程中,因争抢资源而进入相互等待的现象 如果没有外力作用 它们将无法推动进行此时系统处于某种死锁状态
这就是所指的线程进入死锁状态
为什么会产生死锁现象呢?
a.因为系统资源不足
b.系统执行的顺序不正确
c.资源分配不当
操作系统 : 产生死锁需要四个 :
1.互斥条件 :所谓的互斥条件就是进程在某一个时间内独占资源
2.请求与保持条件: 一个进程因为请求资源而阻塞时,对已获得的资源部释放
3.不剥夺条件 :进程对已获得的资源在未使用完毕之前 不能强行剥夺
4.循环等待条件: 若干个进程之间 形成一种头尾相互等待资源的关系
如果避免产生死锁:
只要想法破解其中一个产生死锁的条件即可
避免事务中用户的交互(减少持有资源的时间,较少的锁去竞争)
使用隔离级别(数据库....)
多线程高级部分
JUC详解
一、java.util.concurrent.atomic
支持在单个变量上解除锁的线程安全编程。
AtomicInteger
addAndGet( 以原子方式将给定值与当前值相加。
getAndIncrement() 以原子方式将当前值加 1。
incrementAndGet() 以原子方式将当前值加 1。
public class AtomicIntergerDemo { public int i=0; public static void main(String[] args) { new AtomicIntergerDemo().init(); } AtomicInteger aa=new AtomicInteger(); public void init(){ new Thread(new Runnable() { public void run() { for (int i = 0; i <10000; i++) { add(); } } }).start(); new Thread(new Runnable() { public void run() { for (int i = 0; i <10000; i++) { add(); } } }).start(); while(Thread.activeCount()!=1){ } System.out.println(aa.intValue()); } public synchronized void add(){ aa.getAndIncrement(); } }
底层依赖于 compareAndSet (CAS) CAS需要三个操作数 分别是内存位置V 旧的预期的值 A 新的值N
使用CAS进行更新时 当v符合A 使用N更新v的值 否则就不执行更新
二:java.util.concurrent.locks
1.Lock锁Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock 类还可以提供与隐式监视器锁完全不同的行为和语义,
Lock接口 有一个实现类 ReentrantLock 一个可重入的互斥锁
使用Lock锁 有以下优势:
怎样加锁 在什么位置释放锁一目了然
Lock锁相对于synchronized增加了以下功能:
等待可中断 当前持有锁线程 如果长期不释放 正在等待的线程可以放弃等待 处理其他事情
公平锁
绑定条件
public class LockDemo { public static void main(String[] args) { new LockDemo().init(); } Printstring ps = new Printstring(); public void init() { new Thread(new Runnable() { @Override public void run() { while (true) { ps.showString("AAAAAAAAAAAAAAAA"); } } }).start(); new Thread(new Runnable() { @Override public void run() { while (true) { ps.showString("BBBBBBBBBBBBBBBB"); } } }).start(); } } class Printstring { // 得到Lock锁 Lock lock = new ReentrantLock(); public void showString(String s) { try { // 在这个位置进行加锁 lock.lock(); for (int i = 0; i < s.length(); i++) { System.out.print(s.charAt(i)); } System.out.println(); } finally { lock.unlock(); } } }
2.Condition 条件锁
public class ConditionDemo { PrintString ps=new PrintString(); public static void main(String[] args) { new ConditionDemo().init(); } public void init(){ new Thread(new Runnable() { public void run() { while(true){ ps.f1(); } } }).start(); new Thread(new Runnable() { public void run() { while(true){ ps.f2(); } } }).start(); new Thread(new Runnable() { public void run() { while(true){ ps.f3(); } } }).start(); } class PrintString{ //得到条件锁 Lock lock=new ReentrantLock(); //得到三把条件锁 Condition c1=lock.newCondition(); Condition c2=lock.newCondition(); Condition c3=lock.newCondition(); int token=1; public void f1(){ try{ lock.lock(); while(token!=1){ //是当前线程进入等待状态 c1.await(); } System.out.println(Thread.currentThread().getName()); token=2; //单独唤醒c2 c2.signal(); }catch(Exception e){ }finally{ lock.unlock(); } } public void f2(){ try{ lock.lock(); while(token!=2){ //是当前线程进入等待状态 c2.await(); } System.out.println(Thread.currentThread().getName()); token=3; //单独唤醒c2 c3.signal(); }catch(Exception e){ }finally{ lock.unlock(); } } public void f3(){ try{ lock.lock(); while(token!=3){ //是当前线程进入等待状态 c3.await(); } System.out.println(Thread.currentThread().getName()); token=1; //单独唤醒c2 c1.signal(); }catch(Exception e){ }finally{ lock.unlock(); } } } }
三、java.util.concurrent
1.线程池
线程池概念: 线程池也是一种多线程处理的方式,处理过程中 将任务添加到队列中 然后创建线程后自动启动这些任务
线程池都是一些后台线程 一定时间内创建固定数量的后台线程
使用线程池的步骤:
1.使用Executors 类的静态方法创建ExcutorService对象 这个对象就代表一个线程池
2.使用这个线程池对象的 excute或者submit方法进行提交 这个任务也是一个线程
3.执行完毕要关闭线程池
public class ThreadPool { public static void main(String[] args) { //创建一个固定大小的线程池 ExecutorService pool1=Executors.newFixedThreadPool(5);//相当于创建了5个线程 //创建了一个线程的线程池 ExecutorService pool2=Executors.newSingleThreadExecutor(); //相当于创建了一个固定大小为10的线程池 ExecutorService pool3=Executors.newCachedThreadPool(); //开启十个任务 每个任务的事情都是循环十次 final Random r=new Random(); for (int i = 0; i <10; i++) { final int task=i; pool3.execute(new Runnable() { public void run() { for (int j = 0; j < 10; j++) { try { Thread.sleep(r.nextInt(100)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("当前线程:"+Thread.currentThread().getName()+ "在运行第"+task+"个任务"); } } }); } pool3.shutdown(); } }
2.容器
同步容器:使用同步容器要加锁 不让会报错
java.util.ConcurrentModificationException 迭代的时候进行修改
并发容器 :用并发容器代替同步容器(集合)
ConcurrentHashMap 是代替Map同步的并发容器
ConcurrentLinkedQueue
CopyOnWriteArrayList List的并发容器的替代
CopyOnWriteArraySet set并发容器的实现
四、同步工具
java.util.concurrent
1.Semaphore
这个类代表一个信号量,信号量维护了一个许可集,可以控制某个资源被访问的个数
如果要访问资源 必须获得一个acquire(),如果没有则等待其他的release()
public class SemaphoreDemo { Dosth ds=new Dosth(); public static void main(String[] args) { SemaphoreDemo sd=new SemaphoreDemo(); sd.go(); } //同时访问worked方法的并发不能超过2个 public void go(){ //开启十个线程 final Semaphore sp=new Semaphore(2);//设置最大的许可数 for (int i = 0; i <10; i++) { new Thread(new Runnable() { @Override public void run() { try { //获得许可 sp.acquire(); ds.work(); sp.release();//最后要释放许可 } catch (InterruptedException e) { e.printStackTrace(); } } },"线程:"+i).start(); } } class Dosth{ public void work(){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } }
2.CountDownLatch
四个人约定打球
public class CountDownLatchDemo { public static void main(String[] args) { CountDownLatchDemo cdl=new CountDownLatchDemo(); cdl.init(); } Random r = new Random(); CountDownLatch cla = new CountDownLatch(3); public void init() { new Thread(new Runnable() { @Override public void run() { System.out.println("张三正在赶来的路上..."); try { Thread.sleep(r.nextInt(5000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("张三到达集合地点"); cla.countDown(); } },"张三线程").start(); new Thread(new Runnable() { @Override public void run() { System.out.println("李四正在赶来的路上..."); try { Thread.sleep(r.nextInt(5000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("李四到达集合地点"); cla.countDown(); } },"李四线程").start(); new Thread(new Runnable() { @Override public void run() { System.out.println("王五正在赶来的路上..."); try { Thread.sleep(r.nextInt(5000)); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("王五到达集合地点"); cla.countDown(); } },"王五线程").start(); new Thread(new Runnable() { @Override public void run() { System.out.println("司机正在集合地点等待"); try { cla.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("所有人到达集合地点可以进行下一步任务"); } },"司机线程").start(); } }
可以应用在处理大数据时 比如:1千万个数据可以分为1000个线程处理 每个线程处理10000个数据 然后让一个总线程处理得到的1000个线程的结果
3.CyclicBarrier
public class CyclicBarrierDemo { public static void main(String[] args) { final CyclicBarrier cyclicBarrier = new CyclicBarrier(3); ExecutorService service = Executors.newFixedThreadPool(10); for (int i = 0; i < 3; i++) { service.execute(new Runnable() { @Override public void run() { for (int j = 0; j < 5; j++) { try { Thread.sleep(new Random().nextInt(5000)); System.out.println(Thread.currentThread().getName() + "已经到达集合点" + ",现在共有" + (cyclicBarrier.getNumberWaiting() + 1) + "个线程达到"); if (cyclicBarrier.getNumberWaiting() + 1 == 3) { System.out.println("所有线程都已经达到集合点,可以进行下一步任务.."); } else { System.out.println("正在等待其他线程的到达..."); } cyclicBarrier.await(); } catch (Exception e) { e.printStackTrace(); } } } }); } service.shutdown(); } }