24.线程

1.线程基础
  进程概念:运行中的程序
  线程概念:是进程中的一条执行路径,往往一个进程中会有多条执行路径--多线程

  JVM从main方法入口开始执行,这就是一个执行路径,我们叫做主线程;
  可以在主线程中,创建其他线程,我们叫做子线程


  进程与线程的关系:
  线程是cpu调度的基本单位,cpu切片切到谁就谁执行
  一个进程可以包含多个线程,至少有一个线程
  进程具有独立的资源空间,但是一个进程中的多个线程共享资源
  
  线程的特点: 随机互抢资源

  线程的组成:
  cpu时间片: 由操作系统分配每个线程执行的时间
  
  运行数据:
  堆数据:实例化出来的线程对象
  栈数据:线程引用指向实例化的对象

  线程的逻辑代码

  线程的创建方式:1.创建一个类继承Thread, 2.创建一个类实现Runnable任务

==============线程的创建方式1==============

//案例:主线程和子线程,各打印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方法;
		//没有调度到你,则是就绪状态
		MyThread thread = new MyThread();  
		thread.start();
		//thread.start();  //实例化一个线程对象,多次start  不可以
		
		new MyThread().start();
		
		for(int i=1;i<=200;i++) {
			System.out.println("主线程执行-->"+i);
		}
		
	}
}

=============线程的创建方式2=============

//创建线程方式2:实现一个任务的方式
//案例:主线程和子线程,各打印200次(互抢资源方式)
//Task实现Runnable任务
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);
		}
	}
}

=====线程状态(基本)=====

 

1.1 休眠方法
语法: Thread.sleep(毫秒)
用法: 可以用在主线程或子线程中
目的: 在线程中可以复现互抢资源的现象  


1.2 线程优先级设置setPriority
线程优先级设置:给线程设置优先级,可以大概率的确定谁先执行完; 但不是绝对的

        MyThread thread1 = new MyThread("线程1");
		thread1.setPriority(Thread.MIN_PRIORITY); //设置小优先级
		thread1.start();
		MyThread thread2 = new MyThread("线程2");
		thread2.setPriority(Thread.MAX_PRIORITY); //设置优先级,再启动
		thread2.start();

1.3. 线程的礼让
线程的礼让: yield
设置了礼让的线程,会将线程资源让一次出去,继续跟其他线程争抢;
会影响执行效率,但不是绝对性的

class A extends Thread{
	@Override
	public void run() {
		for(int i=1;i<=200;i++) {
			System.out.println("设置礼让线程1===>"+i);
			Thread.yield();  //礼让
		}
	}
}
同样的设置B

1.4 线程的合并
线程的合并(插队)--join
在线程中设置了插队后,插队的线程绝对性地先执行完;

============线程状态(等待)===============

 

2. 线程安全

2.1 多线程存储数组元素的数据安全问题
线程安全案例:
案例:在线程中给定一个数组,两个线程同时往数组中存元素
分析:先执行的线程应该存储第一个下标位置,后执行的线程,应该存储第二个下标位置
问题:还没来得及下标的累加,可能都存储到了第一个位置---因为线程具有随机互抢特性
 

//代码应用:
//问题1:打印数组时,可能数据还没有存储
//解决: 想个办法先执行子线程的存储;再执行主线程的打印---join
//问题2:new两个对象,线程中对象的属性都有两份,没办法共享数据
//解决:将线程的属性变为static
//问题3:数据出现问题,都存储到第一个位置了
//加锁:
//锁: 同步代码块,同步方法
//同步代码块: synchronized("lock") {}
//锁的注意事项: 1.两个线程使用同一把锁(同一个锁对象),2.锁的范围


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;  //将存储与下标累加操作进行捆绑
		    //睡眠3毫秒,复现有问题的数据
			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));
	}
}

2.2 继承Thread方式实现线程安全

卖票问题:

