
本文共 8154 字,大约阅读时间需要 27 分钟。
线程基础
进程概念:运行中的程序
线程概念:是进程中的一条执行路径,往往一个进程中会有多条执行路径--多线程JVM从main方法入口开始执行,这就是一个执行路径,我们叫做主线程;可以在主线程中,创建其他线程,我们叫做子线程
进程与线程的关系:
线程是CPU调度的基本单位,CPU切片切到谁就谁执行 一个进程可以包含多个线程,至少有一个线程 进程具有独立的资源空间,但是一个进程中的多个线程共享资源线程的特点:随机互抢资源
线程的组成:
CPU时间片:由操作系统分配每个线程执行的时间 运行数据: 堆数据:实例化出来的线程对象 栈数据:线程引用指向实例化的对象线程的逻辑代码
线程的创建方式:1.创建一个类继承Thread,2.创建一个类实现Runnable任务
线程创建方式1
案例:主线程和子线程,各打印200次(互抢资源方式)
//案例:主线程和子线程,各打印200次(互抢资源方式) //分析:1.创建线程继承Thread类,重写run方法,该方法就是子线程执行的区域 //2.实例化子线程对象,调用run方法执行 class MyThread extends Thread { @Override public void run() { for(int i=1;i<=200;i++) { System.out.println("子线程执行-->"+i); } } } public class Test1 { public static void main(String[] args) { //线程的启动:将当前线程对象放入线程组供CPU调度,当CPU调度到你,在内部调用run方法;当CPU调度不到你,则是就绪状态 MyThread thread = new MyThread(); thread.start(); new MyThread().start(); /实例化一个线程对象,多次start() 不可以/ for(int i=1;i<=200;i++) { System.out.println("主线程执行-->"+i); } } }线程创建方式2
案例:实现一个任务的方式
//创建线程方式2:实现一个任务的方式 class Task implements Runnable { @Override public void run() { for(int i=1;i<=200;i++) { System.out.println("子线程执行-->"+i); } } } public class Test2 { public static void main(String[] args) { Thread thread = new Thread(new Task()); thread.start(); for(int i=1;i<=200;i++) { System.out.println("主线程执行-->"+i); } } }线程状态
线程可以处于以下几种状态:
线程的休眠方法:
语法:Thread.sleep(毫秒) 用法:可以用在主线程或子线程中 目的:在线程中可以复现互抢资源的现象线程优先级设置:
线程优先级设置:给线程设置优先级,可以大概率的确定谁先执行完;但不是绝对的 MyThread thread1 = new MyThread("线程1"); thread1.setPriority(Thread.MIN_PRIORITY); thread1.start(); MyThread thread2 = new MyThread("线程2"); thread2.setPriority(Thread.MAX_PRIORITY); thread2.start();线程的礼让:
线程礼让:yield 设置了礼让的线程,会将线程资源让一次出去,继续跟其他线程争抢 class A extends Thread { @Override public void run() { for(int i=1;i<=200;i++) { System.out.println("设置礼让线程1===》"+i); Thread.yield(); } } } 同样的设置B线程的合并:
线程的合并(插队):join 在线程中设置了插队后,插队的线程绝对性地先执行完线程安全
多线程存储数组元素的数据安全问题
线程安全案例:在线程中给定一个数组,两个线程同时往数组中存元素
分析:先执行的线程应该存储第一个下标位置,后执行的线程,应该存储第二个下标位置 问题:还没来得及下标的累加,可能都存储到了第一个位置——因为线程具有随机互抢特性解决:
问题1:打印数组时,可能数据还没有存储 解决: 想个办法先执行子线程的存储;再执行主线程的打印---join 问题2:new两个对象,线程中对象的属性都有两份,没办法共享数据 解决:将线程的属性变为static 问题3:数据出现问题,都存储到第一个位置了 解决:加锁:同步代码块,同步方法class MyThread extends Thread {
static String[] s = {"","","","",""}; static int index; String value; public MyThread(String value) { this.value = value; } @Override public void run() { synchronized("lock") { s[index] = value; index++; } } } public class Test { public static void main(String[] args) throws InterruptedException { MyThread my1 = new MyThread("hello"); my1.start(); MyThread my2 = new MyThread("world"); my2.start(); my1.join(); my2.join(); System.out.println(Arrays.toString(MyThread.s)); } }继承Thread方式实现线程安全
卖票问题:
*问题1:每个对象都会卖1000张,共5000张 解决方案:ticket属性+static修饰,变为了5个线程只卖1000张 *问题2:出现部分重票的问题——数据安全问题 解决方案:加锁 锁的注意事项:class MyThread extends Thread {
private static int ticket = 1000; public MyThread(String name) { super(name); } @Override public void run() { while(true) { synchronized("lock") { if(ticket>0) { System.out.println(super.getName()+"窗口正在卖第"+ticket+"张票"); ticket--; } else { System.out.println(super.getName()+"窗口已经卖完了"); break; } } } } } public class Test2 { public static void main(String[] args) throws InterruptedException { MyThread myTicket1 = new MyThread("窗口1"); myTicket1.start(); MyThread myTicket2 = new MyThread("窗口2"); myTicket2.start(); myTicket1.join(); myTicket2.join(); System.out.println("票已经卖完了"); } }实现任务方式完成线程安全
通过实现任务的方式完成卖票系统的案例:
分析:创建一个Task类实现Runnable接口 与继承Thread的区别: 1.多个线程操作同一个Task任务,所以属性无需加static 2.同步代码块的锁对象可以用this 3.同步方法无需加staticclass Task implements Runnable {
private int ticket = 1000; @Override public void run() { while(true) { synchronized(this) { if(ticket>0) { System.out.println(Thread.currentThread().getName()+"窗口正在卖第"+ticket+"张票"); ticket--; } else { System.out.println(Thread.currentThread().getName()+"窗口已经卖完了"); break; } } } } } public class Test3 { public static void main(String[] args) throws InterruptedException { ExecutorService es = Executors.newFixedThreadPool(5); for(int i=0;i<10;i++) { es.submit(new Task()); } es.shutdownNow(); System.out.println("所有任务已提交"); System.out.println("等待任务完成..."); es.awaitTermination(1, TimeUnit.SECONDS); } }死锁
死锁案例:
线程的双方都握着对方的资源,都退不出去,最后形成了死锁——锁嵌套 创建一个类继承Thread,里面有一个属性,用于做判断的 实例化两个线程,传递参数,1个为true,一个为false 判断中,一个先执行A锁,一个先执行B锁的锁嵌套class MyThread extends Thread {
private boolean flag; public MyThread(boolean flag) { this.flag = flag; } @Override public void run() { if(flag) { synchronized("A") { System.out.println(super.getName()+"--进入了A锁"); synchronized("B") { System.out.println(super.getName()+"--进入了B锁"); } } } else { synchronized("B") { System.out.println(super.getName()+"--进入了B锁"); synchronized("A") { System.out.println(super.getName()+"--进入了A锁"); } } } } }生产者消费者模型
多线程模型:安全锁机制,等待与唤醒
生产者负责生产 消费者负责消费 我们需要准备一个仓库存钱,消费者看库存,库存有钱才能消费 库存没钱——等待消费 库存满了——生产者停止生产,等待消费者消费后,有库存空间才生产//仓库类
public class Store { private int num; //库存数量 public void push() throws InterruptedException { synchronized(Store.this) { while(num>=20) { this.wait(); } num++; System.out.println("生产者已经生产了第"+num+"件货"); this.notify(); } } public void pop() throws InterruptedException { synchronized(Store.this) { while(num<=0) { this.wait(); } System.out.println("消费者已经消费了第"+num+"件货"); num--; this.notify(); } } }public class Test4 {
public static void main(String[] args) { Store store = new Store(); new Producter(store).start(); new Customer(store).start(); //添加更多生产者和消费者线程 new Producter(store).start(); new Customer/store>.start(); } }线程高级功能
线程池
概念:可以认为是线程对象的容器,预先创建多个线程对象,然后根据这些对象可以实现复用
good处:减少创建和销毁线程的数目,提升性能
复用机制:用完了线程对象后,重新回收到线程池中//线程池的创建方式:
ExecutorService es = Executors.newCachedThreadPool(); //带缓冲区的线程池 ExecutorService es = Executors.newSingleThreadExecutor(); //单线程池 ExecutorService es = Executors.newFixedThreadPool(2); //固定线程数的池public class Test5 {
public static void main(String[] args) { ExecutorService es = Executors.newFixedThreadPool(2); Task task = new Task(); es.submit(task); es.submit(task); es.submit(task); es.shutdown(); while(!es.isTerminated()) { } } }Callable接口
Callable接口:和Runnable类似,是处理任务的接口
区别:Callable是带返回值的接口,可以将线程中处理完的数据返回Future接口:该对象接收submit的返回值,可以求出call返回值
//案例:用两个线程计算1~50,51~100的和,并进行汇总
ExecutorService es = Executors.newFixedThreadPool(2); Future读写锁
读写锁:一般用在读远远高于写的情况,可以提升线程执行的性能
读写锁:在读与读之间是不互斥,不阻塞的,所以性能会提升//读写锁的使用案例
public class MyClass { private ReadWriteLock lock = new ReadWriteLock(); ReadLock rl = lock.readLock(); WriteLock wl = lock.writeLock(); private Integer value;public Integer getValue() { try { rl.lock(); return value; } finally { rl.unlock(); } } public void setValue(Integer value) { try { wl.lock(); this.value = value; } finally { wl.unlock(); } }
}
public class Test6 { public static void main(String[] args) { MyClass myClass = new MyClass(); Runnable rt = new Runnable() { @Override public void run() { myClass.getValue(); } }; Runnable wt = new Runnable() { @Override public void run() { myClass.setValue(1); } }; ExecutorService es = Executors.newFixedThreadPool(20); for(int i=0;i<18;i++) es.submit(rt); for(int i=0;i<2;i++) es.submit(wt); es.shutdown(); while(!es.isTerminated()){} System.out.println(System.currentTimeMillis()-start); } }线程安全集合
Collections工具类中提供了针对List,Set,Map的安全集合
//同步列表的使用:
List list = Collections.synchronizedList(new ArrayList());//优化并发安全集合:
CopyOnWriteArrayList:安全且性能优越的手动维护的列表CopyOnWriteArraySet:类似CopyOnWriteArrayList,但相当于一个高级集合
ConcurrentHashMap:线程安全的HashMap
队列
Queue队列接口:特点:先进先出
//普通队列的实现:
Queue queue = new LinkedList(); queue.offer(1); queue.offer(3); queue.offer(2); System.out.println(queue.peek()); System.out.println(queue.poll()); System.out.println(queue);ConcurrentLinkedQueue:
发表评论
最新留言
关于作者
