java学习第二十二天

多线程

1. Lock锁

  • 重入锁 ReentrantLock
    • 作用synchronized一样
    • 如果出现异常,不会自动释放锁,所以开锁的代码一定放在finally中
  • 读写锁
    • 读锁 ReadLock
    • 写锁 WriteLock
    • 规则
      • 写 写 互斥
        • 写 读 互斥
      • 读 读 不互斥
package com.qfedu;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo02 {

	public static void main(String[] args) {
		
		TestThread2 thread = new TestThread2();
		
		for(int i=1; i<=10; i++) {
			Thread t = new Thread(thread,"线程"+i);
			t.start();
		}
		
	}
}

class TestThread2 implements Runnable {
	
	/*
	 * ReentrantLock : 重入锁
	 * 	作用和synchronized一默一样
	 */
	Lock lock = new ReentrantLock();

	List<Integer> list = new ArrayList<Integer>();
	
	@Override
	public void run() {
		
		int i = 1;
		
		while(true) {
			
			try {
				lock.lock();  //加锁

				if(i>100) {
					break;
				}
				list.add(i);
				i++;
				
			} finally {
				lock.unlock(); //解锁
			}
			
		}
		System.out.println(Thread.currentThread().getName()+"添加完毕,当前数组中元素的个数为:"+list.size());
		
	}
	
}
package com.qfedu;

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

public class Demo04 {

	/*
	 * 
	 * 使用synchronized对方法进行加锁
	 * 如果多个线程同时操作同一个LockThread对象
	 * 1. 如果一个线程正在执行LockThread对象的setStr方法(写入)
	 * 	1.1 其他线程都不能执行 getStr()  setStr()  这两个方法
	 * 
	 * 2. 如果一个线程正在执行LockThread对象的getStr方法(写入)
	 * 	2.1 其他线程都不能执行 getStr()  setStr()  这两个方法
	 * 
	 * 写  写 	 互斥
	 * 写  读	 互斥
	 * 读  读      互斥		即使出现线程不安全的情况,没什么影响
	 * 
	 * 
	 * 怎么才能达到读读不互斥呢?
	 * 换一种加锁的方式
	 * - 重入锁 ReentrantLock	作用synchronized一样
	 * - 读锁 ReadLock
	 * - 写锁 writeLock
	 * 	-- 写  写 	 互斥
	 *  -- 写  读	 互斥
	 *  -- 读  读      不互斥
	 * 
	 */
}

class LockThread  {
	
	String str;
	
	ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
	
	//读锁
	ReadLock readLock = lock.readLock();
	
	//写锁
	WriteLock writeLock = lock.writeLock();
	
	
	public void getStr() {
		
		try {
			readLock.lock();
			System.out.println("得到str的值:"+str);
		} finally {
			readLock.unlock();
		}
		
	}
	
	public synchronized void setStr(String str) {
		
		try {
			writeLock.lock();
			System.out.println("为str的重写设置值:" + str);
		} finally {
			writeLock.unlock();
		}
	}
	
}

2. 线程池

使用线程池,获取多个线程,执行相应的任务,而且多个线程对象可以重复使用

package com.qfedu;

public class Demo01 {

	public static void main(String[] args) {
		
		A a = new A();
		
		for(int i=1; i<=5; i++) {
			Thread t = new Thread(a);
			t.start();
		}
		
		B b = new B();
		for(int i=1; i<=5; i++) {
			Thread t = new Thread(b);
			t.start();
		}
		
	}
}

class A implements Runnable {
	
	int i=100;
	
	@Override
	public void run() {
		
		while(true) {
			
			if(i <= 0) {
				break;
			}
			
			
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			System.out.println(Thread.currentThread().getName()+"打印了=="+i);
			
			i--;
		}
		
	}
}


class B implements Runnable {
	
	@Override
	public void run() {
		
		System.out.println(Thread.currentThread().getName()+"==执行结束");
	}
}

2.1 固定长度的线程池

语法:

ExecutorService es = Executors.newFixedThreadPool(线程个数);
  • 会在线程池中创建固定个数的线程对象
  • 线程执行实现了Runnable接口实现类对象定义的run方法
    • 执行多少次submit()方法,就派多少个线程执行此业务
  • 在执行结束后,线程对象不会被回收还可以继续使用执行其他任务
package com.qfedu;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo02 {

	
	public static void main(String[] args) {
		
		/*
		 * 1. 创建一个固定长度的线程池
		 * 池子里面有三个Thread对象
		 */
		ExecutorService es = Executors.newFixedThreadPool(6);
		
		A a = new A();
		
		/*
		 * 每执行一次submit方法,就是派一个线程执行该业务逻辑
		 */
		for(int i=1; i<=3; i++) {
			es.submit(a);
		}
		
		/*
		 * 把a的业务代码执行后,还可以继续使用线程池的线程,执行新的任务
		 */
		B b = new B();
		for(int i=1; i<=3; i++) {
			es.submit(b);
		}
		
			
		//关闭线程池
		es.shutdown();
	}
	
}