*问题1: 每个对象都会卖1000张,共5000张
 *解决方案: ticket属性+static修饰,变为了5个线程只卖1000张
 *
 *问题2:出现部分重票的问题---数据安全问题
 *解决方案: 加锁
 *锁的注意事项:
 *1.同一把锁(静态属性,常量值)    
 *2.锁的范围,锁整个while还是whlie里面的语句(锁住里面,如果锁while,则只有一个窗口卖) 
 *
 *问题3: 会出现负数和0?  临界点问题
 *原因: 当票到达1张时,所有线程都可进入到while判断ticket>0,只是一个线程进入锁,其他在锁外等待
 *解决: 在锁内部加入if(ticket>0)的判断
 *
 *问题4: 优化,有多少个窗口卖,必须有多少个窗口退出
 *解决方案:加入else提示,将while做一个死循环,在else中break

class MyThread extends Thread{
	private static int ticket = 1000;
	public MyThread(String name) {
		super(name);
	}
	@Override
	public void run() {
		
		//锁方式1:同步代码块
		/*
		while(true) {
			synchronized ("lock") {
				if(ticket>0) {
					System.out.println(super.getName()+"窗口正在卖第"+ticket+"张票");
					ticket--;
				}else {
					System.out.println(super.getName()+"窗口已经卖完了");
					break;
				}
			}
		}*/
		//锁方式2: 同步方法:也要注意同一把锁(调用方法的对象-this有问题,需加static),和锁的范围
		while(true) {
			if(save()) {  //在同步方法调用中要进行返回值判断
				break;
			}
		}
	}
	//同步方法的实现
	private static synchronized boolean save() {
		if(ticket>0) {
			System.out.println(Thread.currentThread().getName()+"窗口正在卖第"+ticket+"张票");
			ticket--;
			return false;
		}else {
			System.out.println(Thread.currentThread().getName()+"窗口已经卖完了");
			return true;
		}
	}
}

2.3 实现任务方式完成线程安全

通过实现任务的方式完成卖票系统的案例:
分析:创建一个Task类实现Runnable接口

与继承Thread的区别
1.多个线程操作同一个Task任务,所以属性无需加static
2.同步代码块的锁对象可以用this
3.同步方法无需加static
 

class Task implements Runnable{
	private int ticket = 1000;
	@Override
	public void run() {
		/*
		while(true) {
			//锁方式1:同步代码块
			synchronized (this) {
				if(ticket>0) {
					System.out.println(Thread.currentThread().getName()+"窗口正在卖第"+ticket+"张票");
					ticket--;
				}else {
					System.out.println(Thread.currentThread().getName()+"窗口已经卖完了");
					break;
				}
			}
		}
		*/
		//锁方式2: 同步方法:也要注意同一把锁(调用方法的对象-this有问题,需加static),和锁的范围
		while(true) {
			if(save()) {  //在同步方法调用中要进行返回值判断
				break;
			}
		}
	}
	
	//同步方法的实现
	private synchronized boolean save() {
		if(ticket>0) {
			System.out.println(Thread.currentThread().getName()+"窗口正在卖第"+ticket+"张票");
			ticket--;
			return false;
		}else {
			System.out.println(Thread.currentThread().getName()+"窗口已经卖完了");
			return true;
		}
	}
	
}

3. 死锁

死锁案例: 线程的双方都握着对方的资源,都退不出去,最后形成了死锁---锁嵌套
分析:
创建一个类继承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锁");
				}
			}
		}
	}
} 

4.生产者消费者模型
   技术点:
   多线程模型,安全锁机制,等待与唤醒
   生产者负责生产
   消费者负责消费
   我们需要准备一个仓库存钱,消费者看库存,库存有钱才能消费
   库存没钱---等待消费
   库存满了---生产者停止生产,等待消费者消费后,有库存空间才挣

============代码案例===========

