什么是进程和线程?
进程:在操作系统中能够独立运行,并且作为资源分配的基本单位。它表示运行中的程序。系统运行一个程序就是一个进程从创建、运行到消亡的过程。
线程:是一个比进程更小的执行单位,能够完成进程中的一个功能,也被称为轻量级进程。一个进程在其执行的过程中可以产生多个线程。
线程的生存周期
线程生存周期示意图:
线程的几种状态:
1.新建(new):通过new新创建了一个线程对象。
2.就绪(Runable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
3.运行:可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码
4.阻塞:阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
5.销毁:线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
创建线程的四种方式?
一.继承Thread类
package com.study.test;
/**
* Create with IntelliJ IDEA.
*
* @author: [email protected]
* Date: 2019/10/17
* Time: 15:16
*/
public class Test {
public static void main(String[] args) {
//直接继承了Thread ,创建一个Thread的实例
ThreadTest t1 = new ThreadTest();
t1.start();
//如果是实现了接口的线程类,需要用对象的实例作为Thread类构造方法的参数
Thread t2 = new Thread(new RunableTest());
t2.start();
}
}
class ThreadTest extends Thread{
@Override
public void run() {
System.out.println("继承了Thread类");
}
}
class RunableTest implements Runnable{
@Override
public void run() {
System.out.println("实现了Runnable类");
}
}
二.实现Runable接口
三.实现Callable接口
package com.cecdata.test;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
for (int i=0 ;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行时间"+new Date().getTime()+"执行次数"+i);
}
return "callable执行完成";
}
public static void main(String[] args) {
//实现callable创建一个线程
//创建futureTask实例,创建callable实例
FutureTask<String> futureTask = new FutureTask<String>(new MyCallable());
//创建Thread实例,执行futureTask
Thread thread = new Thread(futureTask,"myCallable");
thread.start();
//在主线程上打印信息
for (int i=0 ;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行时间"+new Date().getTime()+"执行次数"+i);
}
//获取并打印mycallable打印的结果
try {
String result = futureTask.get();
System.out.println("MYcallable执行结果:"+ result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
实现接口和继承Thread类比较
1.接口更适合多个相同的程序代码的线程去共享同一资源
2.接口可以避免java的单继承局限性
3.接口代码可以被多个线程共享,代码和线程独立
4.线程池只能放入runable和callable接口,不能直接放入thread的类
注:在Java中,每个程序启动至少启动2个线程。一个是main,一个是垃圾收集线程。
Runable和Callable接口比较
相同点:1.两者都是接口
2.两者都可用来编写多线程程序
3.两者都需要Thread.start()启动线程
不同点:1.实现Callable接口的线程能返回结果,runable接口则不能
2.callable接口call()方法允许抛出异常,runable接口的run()方法则不能
3.callable接口可以调用Future.cancel取消执行,runable接口则不能取消
注:callable支持返回结果,此时调用futureTask.get()实现,此方法会阻塞主线程直接过去'将来'结果;当不调用此方法时,主线程不会阻塞。
四.线程池创建线程
为什么使用线程池?
多线程缺点:
处理任务的线程创建和销毁,以及多线程之间的切换都非常耗时并消耗资源;
解决办法:线程池
使用时线程已经存在,消除了线程的创建的时耗;
通过设置线程数目,防止资源不足。
ThreadPoolExecutor源码
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
线程池类关系图:
java封装了Executors类,方便我们了解创建四大线程池
1.Executors调用线程池创建线程
package com.cecdata.test;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyRunabler implements Runnable{
@Override
public void run() {
for (int i=0 ;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行时间"+new Date().getTime()+"执行次数"+i);
}
}
public static void main(String[] args) {
//使用线程池创建线程
//使用Executors 获取线程池对象,
// 固定线程个数
ExecutorService executorService = Executors.newFixedThreadPool(10);
//通过线程池对象创建线程并执行MyRunable实例
executorService.execute( new MyRunabler());
//在主线程上打印信息
for (int i=0 ;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行时间"+new Date().getTime()+"执行次数"+i);
}
}
}
2.Executors.newFixedThreadPool(int); 一池固定数线程,newFixedThreadPool:用于执行长期的任务,性能好很多
实例:
public class MyNewFixedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个处理线程
// threadPool.execute(); 无返回值
// threadPool.submit(); 可以有返回值/无返回值
try {
//模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
for (int i = 1; i <= 10; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"\t 办理业务");
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
threadPool.shutdown();
}
}
}
3.Executors.newSingleThreadExecutor(); 一池一线程,用于一个任务一个任务执行的场景
实例:
/**
* newSingleThreadExecutor:用于一个任务一个任务执行的场景
*/
public class MyNewSingleThreadExecutorDemo {
public static void main(String[] args) {
//判断cpu核数
System.out.println(Runtime.getRuntime().availableProcessors());
ExecutorService threadPools = Executors.newSingleThreadExecutor();//一池一线程
try {
//模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
for (int i = 1; i <= 10; i++) {
threadPools.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"\t 办理业务");
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
threadPools.shutdown();
}
}
}
4.Executors.newCachedThreadPool(); 一池多线程,用于执行很多短期异步的小程序或者负载较轻的服务器
/**
* newCachedThreadPool:用于执行很多短期异步的小程序或者负载较轻的服务器
*/
public class MyNewCachedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();//一池多线程
try {
//模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
for (int i = 1; i <= 10; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"\t 办理业务");
}
});
//线程休息一会
TimeUnit.SECONDS.sleep(5);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
threadPool.shutdown();
}
}
}
线程池七大参数:
corePoolSize:核心线程的数量,默认不会被回收掉,但是如果设置了allowCoreTimeOut为true,那么当核心线程闲置时,也会被回收。
maximumPoolSize :最大线程数量,线程池能容纳的最大容量
keepAliveTime:闲置线程被回收的时间限制,也就是闲置线程的存活时间
unit :keepAliveTime的单位,即保活时间的单位
workQueue :用于存放任务的队列
threadFactory :创建线程的工厂类
handler:当任务执行失败时,使用handler通知调用者,代表拒绝的策略
用生活的例子来解释:去银行办理业务
线程池的工作原理示意图:
如果还不明白的话:
当调用线程池的 execute() 方法时,线程池会做出以下判断:
如果当前运行的线程小于线程池的核心线程数,那么马上创建线程完成这个任务。
如果运行中的线程数大于等于线程池的核心线程数,那么将线程放进任务队列等待。
如果此时任务队列已满,且正在运行的线程数小于最大线程数,立即创建非核心线程执行这个任务。
如果此时任务队列已满,且正在运行的线程数等于最大线程数,则线程池会启动饱和拒绝策略来执行。
当线程空闲超过一定时间时,线程池会判断当前运行线程数是否大于核心线程数,如果大于核心线程数,该线程就会被停掉,直至当前线程数等于核心线程数。
谈谈线程池的拒绝策略——四大拒绝策略:
拒绝策略是什么?
等待队列也已经排满了,再也塞不下新任务了,同时,线程池中的max线程也达到了,无法继续为新任务服务 这时候我们就需要拒绝策略机制合理的处理这个问题
四大拒绝策略??
CallerRunsPolicy:"调用者运行"一种调节机制,该策略不会抛弃任务,也不会抛弃一场,而是将某些任务退回到调用的线程 AbortPolicy(默认):直接抛出异常阻止系统正常运行
DiscardPolicy:直接丢弃任务,不予任何处理,不抛出异常,如果允许任务丢失,这是最好的一种方案。
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。 工
作中单一的/固定数的/可变的三种创建线程池的方法,用的哪个多?
超级大坑 一个都不用,我们生产中只能使用自定义的。
工作中如何使用线程池,是否自定义过线程池?
//自定义线程池
public final class MyExecutorThreadPoolDemo {
private static MyExecutorThreadPoolDemo myExecutorThreadPoolDemo = new MyExecutorThreadPoolDemo();
private static final int CORE_SIZE_POOL = 20;
private static final int MAX_SIZA_POOL = 25;
private static MyExecutorThreadPoolDemo newInstance() {
return myExecutorThreadPoolDemo;
}
private final ExecutorService threadPool = new ThreadPoolExecutor(
CORE_SIZE_POOL,
MAX_SIZA_POOL,
10L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
/**
* 初始化线程池,并添加线程
*/
public void perpare(Runnable task) {
if (task != null) {
threadPool.execute(task);
}
}
/**
* 关闭线程
*/
public void shutDown(){
threadPool.shutdown();
}
}
我们公司直接使用底层的ThreadPoolExecutor进行封装自定义线程池
package com.hzz.cecdata.thread;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* Create with IntelliJ IDEA.
*
* @author: [email protected]
* Date: 2019/9/24
* Time: 17:03
*/
public final class ThreadPoolManager {
private static ThreadPoolManager threadPoolManager = new ThreadPoolManager();
// 线程池维护线程的最少数量
private static final int SIZE_CORE_POOL = 20;
// 线程池维护线程的最大数量
private static final int SIZE_MAX_POOL = 20;
public static ThreadPoolManager newInstance() {
return threadPoolManager;
}
/**
* 线程池
* @param corePoolSize - 池中所保存的线程数,包括空闲线程。
* @param maximumPoolSize - 池中允许的最大线程数。
* @param keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
* @param unit - keepAliveTime 参数的时间单位。
* @param workQueue - 执行前用于保持任务的队列。此队列仅由保持 execute 方法提交的 Runnable 任务。
* @param handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
*/
private final ThreadPoolExecutor mThreadPool = new ThreadPoolExecutor(SIZE_CORE_POOL, SIZE_MAX_POOL, 0L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
/**
* 初始化
*/
public void perpare() {
mThreadPool.setKeepAliveTime(10, TimeUnit.SECONDS);
mThreadPool.allowCoreThreadTimeOut(true);
if (mThreadPool.isShutdown() && !mThreadPool.prestartCoreThread()) {
int startThread = mThreadPool.prestartAllCoreThreads();
}
}
/**
* 添加任务
* @param task
*/
public void addExecuteTask(Runnable task) {
if (task != null) {
mThreadPool.execute(task);
}
}
/**
* 判断是否最后一个任务
* @return
*/
public boolean isTaskEnd() {
if (mThreadPool.getActiveCount() == 0) {
return true;
} else {
return false;
}
}
/**
* 获取缓存大小
* @return
*/
public int getQueue(){
return mThreadPool.getQueue().size();
}
/**
* 获取线程数量
* @return
*/
public int getPoolSize(){
return mThreadPool.getPoolSize();
}
/**
* 获取已完成任务数
* @return
*/
public long getCompletedTaskCount(){
return mThreadPool.getCompletedTaskCount();
}
/**
* 关闭线程池
*/
public void shutdown() {
mThreadPool.shutdown();
}
private ThreadPoolManager() {}
}
线程安全问题
开启线程后,那就涉及安全问题
安全即是数据安全,线程是什么,是否安全,与我无关。但数据必须安全。这里就要提到内存了,因为,造成数据不安全的就是内存。
对于码农来说:程序就是一个进程,一个线程是其中之一。当系统为进程分配空间时,就会有公共空间(堆,公共方法区),和栈等。而造成不安全的就是这块公共的内存空间。当一个线程在进行数据处理时而另一个线程也对此数据进行处理,数据不安全,程序紊乱,也就是说线程不安全。
线程安全的根本原因:
1.多个线程在操作共享数据
2多个线程对共享数据有写操作
演出出现线程问题
package com.cecdata.test;
public class Ticket implements Runnable {
private int ticketNum = 100;//电影票数量
@Override
public void run() {
while (true) {
if (ticketNum > 0) {
//有票,线程睡眠100毫秒
try {
Thread.sleep(100);
//打印当前售出票数字和线程名
String name = Thread.currentThread().getName();
//票数-1
System.out.println("线程name" + name + "销售电影票:" + ticketNum--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
//创建电影票
Ticket ticket = new Ticket();
//创建Thread对象,执行售卖电影票
Thread thread1 = new Thread(ticket, "窗口1");
Thread thread2 = new Thread(ticket, "窗口2");
Thread thread3 = new Thread(ticket, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
执行结果出现线程安全问题:同一张票出现多个窗口进行贩卖
解决线程安全问题
要解决上述线程问题,只要在某个线程修改共享资源的时候,其他线程不修改共享资源,等待修改完毕同步之后,才能去争夺spu资源,完成对应操作,保证了数据的同步性,解决了线程安全问题
保证线程都能正常执行共享资源,java引入了7种线程同步机制
解决方法一:悲观锁
加锁,只要加锁,那就要产生线程的阻塞,执行的性能就会降低。悲观锁就是悲观的认为只要我不加锁,那我的数据就会被其他线程修改,所以每次操作都要加锁,直到操作完成。
悲观锁:
悲观锁就是假设每次操作都会有其他人使用同一个资源, 所以每次执行过程都是; 加锁-->使用资源-->释放锁, 项目中常使用synchronized对需要的代码部分加锁。
使用synchronized主要是因为synchronized使用的是内置锁, 加锁和解锁都由jdk实现, 使用者无需手动控制, 比较方便。
使用场景多是多线程开发时, 并行处理数据, 对方法或者代码块使用。
在使用时, 可尽量减少synchronized修饰的方法或代码块中的代码, 减少资源消耗。
具体悲观锁使用情况:
1.同步代码块(synchronized)
package com.cecdata.test;
public class Ticket implements Runnable {
private int ticketNum = 100;//电影票数量
@Override
public void run() {
while (true) {
synchronized (this){
if (ticketNum > 0) {
//有票,线程睡眠100毫秒
try {
Thread.sleep(100);
//打印当前售出票数字和线程名
String name = Thread.currentThread().getName();
//票数-1
System.out.println("线程name" + name + "销售电影票:" + ticketNum--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
//创建电影票
Ticket ticket = new Ticket();
//创建Thread对象,执行售卖电影票
Thread thread1 = new Thread(ticket, "窗口1");
Thread thread2 = new Thread(ticket, "窗口2");
Thread thread3 = new Thread(ticket, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
2.同步方法(synchronized)
package com.cecdata.test;
public class Ticket implements Runnable {
private int ticketNum = 100;//电影票数量
@Override
public void run() {
while (true) {
saveTicket();
}
}
public synchronized void saveTicket() {
if (ticketNum > 0) {
//有票,线程睡眠100毫秒
try {
Thread.sleep(100);
//打印当前售出票数字和线程名
String name = Thread.currentThread().getName();
//票数-1
System.out.println("线程name" + name + "销售电影票:" + ticketNum--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//创建电影票
Ticket ticket = new Ticket();
//创建Thread对象,执行售卖电影票
Thread thread1 = new Thread(ticket, "窗口1");
Thread thread2 = new Thread(ticket, "窗口2");
Thread thread3 = new Thread(ticket, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
同步锁是谁?
可以理解为把需要的数据全部加锁,在事务提交之前,这些数据全部读写互斥。
对于非static方法同步,同步锁就是this,使用在方法上是,指的是当前对象;
对于static方法同步,同步锁是当前方法所在类的字节码对象(类名.class)。
3.同步锁(ReentrantLock)
package com.cecdata.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable {
private int ticketNum = 100;//电影票数量
private Lock lock = new ReentrantLock(true);//ture-公平锁,多个线程拥有共同执行权;false-非公平,独占锁
@Override
public void run() {
while (true) {
//加锁
lock.lock();
if (ticketNum > 0) {
//有票,线程睡眠100毫秒
try {
Thread.sleep(100);
//打印当前售出票数字和线程名
String name = Thread.currentThread().getName();
//票数-1
System.out.println("线程name" + name + "销售电影票:" + ticketNum--);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
}
}
}
public static void main(String[] args) {
//创建电影票
Ticket ticket = new Ticket();
//创建Thread对象,执行售卖电影票
Thread thread1 = new Thread(ticket, "窗口1");
Thread thread2 = new Thread(ticket, "窗口2");
Thread thread3 = new Thread(ticket, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
synchronized和lock的区别
synchronized是jvm层面的java内置关键字,Lock是java并发包的一个类
synchronized可以自动释放锁(线程执行完同步代码块释放锁,Lock线程执行过程中发生异常会释放锁),Lock需在finally中手动释放锁(unlock方法),否则造成线程死锁
synchronized 关键字的两个线程1,2,如果1获取锁,2等待,如果1阻塞,2一直等待;而Lock不一定等待,如果尝试获取不到锁,线程可以不用一直等待就结束了。
synchronized 的锁可重入,不可中断,非公平,而Lock锁可重入,可判断,可公平(可非)
synchronized 锁适合代码少的同步问题
synchronized 无法判断是否获取锁的状态,Lock可以判断是否获取锁
Lock用于实现分组唤醒线程,可以精确唤醒(condition),而synchronized要么随机唤醒,要么唤醒全部:代码实例:
class ShareResource {
private int number = 1;//A:1,B:2,C:3
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
//判断
public void print5() {
lock.lock();
try {
while (number != 1) {
condition1.await();
}
//2.干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
number = 2;
//3.通知
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print8() {
lock.lock();
try {
while (number != 2) {
condition2.await();
}
//2.干活
for (int i = 1; i <= 8; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
//3.通知
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print10() {
lock.lock();
try {
while (number != 3) {
condition3.await();
}
//2.干活
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
//3.通知
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class TestLockCondition {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print5();
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print8();
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i <= 10; i++) {
shareResource.print10();
}
}, "C").start();
}
}
请手写一个自旋锁 ?
自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁, 这样的好处时减少线程上下 文切换的消耗 ,缺点:消耗CPU
实例代码:
/**
* 自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,
* 这样的好处时减少线程上下文切换的消耗,缺点:消耗CPU
* 模拟卫生间
* 编制一个自旋锁
*/
public class SpinLockDemo {
//原子引用线程
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
Thread thread =Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t come to 0()0");
while (!atomicReference.compareAndSet(null,thread)){
}
}
public void myUnLock(){
Thread thread =Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t invoke myUnlock");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try {
//暂停线程一会
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnLock();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
spinLockDemo.myLock();
try {
//暂停线程一会
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnLock();
},"B").start();
}
}
执行情况:
何为公平锁和非公平锁?两者区别?
公平锁:
是指多个线程按照申请锁的顺序来获取锁,获取锁时先查看此锁维护的等待队列,如果为空,或者当前线程时等待队列的第一个,就占有锁 ,否则加入等待队列中,类似排队打饭,先来后到;
非公平锁:
是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁, 在高并发的情况下,有可能会造成优先级反转或者饥饿现象
区别:
Lock lock = new ReentrantLock() 默认false非公平锁,非公平锁的优点在于吞吐量比公平锁大, synchronized非公平锁
可重入锁(又名递归锁):
指的是同一线程外层函数获取锁之后,内层递归函数仍能获取该锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,即 线程可以进入任何一个它已经拥有的锁所同步着的代码块 , 比如:回家用锁开过大门后,去有锁卫生间,默认信任厕所锁
可重入锁作用:避免死锁 , reentrantlock/synchronized就是典型的可重入锁
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "A").start();
new Thread(() -> {
try {
phone.sendSMS();
} catch (Exception e) {
e.printStackTrace();
}
}, "B").start();
Thread t3 = new Thread(phone);
Thread t4 = new Thread(phone);
t3.start();
t4.start();
}
}
class Phone implements Runnable {
public synchronized void sendSMS() throws Exception {
System.out.println(Thread.currentThread().getId() + "\t invoked sendSms()");
sendEmail();
}
public synchronized void sendEmail() throws Exception {
System.out.println(Thread.currentThread().getId() + "\t invoked sendEmail()+++++++++++++++++++");
}
Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
public void get() {
lock.lock();
try {
System.out.println(Thread.currentThread().getId() + "\t invoked get()");
set();
} finally {
lock.unlock();
}
}
//将两次锁能编译成功,能运行,但是加几次,解锁几次
public void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getId() + "\t invoked set()");
} finally {
lock.unlock();
}
}
}
运行情况:
独占锁(写锁):
该锁一次只能被一个线程持有,ReentrantLock 和 synchronized均为独占锁
共享锁(读锁):
指该锁可被多个线程所持有
读写锁(ReentrantReadWriteLock):
对ReentrantReadWriteLock其读锁时共享锁,其写锁为独占锁 ,读锁的共享锁可以保证并发读是非常高效的,读写,写写,写读的过程是互斥的
实例:不加读写锁
/**
* 资源类
*/
class MyCach {
private volatile Map<String, Object> map = new HashMap<>();
// private Lock lock = new ReentrantLock();//只允许一个线程操作,保证了原子性,但不能满足并发性
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
// lock.lock();
try {
//暂停线程一会
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// lock.unlock();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 写入完成:");
}
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "\t 正在读取:" + key);
try {
//暂停线程一会
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);
}
}
/**
* 多个线程同时读一个资源类无任何问题,所有为了满足并发量,读取共享资源应该可以同时进行
* 但是,如果有一个线程想去写共享资源,就不应该再有其他线程可以对该资源进行读写
* 小总结:读-读能共存
* 读-写不共存
* 写-写不共存
* 写操作:原子+ 独占,整个过程必须是一个完整的统一体,中间不许分割,被打断
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCach myCach = new MyCach();
for (int i = 1; i <=5 ; i++) {
final int tempInt = i;
new Thread(()->{
myCach.put(tempInt +"", tempInt +"");
},"aa").start();
}
for (int i = 1; i <=5 ; i++) {
final int tempInt = i;
new Thread(()->{
myCach.get(tempInt +"");
},"bb").start();
}
}
}
执行结果为:出现正在写入时,被打断了,违反了原子性
实例:加锁之后
/**
* 独占锁:该锁一次只能被一个线程持有,ReentrantLock 和 synchronized均为独占锁
* 共享锁:指该锁可被多个线程所持有
* 对ReentrantReadWriteLock其读锁时共享锁,其写锁为独占锁
* 读锁的共享锁可以保证并发读是非常高效的,读写,写写,写读的过程是互斥的
*/
/*
* 资源类
*
* 读写锁Demo
*/
class MyCach1 {
private volatile Map<String, Object> map = new HashMap<>();
//private Lock lock = new ReentrantLock();只允许一个线程操作,保证了原子性,但不能满足并发性
private ReentrantReadWriteLock rwLork = new ReentrantReadWriteLock();
public void put(String key, Object value) {
rwLork.writeLock().lock();
try {
try {
System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
//暂停线程一会
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t 写入完成:");
} catch (Exception e) {
e.printStackTrace();
} finally {
rwLork.writeLock().unlock();
}
}
public void get(String key) {
rwLork.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t 正在读取:" + key);
try {
//暂停线程一会
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
rwLork.readLock().unlock();
}
}
}
/**
* 多个线程同时读一个资源类无任何问题,所有为了满足并发量,读取共享资源应该可以同时进行
* 但是,如果有一个线程想去写共享资源,就不应该再有其他线程可以对该资源进行读写
* 小总结:读-读能共存
* 读-写不共存
* 写-写不共存
* 写操作:原子+ 独占,整个过程必须是一个完整的统一体,中间不许分割,被打断
*/
public class ReadWriteLockDemo2 {
public static void main(String[] args) {
MyCach1 myCach = new MyCach1();
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCach.put(tempInt + "", tempInt + "");
}).start();
}
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCach.get(tempInt + "");
}).start();
}
}
}
执行结果::读-读能共存,读-写不共存,写-写不共存
//要了解:java.util.concurrentjava并发包
Semaphore
信号灯主要有两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
package com.hzz.cecdata.lock.semaphore;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* 争车位
* 信号量主要有两个目的,一个时用于多个共享资源的互斥使用,另一个用于并发线程数的控制
*/
public class SemaPhoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);//模拟3个车位
for (int i = 1; i <= 6; i++) {//六部车
final int tempInt = i;
new Thread(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();//占
System.out.println(Thread.currentThread().getName() + "\t 抢到车位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "\t 停车3秒后离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放
semaphore.release();
}
}
}, String.valueOf(i)).start();
}
}
}
CyclicBarrier :
的字面意思时可循环使用的屏障。它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点) 时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过 CyclicBarrier的await方法。
package com.hzz.cecdata.lock.cyclic_barrier;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 收集龙珠召唤神龙
* CyclicBarrier 的字面意思时可循环使用的屏障。它要做的事情是,让一组线程到达一个屏障(也可以叫做同步点)
* 时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过
* CyclicBarrier的await方法。
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
// CyclicBarrier(int parties, Runnable barrierAction)
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙");
});
for (int i = 1; i <=7 ; i++) {
final int tempInt = i;
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"收集到第:"+tempInt+"龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
},String.valueOf(i)).start();
}
}
}
CountDownLatch :
让一些线程阻塞直到另一个线程完成一系列操作后才被唤醒,CountDownLatch主要有两个方法,当一个或多个线程await方法时,调用线程会被阻塞。 其他线程调用countDown方法时会将计数器减1(调用countDown方法的线程不会被阻塞), 当计数器变为0时,因调用await()方法被阻塞的线程会被唤醒,继续执行。
举例:使用CountDownLatch之前
public class CountDownLatchDemoBefore {
public static void main(String[] args) {
for (int i = 1; i <= 6; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t 上晚自习,离开教师");
}
}).start();
}
System.out.println(Thread.currentThread().getName() + "\t 班长离开教室,锁门 ");
}
}
执行结果:导致问题:班长把门锁了,学生未出来,实际情况应当学生出来完,班长锁门。
使用CountDownLatch之后
/**
* 人离开完,才会锁门
* CountDownLatch : 让一些线程阻塞直到另一个线程完成一系列操作后才被唤醒
* CountDownLatch主要有两个方法,当一个或多个线程await方法时,调用线程会被阻塞。
* 其他线程调用countDown方法时会将计数器减1(调用countDown方法的线程不会被阻塞),
* 当计数器变为0时,因调用await()方法被阻塞的线程会被唤醒,继续执行。
*/
public class CountDownLatchDemo {
public static void main(String[] args) {
closeDoor();
}
public static void closeDoor() {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t 上晚自习,离开教师");
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t 班长离开教室,锁门");
}
}
执行结果:问题解决,学生离开之后,班长关门(班长关门一定要放在主线程,最后执行)
秦灭六国例子()
如何使用枚举
package com.hzz.cecdata.lock.count_down_latch;
/**
* 枚举的使用
* Created by 86175 on 2019/11/27.
*/
public enum CountryEnum {
ONE(1, "齐"), TWO(2, "楚"), THREE(3, "韩"), FOUR(4, "燕"), FIVE(5, "魏"), SIX(6, "赵");
private Integer retCode;
private String retMessage;
public Integer getRetCode() {
return retCode;
}
public void setRetCode(Integer retCode) {
this.retCode = retCode;
}
public String getRetMessage() {
return retMessage;
}
public void setRetMessage(String retMessage) {
this.retMessage = retMessage;
}
CountryEnum(Integer retCode, String retMessage) {
this.retCode = retCode;
this.retMessage = retMessage;
}
public static CountryEnum foreachCountryEnum(int index) {
CountryEnum[] myArray = CountryEnum.values();
for (CountryEnum countryEnum : myArray) {
if (index == countryEnum.getRetCode()) {
return countryEnum;
}
}
return null;
}
}
package com.hzz.cecdata.lock.count_down_latch;
import java.util.concurrent.CountDownLatch;
/**
* 秦灭六国才会统一
* CountDownLatch : 让一些线程阻塞直到另一个线程完成一系列操作后才被唤醒
* CountDownLatch主要有两个方法,当一个或多个线程await方法时,调用线程会被阻塞。
* 其他线程调用countDown方法时会将计数器减1(调用countDown方法的线程不会被阻塞),
* 当计数器变为0时,因调用await()方法被阻塞的线程会被唤醒,继续执行。
*/
public class CountDownLatchDemo2 {
public static void main(String[] args) {
closeDoor();
}
public static void closeDoor() {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t 国被灭");
countDownLatch.countDown();
}
}, CountryEnum.foreachCountryEnum(i).getRetMessage()).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t 秦国一统天下");
//如何调用枚举
System.out.println(CountryEnum.FOUR);
}
}
线程死锁
什么是线程死锁?
多线程虽然提高了系统的处理能力,但是,并发执行也带来了新的问题—死锁
所谓死锁指:多个线程因竞争资源而造成一种僵局,若无外力左右,进程无法继续推进
死锁产生的必要条件?
死锁原因:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
死锁代码示例:前提是请求的资源是共享资源
package com.cecdata.test;
public class ThreadDeadLock implements Runnable {
private int flag;//决定线程走向的标记
private static Object object1 = new Object();//锁对象1
private static Object object2 = new Object();//锁对象2
public ThreadDeadLock(int flag){
this.flag =flag;
}
@Override
public void run() {
if (flag == 1) {
//线程1执行
synchronized (object1){
System.out.println( Thread.currentThread().getName()+"已获取到资源obj1,请求obj2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2){
System.out.println(Thread.currentThread().getName()+"已经获取到obj1和obj2");
}
}
}else {
//线程2执行
synchronized (object2){
System.out.println( Thread.currentThread().getName()+"已获取到资源obj2,请求obj1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object1){
System.out.println(Thread.currentThread().getName()+"已经获取到obj1和obj2");
}
}
}
}
public static void main(String[] args) {
//创建两个ThreadDeadLock实例:falg=1;flag =2;
ThreadDeadLock threadDeadLock1 = new ThreadDeadLock(1);
ThreadDeadLock threadDeadLock2= new ThreadDeadLock(2);
//创建两个线程执行创建两个ThreadDeadLock实例
Thread thread1 = new Thread(threadDeadLock1,"threadDeadLock1");
Thread thread2 = new Thread(threadDeadLock2,"threadDeadLock2");
thread1.start();
thread2.start();
}
}
打印结果:
避免死锁的方法:
1.加锁顺序:当多个线程要相同的一些锁,但是按照不同的顺序加锁,死锁的情况发生率较高,如果,程序运行能确保所有线程都是按照相同的顺序去获得锁,那么死锁就不会发生。
2.加锁时限:加一个超时时间,若一个线程没有在给定的时间内成功获取所需的锁,则进行回退操作,并释放自己本身所持有的锁,一段随机时间之后,重新去获取锁。
3.死锁检测:死锁检测,每当线程去获取锁的时候,会在线程和锁相关的数据结构中将其记下,除此之外,每当线程请求锁,都需要记录在数据结构中。死锁检测是一个死锁避免机制。他主要针对的时那些不可能实现按序加锁并且锁超时也不可行的应用场景
线程通信
为何要进行线程通信?
多个线程并发运行时,在默认情况下CPU是随机切换线程的,有时我们希望CPU按我们自己的规律执行线程,此时需要线程直接协调通信。
线程通信的常用集中方式:
1.休眠唤醒方式
Object的notify ,notifyAll , wait
例子:利用线程打印10以内的奇偶数
package com.cecdata.test;
//使用object的notify();wait()唤醒和等待方法时必须放在synchronized方法和synchronized代码快中
public class OddEvenDemo {
private int i = 0;//要打印的数
private Object object = new Object();
//奇数打印方法,由奇数线程调用
public void odd() {
//判断i是否小于10,小于10的进行打印
while (i < 10) {
synchronized (object) {
if (i % 2 == 1) {
System.out.println("奇数:" + i);
i++;
//唤醒偶数线程打印
object.notify();
} else {
try {
//等待偶数线程打印
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//偶数打印方法,由偶数线程调用
public void even() {
while (i < 10) {
synchronized (object) {
//判断i是否小于10,小于10的进行打印
if (i % 2 == 0) {
System.out.println("偶数:" + i);
i++;
//唤醒奇数线程打印
object.notify();
} else {
try {
//等待奇数线程打印
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
final OddEvenDemo oddEvenDemo = new OddEvenDemo();
//开启奇数线程打印
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
oddEvenDemo.odd();
}
});
//开启偶数线程打印
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
oddEvenDemo.even();
}
});
//开启线程
thread1.start();
thread2.start();
}
}
使用condition实现线程等待唤醒方法
package com.cecdata.test.threadwait;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// condition的await()和lock(互斥锁/共享锁)配合使用
public class OddEvenDemo2 {
private int i = 0;//要打印的数
private Lock lock = new ReentrantLock();
//创建condition对象
private Condition condition = lock.newCondition();
//奇数打印方法,由奇数线程调用
public void odd() {
//判断i是否小于10,小于10的进行打印
while (i < 10) {
//加锁
lock.lock();
try {
if (i % 2 == 1) {
System.out.println("奇数:" + i);
i++;
//唤醒偶数线程打印
condition.signal();
} else {
try {
//等待偶数线程打印
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
//释放锁
lock.unlock();
}
}
}
//偶数打印方法,由偶数线程调用
public void even() {
while (i < 10) {
//加锁
lock.lock();
try{
//判断i是否小于10,小于10的进行打印
if (i % 2 == 0) {
System.out.println("偶数:" + i);
i++;
//唤醒奇数线程打印
condition.signal();
} else {
try {
//等待奇数线程打印
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
//释放锁
lock.unlock();
}
}
}
public static void main(String[] args) {
final OddEvenDemo2 oddEvenDemo2 = new OddEvenDemo2();
//开启奇数线程打印
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
oddEvenDemo2.odd();
}
});
//开启偶数线程打印
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
oddEvenDemo2.even();
}
});
//开启线程
thread1.start();
thread2.start();
}
}
condition和object 进行线程唤醒,等待方法总结
使用object的notify();wait()唤醒和等待方法时必须放在synchronized方法和synchronized代码快中(同步锁)
object.wait()必须使用notify()方法唤醒
condition的await()和lock(互斥锁/共享锁)配合使用
condition的await()必须 signal()唤醒
java执行流程图:
java内存模型
jvm内存共分为:虚拟机栈,堆,方法区,程序计数器,本地方法栈五个部分,结构图如下:
pc程序计算器
java栈 JAVA Stask(虚拟机栈 JVM Stask)
方法区Method Area
常量池
java内存工作模型图
多线程需要满足以下特性
1.原子性
原子性,指一个操作或者多个操作,要么全部执行,要么全部不执行。
2.可见性
可见性是指当多个线程访问同一变量时,一个线程修改了这个变量的值,其他线程能够立刻看到修改的值。显然,对於单线程,可见性不存在
3.有序性
程序执行得顺序按照代码得先后顺序执行的。
---------------------------------------------------------
多线程控制类
为了保持多线程的三个特性,jav引入多线程机制,介绍常用的几种:
1.ThreadLocal:线程本地变量
作用:
常用方法::副本创建方法,get:获取副本方法,set:设置副本方法
模拟线程转账:待续
2.原子类:保证变量原子操作
3.Lock类:保证线程有序性
lock接口类关系图:
可重入锁
可重入锁:即线程可以进入它已经拥有的锁的同步代码块,则可以自动获取锁
不可重入锁:即线程请求它已经拥有的锁时会阻塞
package com.cecdata.test;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
for (int i = 0; i < 10; i++) {
lock.lock();
System.out.println("加锁次数:" + i );
}
for (int i = 0; i < 10; i++) {
try {
System.out.println("解锁次数:" + i);
} finally {
lock.unlock();
}
}
}
}
读写锁
读写锁,即可以同时读,读的时候不能写;不能同时写;写的时候不能读
package com.cecdata.test;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
private Map<String, String> map = new HashMap<>();//操作的map对象
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();//读操作锁
private ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();//写操作锁
public String get(String key) {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "读操作已加锁,开始读操作");
Thread.sleep(3000);
return map.get(key);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
System.out.println(Thread.currentThread().getName() + "读操作已解锁,读操作结束");
readLock.unlock();
}
}
public void put(String key, String value) {
writeLock.lock();
try {
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写操作已加锁,开始写操作");
Thread.sleep(3000);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "写操作已解锁,写操作结束");
writeLock.unlock();
}
}
public static void main(String[] args) {
final ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
readWriteLockDemo.put("key1", "value1");
new Thread("读线程1"){
public void run(){
System.out.println(readWriteLockDemo.get("key1"));
}
}.start();
new Thread("读线程2"){
public void run(){
System.out.println(readWriteLockDemo.get("key1"));
}
}.start();
new Thread("读线程3"){
public void run(){
System.out.println(readWriteLockDemo.get("key1"));
}
}.start();
}
}
4.Volatile关键字:保证线程变量可见性
四种常用的线程池
示例
package com.cecdata.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index =i;
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
}
}