2.2 可变长度的线程池

语法:

ExecutorService es = Executors.newCachedThreadPool();
  • 多少个任务就创建多少个线程对象(核心线程数,最大线程数)
  • 如果线程池的个数,小于核心线程数的,那么只要有新的任务就创建新的线程对象
  • 如果线程池的个数,已经达到核心线程数,如果有新的任务
    • 如果所有的线程都在执行,没有空闲线程,才会创建新的线程,但是不能超过最大线程数
      • 我们很多设置把核心线程数,最大线程数相同
    • 如果有空闲线程,那么不会创建新的线程,使用空闲的线程执行的任务
package com.qfedu;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo03 {

	
	public static void main(String[] args) {
		
		/*
		 * 2. 创建一个可变长度的线程池
		 * 有多少个任务,就创建多少个线程对象去执行
		 * 	
		 */
		ExecutorService es = Executors.newCachedThreadPool();
		
		A a = new A();
		
		/*
		 * 每执行一次submit方法,就是派一个线程执行该业务逻辑
		 */
		for(int i=1; i<=4; i++) {
			es.submit(a);
		}
		
		/*
		 * 把a的业务代码执行后,还可以继续使用线程池的线程,执行新的任务
		 */
		B b = new B();
		for(int i=1; i<=4; i++) {
			es.submit(b);
		}
		
			
		//关闭线程池
		es.shutdown();
		
	}
	
}

2.3 单线程的线程池

语法:

ExecutorService es = Executors.newFixedThreadPool(线程的个数);
  • 线程池中只有一个线程
package com.qfedu;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo04 {

	
	public static void main(String[] args) {
		
		/*
		 * 3. 创建一个单线程的线程池
		 * 有多少个任务,就创建多少个线程对象去执行
		 * 	
		 */
		ExecutorService es = Executors.newSingleThreadExecutor();
		
		A a = new A();
		
		/*
		 * 每执行一次submit方法,就是派一个线程执行该业务逻辑
		 */
		for(int i=1; i<=3; i++) {
			es.submit(a);
		}
		
		/*
		 * 把a的业务代码执行后,还可以继续使用线程池的线程,执行新的任务
		 */
		B b = new B();
		for(int i=1; i<=3; i++) {
			es.submit(b);
		}
		
			
		//关闭线程池
		es.shutdown();
		
	}
}

3. Runnable,Callable

  • Runnable
    • 定义一个线程的任务
    • 如果想让一个线程调用执行什么业务逻辑,那就定义类实现Runnable,把业务定义在run方法中
    • 可以做到多个线程操作同一个变量
    • 没有返回值
  • Callable
    • 定义一个线程的任务
    • 如果想让一个线程调用执行什么业务逻辑,那就定义类实现Callable,把业务定义在run方法中
    • 可以做到多个线程操作同一个变量
    • 可以有返回值
package com.qfedu;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Demo05 {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		
		E e = new E();
		
		ExecutorService es = Executors.newSingleThreadExecutor();
		
		es.submit(e);
		
		
		F f = new F();
		Future<Integer> future = es.submit(f);
		
		//获取执行完该任务后,返回的值
		int result = future.get();
		
		System.out.println(result);
		
		
		es.shutdown();
	}
}

class E implements Runnable {

	@Override
	public void run() {
		
		int sum = 0;
		for(int i=1; i<=100; i++) {
			sum += i;
		}
		
		System.out.println(Thread.currentThread().getName()+"执行结束,结果为:"+sum);
	}
	
}

class F implements Callable<Integer> {

	@Override
	public Integer call() throws Exception {
		
		int sum = 0;
		for(int i=1; i<=100; i++) {
			sum += i;
		}
		return sum;
	}
	
}
//编写一个任务,执行结束后,返回一个随机的小写字母

//编写一个双色球程序 6个1-33   1个1-16的整数

4. 线程安全的集合

1. 转化为线程安全的集合

package com.qfedu;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Demo01 {

	public static void main(String[] args) {
		
		Collection<String> coll = new ArrayList<String>();
		
		/*
		 * SynchronizedCollection  这是一个线程安全的集合
		 */
		Collection<String> sc = Collections.synchronizedCollection(coll);
		
		/*
		 * SynchronizedList  线程安全的List集合
		 */
		List<String> list = new LinkedList<String>();
		List<String> sl = Collections.synchronizedList(list);
		
		/*
		 * SynchronizedSet 线程安全的Set集合
		 */
		Set<String> set = new HashSet<String>();
		Set<String> ss = Collections.synchronizedSet(set);
		
		
		/*
		 * SynchronizedMap  线程安全的Map集合
		 */
		Map<String,String> map = new HashMap<String,String>();
		Map<String, String> sm = Collections.synchronizedMap(map);
		
	}
}
  • Vector
  • Hashtable

他们都是线程安全集合,他们都是使用synchronized关键字修饰方法,同一时刻只能一个线程执行其中一个方法。