/**
  多线程 生产与消费模型: 
 分析:
 1,创建生产者类继承Thread,只负责生产
 2. 创建消费者类继承Thread,只负责消费
 3. 两个线程共同操作仓库类Store
 4. 加入安全锁及等待与唤醒机制
 
 问题1:生产两个动作,消费也是两个动作,当你打印生产,还没加库存;就已经消费了,库存值不对
 处理:加锁 ,解决生产与消费的数据混乱
 
 问题2: 仓满和仓空的限制
处理:  限制仓满,生产者等待(wait);仓空,消费者等待(wait)      

细节: 
1. 等待时,会将资源交出去
2. 锁对象与等待唤醒的对象是同一个 

扩展: 多个生产与多个消费情况
    判断中改为while
   
 *
 */
public class Test1 {
	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();
	}
}

-----生产者-----
public class Producter extends Thread {
	private Store store;
	public Producter(Store store){  //库存要存进来
		this.store = store;
	}
	@Override
	public void run() {
		while(true) {
			try {
				store.push();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}  //生产者负责生产
		}
	}
}

----消费者----
public class Customer extends Thread {
	private Store store;
	public Customer(Store store) {
		this.store = store;
	}
	@Override
	public void run() {
		while(true) {
			try {
				store.pop();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}  //消费者负责消费
		}
	}
}

---库存---
public class Store {
	private int num;  //单位万
	
	public void push() throws InterruptedException {  //库存生产
		
		synchronized (this) {
			//生产20件货,就满了,停止生产
			while(num>=20) {
				this.wait();
			}
			
			num++;
			System.out.println("生产者已经生产了第"+num+"件货");
			
			this.notify();  //唤醒等待的线程
		}
		
	}
	
	public void pop() throws InterruptedException {  //库存消费
		synchronized (this) { //
			while(num<=0) {
				this.wait();  //等待消费,等待时,相当于把自身资源交出去了
			}
			System.out.println("消费者已经消费了第"+num+"件货");
			num--;
			
			this.notify();  //唤醒等待的线程
		}
		
	}
}

===========线程状态(阻塞)===========

 

 5.多线程高级
  5.1 线程池(重点)

  概念:可以认为是线程对象的容器,预先创建多个线程对象,然后根据这些对象可以实现复用
  好处:减少创建和销毁线程的数目,提升性能 
  复用机制: 用完了线程对象后,重新回收到线程池中

class Task implements Runnable{
	@Override
	public void run() {
		for(int i=1;i<=10;i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	};
}
public static void main(String[] args) {
		//创建一个单个线程的线程池对象
		//ExecutorService es = Executors.newSingleThreadExecutor();
		
		//创建一个带缓冲区的线程池对象,灵活控制线程对象,有多少任务,我就创建多少个线程对象
		//ExecutorService es = Executors.newCachedThreadPool();
		
		//创建固定个数的线程池对象,如果任务过多,则用回收线程对象去处理多余任务
		ExecutorService es = Executors.newFixedThreadPool(2);
		Task task = new Task();
		es.submit(task);  //类似,线程.start()
		es.submit(task);
		es.submit(task); //谁先用完并回收,就谁去执行新任务
		es.shutdown();  //关闭线程池
	}

5.2 Callable接口
Callable接口: 和Runnable类似,也是处理任务的接口
区别: Callable是带返回值的接口,可以将线程中处理完的数据返回

Future接口,该对象接收submit的返回值,可以求出call返回值

//案例: 使用两个线程计算1~50,51~100的和,并进行汇总
ExecutorService es = Executors.newFixedThreadPool(2);
		Future<Integer> future = es.submit(new Callable<Integer>() {
			@Override
			public Integer call() throws Exception {
				int sum = 0;
				for(int i=1;i<=50;i++) {
					sum += i;
				}
				return sum;
			}
		});
		
		Future<Integer> future2 = es.submit(new Callable<Integer>() {

			@Override
			public Integer call() throws Exception {
				int sum = 0;
				for(int i=51;i<=100;i++) {
					sum += i;
				}
				return sum;
			}
		});
		