2. CopyOnWriteArrayList

  • 线程安全的ArrayList,加强版读写分离
  • 写有锁,读无锁,读写之间不阻塞
  • 写入时,先copy一个容器副本、再添加新元素,最后替换引用

3. CopyOnWriteArraySet

  • 线程安全的Set,底层使用CopyOnWriteArrayList实现

4. ConcurrentHashMap

  • 初始容量默认为16段(Segment),使用分段锁设计
  • 不对整个Map加锁,而是为每个Segment加锁
  • 当多个对象存入同一个Segment时,才需要互斥
  • 最理想状态为16个对象分别存入16个Segment,并行数量16。
  • 使用方式与HashMap无异

5. 队列

队列也是一种集合,能够做到边遍历边删除的效果,这符合很多实际的使用场景

  • 银行取号排队
  • 医院挂号排队
  • 请求的排队处理
package com.qfedu;

import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;

public class Demo02 {

	public static void main(String[] args) {
		
		//定义队列
		Queue<String> queue = new ArrayBlockingQueue<String>(5);
		
		//添加数据
		queue.offer("aa");
		queue.offer("bb");
		queue.offer("cc");
		queue.offer("dd");
		queue.offer("ee");
		
		for(int i=0;i<queue.size(); i++) {
			String v = queue.poll();
			System.out.println(v+"队列中还剩下"+queue.size()+"个元素");
			i--;
		}
		
	}
}

6. lambda表达式

当接口中只有一个抽象方法时,可以使用lambda表达式来实现接口的实现

接口  变量 = (参数列表) -> { 
	//方法体
};
  • 如果方法没有返回值,且方法体只有一行代码,可以省略掉大括号{}
  • 如果有参数,参数可以直接定义在小括号中
    • 可以不定义参数的数据类型 可以自动匹配
  • 如果参数只有一个,不仅数据类型可以省略,小括号也可以省略
  • 如果有参数,有返回值,那就在大括号中使用return关键返回数据
  • 如果方法体中只有一行代码,且为返回数据(return),在省略掉大括号时,也可以把return关键字也省略点
package com.qfedu;

public class Demo02 {
	
	
	public static void main(String[] args) {
		/*
		 *  () -> {}
		 * 
		 *  () : 参数列表
		 *  {} : 方法体
		 */
		B b1 = () -> {
			System.out.println("实现了test方法1");
			/* System.out.println("实现了test方法1"); */
		};
		
		
		/*
		 * 如果方法没有返回值,且方法体只有一行代码,可以省略掉大括号{}
		 */
		B b2 = () -> System.out.println("实现了test方法2"); 
		
		
		
		/*
		 * 如果有参数,参数可以直接定义在小括号中
		 */
		C c = (int a, int b) -> System.out.println(a+b);
		
		/*
		 * 如果有参数,参数可以直接定义在小括号中,可以不定义参数的数据类型 可以自动匹配
		 */
		C c2 = (a, b) -> System.out.println(a+b);
		
		/*
		 * 如果参数只有一个,不仅数据类型可以省略,小括号也可以省略
		 */
		CD cd = a ->System.out.println(a);
		
		
		/*
		 * 如果有参数,有返回值,那就在大括号中使用return关键返回数据
		 */
		D d = (a, b) -> {
			int sum = a+b;
			return sum;
		};
		
		
		/*
		 * 如果方法体中只有一行代码,且为返回数据(return)
		 * 在省略掉大括号时,也可以把return关键字也省略点
		 */
		D d2 = (a, b) -> a+b;
		
		int r = d2.test(1, 2);
		
		System.out.println("-------------------------------------------");
		
		
		Runnable runnable = ()->{
			
		};
		
		Thread t = new Thread(runnable);
		
		Thread t2 = new Thread(()->{
			
		});
		
		//使用lambda表达式实现接口的方式,让4个窗口共卖100张票
		
	}

}

interface B {
	
	void test();
	
}


interface C {
	
	void test(int a, int b);
	
}

interface CD {
	
	void test(int a);
	
}


interface D {
	
	int test(int a, int b);
	
}

7. 函数式接口

1. 消费型接口

消费型接口:只有参数,没有返回值

@FunctionalInterface
public interface Consumer<T> {
 	void accept(T t);
}
Consumer<Integer> consumer = t -> {
			System.out.println(t%10);
		};
		
consumer.accept(19);

2. 供给型接口

public interface Supplier<T> {

    T get();
    
}
Supplier<Character> supplier = () ->{
			int n = (int) (Math.random()*26+97);
			return (char) n;
		};
		
Character c = supplier.get();

3. 函数型接口

public interface Function<T, R> {

    R apply(T t);
}
Function<String, Integer> function = t -> t.length();
		
Integer len = function.apply("abc");
System.out.println(len);

4. 断言型接口

public interface Predicate<T> {

    boolean test(T t);
}
//断言型接口
Predicate<String> predicate = t -> t.startsWith("j");
boolean r = predicate.test("jack");
System.out.println(r);
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值