		//get方法类似线程基础的join,在主线程中阻塞
		int sum = future.get()+future2.get();
		System.out.println("两个线程的和:"+sum);

5.3 重入锁
重入锁:Lock接口    实现类:ReentrantLock
与synchronized类似,用于加锁的
提供了两个方法: lock()-获取锁   unlock()-释放锁
 

向字符串数组中写入值,避免在3,4 的位置重复写入
class MyList{
	String[] ss = {"A","B","",""};
	int   index = 2;
	Lock lock = new ReentrantLock();
	
	public void add(String value) {
		//有多个线程可以调用add方法
		lock.lock();  //加锁
		try {
			ss[index] = value;
			index++;
		} finally {
			lock.unlock();
		}
	}
}

5.4 读写锁
读写锁:一般用在读远远高于写的情况,可以提升线程执行的性能
读写锁,在读与读之间是不互斥,不阻塞的,所以性能会提升

class MyClass{
	//加入读写锁
	ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	ReadLock rl = readWriteLock.readLock();
	WriteLock wl = readWriteLock.writeLock();
	
	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 Test2 {
	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);
		
		long start = System.currentTimeMillis();
		for(int i=0;i<18;i++) {
			es.submit(rt);  //18个线程用于读操作
		}
		for(int i=0;i<2;i++) {
			es.submit(wt);  //2个线程用于写操作
		}
		es.shutdown();
		while(!es.isTerminated()){}  //阻塞,确保所有线程结束后,才往下走
		
		System.out.println(System.currentTimeMillis()-start);
		
	}
}

6.线程安全的集合
6.1 Collections提供的安全集合

Collections工具类中提供了针对List,Set,Map的安全集合

List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());


当然他们的安全机制性能方面有比较大的影响,一般我们会用后续并发安全集合

6.2 CopyOnWriteArrayList
CopyOnWriteArrayList: ArrayList的安全集合(读写并发)
内部进行了加锁,且性能方面比Collestions提供的安全集合更高
该安全集合具有读写分离的特性
读无锁,写有锁,读写之间不互斥,不阻塞,在内部拷贝了一份副本存数据
往往也是用在读多写上的情况

6.3 CopyOnWriteArraySet
CopyOnWriteArraySet: 安全的set集合,底层实现通过CopyOnWriteArrayList
该类也是读写分离的set集合类,该类具备存储唯一性

6.4.线程安全的HashMap
ConcurrentHashMap: 并发的HashMap(前提是安全)
Coolections中也提供了线程安全的Map,只不过锁的是整个hash表

 

7.队列

7.1 Queue队列接口
Queue:队列的接口,特点:先进先出
LinkedList实现类就实现了该接口

        Queue<Integer> queue = new LinkedList<Integer>();
        //如果队列没有元素,则返回null
        queue.offer(1);
		queue.offer(3);
		queue.offer(2);
		
		System.out.println(queue.peek());  //取元素不移除
		System.out.println(queue.poll());  //取元素并移除
		System.out.println(queue);

7.2ConcurrentLinkedQueue实现类

ConcurrentLinkedQueue: 在多线程中,性能最高的并发的队列
内部采用了CAS无锁交换算法进行存储
有3个参数:  V(要改变的变量)      E(预期值)      N(新值)
执行过程中,如果V=E,那么N就可以改变V变量的值了
 

Queue<Integer> queue = new ConcurrentLinkedQueue<Integer>();
		Thread th1 = new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=1;i<=5;i++) {
					queue.offer(i);
				}
			}
		});
		th1.start();
		Thread th2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i=6;i<=10;i++) {
					queue.offer(i);
				}	
			}
		});
		th2.start();
		th1.join();  th2.join();
		for(int i=1;i<=10;i++) {
			System.out.println(queue.poll());
		}

7.3 阻塞队列
BlockingQueue:阻塞队列的接口
实现类有: 有界队列ArrayBlockingQueue(推荐),无界队列LinkedBlockingQueue
有界队列是有长度限制    无界队列可以认为没有
有两个阻塞方法: put,take ,该阻塞方法的使用类似于生产者消费者用法
put:一般不能超过有界队列长度,超过了阻塞       take,没有值时阻塞
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值