多线程
指从软件或者硬件上实现多个线程并发执行的技术
具有多线程能力的计算机因有硬件支持而能够再同一时间执行多个线程,提升性能
并发和并行
并行:在同一时刻,有多个指令在多个CPU同时执行
并发:在同一时刻,有多个指令在单个CPU上交替执行
进程和线程
- 进程:是正在运行的软件
- . 线程:是线程中的单个顺序控制流,是一条执行路径
单线程和多线程
- 单线程:一个进程如果只有一条执行路径,则称为单线程程序
- .多线程:一个进程如果有多条执行路径,则成为多线程程序
多线程的实现
- 继承Thread类的方式进行实现
- 实现Runnable接口的方式进行实现
- 利用Callable和Future接口方式实现
继承Thread类的方式进行实现
package cn.cdw.demo;
/**
* @author DW-CHEN
* Thread
* 多线程
*/
public class Demo94 {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
}
}
class MyThread extends Thread{//多线程
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(i);
}
}
}
实现Runnable接口的方式进行实现
package cn.cdw.demo;
/**
* @author DW-CHEN
* 多线程
* Runnable
*/
public class Demo95 {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyThread1());
Thread thread2 = new Thread(new MyThread1());
thread1.start();
thread2.start();
}
}
class MyThread1 implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(i);
}
}
}
利用Callable和Future接口方式实现
package cn.cdw.demo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author DW-CHEN
* 多线程
* Callable
*/
public class Demo96 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask1 = new FutureTask<>(new MyThread2());
FutureTask futureTask2 = new FutureTask<>(new MyThread2());
Thread thread1 = new Thread(futureTask1);
Thread thread2 = new Thread(futureTask2);
thread1.start();
System.out.println(futureTask1.get());
thread2.start();
System.out.println(futureTask2.get());
}
}
class MyThread2 implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 1; i <= 100; i++) {
System.out.println(i);
}
return "线程执行完了";//返回线程执行完毕后的的结果
}
}
run()方法和start()方法的区别
方法名 | 说明 |
---|---|
run() | 封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程 |
start() | 启动线程,然后有JVM调用此线程的run()方法 |
三种实现多线程方式对比
优点 | 缺点 | |
---|---|---|
实现Runnable/Callable接口 | 扩展性强,实现该接口的同时,还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 扩展性较差,不能再继承其他的类 |
获取和设置线程的名称
方法名 | 说明 |
---|---|
String getName() | 返回此线程的名称 |
void setName(String name) | 将此线程中的名称改为等于参数name |
还可以通过构造方法来设置线程的名称
获取线程默认名称
package cn.cdw.demo;
/**
* @author DW-CHEN
* 获取线程默认名称
*/
public class Demo97 {
public static void main(String[] args) {
MyThread3 t1 = new MyThread3();
MyThread3 t2 = new MyThread3();
t1.start();
t2.start();
}
}
class MyThread3 extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + " "+i);
}
}
}
设置线程名称
package cn.cdw.demo;
/**
* @author DW-CHEN
* 设置线程名称
*/
public class Demo98 {
public static void main(String[] args) {
MyThread4 t1 = new MyThread4();
MyThread4 t2 = new MyThread4();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
class MyThread4 extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + " "+i);
}
}
}
通过构造方法设置线程名称
package cn.cdw.demo;
/**
* @author DW-CHEN
* 通过构造方法设置线程名称
*/
public class Demo99 {
public static void main(String[] args) {
MyThread5 t1 = new MyThread5("线程1");
MyThread5 t2 = new MyThread5("线程2");
t1.start();
t2.start();
}
}
class MyThread5 extends Thread{
public MyThread5() {
}
public MyThread5(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + " "+i);
}
}
}
获取当前线程对象,线程休眠
方法名 | 说明 |
---|---|
public static Thread currentThread() | 返回对当前正在执行的线程的引用 |
public static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
获取当前线程 对象
package cn.cdw.demo;
/**
* @author DW-CHEN
* 获取当前线程 对象
* 例如:当不能直接使用Thread获取线程名称时,可以向获取当前线程对象,然后再获取到线程名称
*/
public class Demo100 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread6());
Thread t2 = new Thread(new MyThread6());
t1.start();
t2.start();
}
}
class MyThread6 implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
线程睡眠
package cn.cdw.demo;
/**
* @author DW-CHEN
* 线程睡眠
*/
public class Demo101 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread7());
t1.start();
}
}
class MyThread7 implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
try {
Thread.sleep(1000); //一秒打印一次
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程优先级
方法名 | 说明 |
---|---|
public final void setPriority(int newPriority) | 设置线程的优先级 |
public final int getPriority() | 获取线程的优先级 |
多线程的优先级(1-10),默认优先级都是5
package cn.cdw.demo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author DW-CHEN
* 多线程的优先级(1-10),默认优先级都是5
* 注意:优先级只是优先抢占CPU,并不一定能最先执行
*/
public class Demo102 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask1 = new FutureTask<>(new MyThread8());
FutureTask<String> futureTask2 = new FutureTask<>(new MyThread8());
Thread t1 = new Thread(futureTask1);
Thread t2 = new Thread(futureTask2);
t1.setName("蚂蚁");
t1.setPriority(10);
t2.setName("蜗牛");
t2.setPriority(1);
t1.start();
System.out.println(futureTask1.get());
t2.start();
System.out.println(futureTask2.get());
}
}
class MyThread8 implements Callable<String>{
@Override
public String call() throws Exception {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + " "+i);
}
return "线程执行完毕";
}
}
后台线程/守护线程
方法名 | 说明 |
---|---|
public final void setDaemon(boolean on) | 设置为守护线程 |
package cn.cdw.demo;
/**
* @author DW-CHEN
* 守护线程
*/
public class Demo103 {
public static void main(String[] args) {
MyThread9 t1 = new MyThread9();
MyThread10 t2 = new MyThread10();
t2.setDaemon(true);//开启线程守护,就是当普通线程执行完之后,那么守护线程也就没有必要继续运行下去了
t1.start();
t2.start();
}
}
class MyThread9 extends Thread{
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(getName() + " " + i);
}
}
}
class MyThread10 extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + " " + i);
}
}
}
线程安全问题
- java提供了同步代码块的方式把多条语句操作共享数据的代码锁起来,让任意时刻只能有一个线程执行
卖票问题
package cn.cdw.demo;
/**
* @author DW-CHEN
* 线程安全问题
* 模拟三个窗口卖票,出现票的重复和出现负数的票问题
*/
public class Demo104 {
public static void main(String[] args) {
MyThread11 ticket = new MyThread11();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class MyThread11 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket == 0) {
break;//没票了结束循环
}else {
ticket --;
System.out.println(Thread.currentThread().getName() + "正在卖票,还剩:"+ticket+ "张票");
}
}
}
}
同步代码块
锁多条语句操作共享数据,可以使用同步代码块
- 格式:
synchronized(){ 多条语句操作共享数据的代码 } - 默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭
- 当线程执行完出来了,锁才会自动打开
同步代码块解决多个线程操作同一个共享数据问题
package cn.cdw.demo;
/**
* @author DW-CHEN
* 线程安全问题
* 同步代码块
*/
public class Demo105 {
public static void main(String[] args) {
MyThread12 ticket = new MyThread12();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class MyThread12 implements Runnable {
private int ticket = 100;
private Object object = new Object(); //锁对象要唯一
@Override
public void run() {
while (true) {
synchronized (object){
if (ticket == 0) {
break;//没票了,结束循环
}else{
ticket --;
System.out.println(Thread.currentThread().getName() + "正在卖票,还剩:"+ticket+ "张票");
}
}
}
}
}
同步的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
同步方法
就是把synchronized 关键字加到方法上
- 格式:
修饰符 synchronized 返回值类型 方法名(方法参数){ }
同步方法解决多个线程操作同一个共享数据问题
package cn.cdw.demo;
/**
* @author DW-CHEN
* 同步方法
*/
public class Demo106 {
public static void main(String[] args) {
MyThread13 ticket = new MyThread13();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
class MyThread13 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true) {
if ("窗口一".equals(Thread.currentThread().getName())) {//调用同步方法
boolean result = synchronizedMethod();
if(result){
break;
}
}
if ("窗口二".equals(Thread.currentThread().getName())) {//同步代码块
synchronized (this) { //同步方法的锁对象是this
if (ticket == 0) {
break;
}else {
ticket --;
System.out.println(Thread.currentThread().getName() + "正在卖票,还剩:"+ticket+ "张票");
}
}
}
}
}
public synchronized boolean synchronizedMethod(){//同步方法
if (ticket == 0) {
return true;
}else {
ticket --;
System.out.println(Thread.currentThread().getName() + "正在卖票,还剩:"+ticket+ "张票");
return false;
}
}
}
同步代码块和同步方法的区别
- 同步代码块可以锁住指定代码,同步方法是锁住方法中的所有代码
- 同步代码块可以指定锁对象,同步方法不能指定锁对象
同步方法的锁对象是:this
同步静态方法
就是把 synchronized 关键字加到静态方法上
同步静态方法的锁对象是:类名.class
静态同步方法解决多个线程操作同一个共享数据问题
package cn.cdw.demo;
/**
* @author DW-CHEN
* 静态同步方法
*/
public class Demo107 {
public static void main(String[] args) {
MyThread13 ticket = new MyThread13();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
t1.setName("窗口一");
t2.setName("窗口二");
t1.start();
t2.start();
}
}
class MyThread14 implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while (true) {
if ("窗口一".equals(Thread.currentThread().getName())) { //调用静态同步方法
boolean result = staticSynchronizedMethod();
if (result) {
break;
}
}
if ("窗口二".equals(Thread.currentThread().getName())) { //静态代码块
synchronized (MyThread13.class) {//静态同步方法的锁对象是 类名.class
if (ticket == 0) {
break;
}else {
ticket --;
System.out.println(Thread.currentThread().getName() + "正在卖票,还剩:"+ticket+ "张票");
}
}
}
}
}
public static synchronized boolean staticSynchronizedMethod() {//静态同步方法
if (ticket == 0) {
return true;
}else {
ticket --;
System.out.println(Thread.currentThread().getName() + "正在卖票,还剩:"+ticket+ "张票");
return false;
}
}
}
Lock锁
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock是接口,通过多态它的实现类ReentrantLock来实例化
Lock的获得锁和 释放锁方法
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
Lock锁解决多个线程操作同一个共享数据问题
package cn.cdw.demo;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author DW-CHEN
* Lock锁
*/
public class Demo108 {
public static void main(String[] args) {
MyThread15 ticket = new MyThread15();
Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
class MyThread15 implements Runnable {
private int ticket = 100;
private ReentrantLock reentrantLock = new ReentrantLock();//Lock
@Override
public void run() {
while (true) {
try {
reentrantLock.lock();//加锁
if (ticket == 0) {
break;
}else {
ticket --;
System.out.println(Thread.currentThread().getName() + "正在卖票,还剩:"+ticket+ "张票");
}
}catch (Exception e){
e.printStackTrace();
}finally {
reentrantLock.unlock();//释放锁
}
}
}
}
死锁
线程死锁是指由两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
package cn.cdw.demo;
/**
* @author DW-CHEN
* 死锁
*/
public class Demo109 {
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
new Thread(() -> {
while (true) {
synchronized (a){
synchronized (b){
System.out.println("小明走的路线");
}
}
}
}).start();
new Thread(() -> {
while (true) {
synchronized (b){
synchronized (a){
System.out.println("小李走的路线");
}
}
}
}).start();
}
}
生产者和消费者
生产者模式是一个十分经典的多线程协作模式
等待和唤醒的方法
方法名 | 说明 |
---|---|
void wait | 导致当前线程等待,直到另一个线程调用该对象的notify()方法 |
void notify() | 唤醒正在等待监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
厨师和吃货,厨师生产一个包子,吃货吃一个包子
package cn.cdw.demo;
/**
* @author DW-CHEN
* 生产者和消费者
* 厨师和吃货,厨师生产一个包子,吃货吃一个包子
*/
public class Demo110 {
public static void main(String[] args) {
Thread cooker = new Thread(new Cooker());
Thread eat = new Thread(new Eat());
cooker.start();
eat.start();
}
}
//模拟桌子
class Desk{
public static boolean flag = false;//默认桌子上没有包子
public static int count = 10;//默认厨师只可以做10个包子
public static final Object lock = new Object();//锁对象,保证唯一
}
//厨师(生产者)
class Cooker implements Runnable{
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
}else {
if (!Desk.flag) {
System.out.println("厨师生产了一个包子");
Desk.flag = true;//让桌子上显示有包子了
Desk.lock.notifyAll();//唤醒吃货吃包子
}else {
try {
Desk.lock.wait();//有包子,就等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
//吃货(消费者)
class Eat implements Runnable{
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
}else {
if (Desk.flag) {
System.out.println("吃货再吃包子");
Desk.count --;
Desk.flag = false; //让桌子上显示没有包子了
Desk.lock.notifyAll();//唤醒厨师做包子
}else {
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
阻塞队列,BlockingQueue的核心方法
方法名 | 说明 |
---|---|
put(anObject) | 将参数放入队列,如果放不进去会阻塞 |
take() | 取出第一个数据,取不出来会阻塞 |
package cn.cdw.demo;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @author DW-CHEN
* 阻塞队列
*/
public class Demo111 {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);//参数表示容量,这里表示创建阻塞对象对象,容量为1
arrayBlockingQueue.put("小小");//存储元素
String take = arrayBlockingQueue.take();//取出元素
System.out.println(take);
System.out.println(arrayBlockingQueue.take());//里面只有一个元素,所有程序会一直再这等着
}
}
阻塞队列实现等待唤醒机制
- 常见BlockinQueue
1.ArrayBlockingQueue:底层是数组,有界
2.LinkedBlockingQueue:底层是列表,无界。但不是真正的无界,最大为int的最大值
使用阻塞队列实现等待唤醒机制
package cn.cdw.demo;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @author DW-CHEN
* 使用阻塞队列实现等待唤醒机制
* 例如:创建一个阻塞队列的对象容量为1,然后厨师做好一个包子,吃货吃一个包子,如果厨师没做好包子,那么吃货只有等待
*/
public class Demo112 {
public static void main(String[] args) {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(1);//容量为1
Thread t1 = new Thread(new Cooker2(arrayBlockingQueue));
Thread t2 = new Thread(new Eat2(arrayBlockingQueue));
t1.start();
t2.start();
}
}
//厨师
class Cooker2 implements Runnable {
private ArrayBlockingQueue<String> arrayBlockingQueue;
public Cooker2(ArrayBlockingQueue<String> arrayBlockingQueue) {//构造方法里的参数为阻塞队列
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
while (true) {
try {
arrayBlockingQueue.put("包子");
System.out.println("厨师做好了一个包子");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//吃货
class Eat2 implements Runnable {
private ArrayBlockingQueue<String> arrayBlockingQueue;
public Eat2(ArrayBlockingQueue<String> arrayBlockingQueue) {//构造方法里的参数为阻塞队列
this.arrayBlockingQueue = arrayBlockingQueue;
}
@Override
public void run() {
while (true) {
try {
String take = arrayBlockingQueue.take();
System.out.println("吃货吃了厨师做好的" + take);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
虚拟机中线程的6中状态
1.新建状态(NEW)----------------->创建线程对象
2.就绪状态(RUNNABLE)----------------->start()方法
3.阻塞状态(BLOCKED)----------------->无法获得锁对象
4.等待状态(WAITING)----------------->wait()方法
5.计时状态(TIMED_WAITING)----------------->sleep()方法
6.结束状态(TERMINATED)----------------->全部代码执行完毕
线程池
创建一个池子,池子是空的,有任务需要执行时,才会创建线程对,当任务执行完毕,线程对象归还给池子
Executors
方法名 | 说明 |
---|---|
static ExecutorService newCachedThreadPool() | 创建一个默认的线程池 |
static newFixedThreadPool(int nThreads) | 创建一个指定最多线程数量的线程池 |
方法名 | 说明 |
---|---|
submit() | 池子会自动的帮助我们创建对象 ,任务执行完毕,也会自动把线程对象归还给池子 |
shutdown() | 所有任务全部执行完毕,关闭池子 |
newCachedThreadPool()
package cn.cdw.demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author DW-CHEN
* 线程池
* Executors
*/
public class Demo113 {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();//创建一个默认的线程池,里面默认为空的,默认可以容纳int类型的最大值
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "线程执行了");
});
// Thread.sleep(1000);
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "线程执行了");
});
executorService.shutdown();//关闭线程
}
}
newFixedThreadPool(int nThreads)
package cn.cdw.demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author DW-CHEN
* 线程池
* Executors
*/
public class Demo114 {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);//注意:创建出来的线程池默认也是空的,参数表示池子中最多能存储的线程数
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "线程执行了");
});
executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + "线程执行了");
});
executorService.shutdown();//关闭线程
}
}
ThreadPoolExecutor
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
参数一:核心线程数量------------------------------->不能小于0
参数二:最大线程数---------------------------------->不能小于等于0,最大数量>=核心线程数量
参数三:空闲线程最大存活时间------------------->不能小于0
参数四:时间单位------------------------------------->时间单位
参数五:任务队列------------------------------------->不能为null
参数六:创建线程工厂------------------------------->不能为null
参数七:任务的拒绝策略---------------------------->不能为null
任务拒绝策略
拒绝策略 | 说明 |
---|---|
ThreadPoolExecutor.AborPoliy | 丢弃任务抛出RdjectedExecutionExeption异常,是默认策略 |
ThreadPoolExecutor.DiscardPoliy | 丢弃任务,但是不抛出异常,不推荐这种做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务,然后把当前的任务加入到队列中 |
ThreadPoolExecutor,CallerRunsPolicy | 调用任务的run()方法,绕过线程池直接执行 |
package cn.cdw.demo;
import java.util.concurrent.*;
/**
* @author DW-CHEN
* 线程池
* ThreadPoolExecutor
*/
public class Demo115 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "线程执行了");
});
threadPoolExecutor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "线程执行了");
});
threadPoolExecutor.shutdown();
}
}
volatile
强制线程每次在使用的时候,都会去看一些共享区最新的值
volatile解决当两个或者多个线程去操作同一个共享数据时的问题
package cn.cdw.demo;
/**
* @author DW-CHEN
* volatile
* 当两个或者多个线程去操作同一个共享数据时的问题
* 当线程A将数据改变之后,线程B并不知道,还是使用之前的旧数据就会出错
*/
public class Demo116 {
public static void main(String[] args) {
Thread a = new Thread(new TheadA());
Thread b = new Thread(new TheadB());
a.start();
b.start();
}
}
//共享数据
class Num{
public volatile static int num = 10; //volatile关键字,强制线程每次使用的时候,都会去看一下共享区的最新数据
}
class TheadA implements Runnable{
@Override
public void run() {
while (Num.num == 10) {//注意观察控制台,当没有加volatile关键词时,程序一直是再这里循环的,没有跳出去
}
System.out.println("数据已经发生改变了");
}
}
class TheadB implements Runnable{//线程B把数据改变了
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Num.num = 5;
}
}
synchronized解决数据共享问题
package cn.cdw.demo;
/**
* @author DW-CHEN
* synchronized解决数据共享问题
*/
public class Demo117 {
public static void main(String[] args) {
Thread c = new Thread(new C());
Thread d = new Thread(new D());
c.start();
d.start();
}
}
//共享数据
class Num1 {
public static int num = 10;
public static final Object lock = new Object();
}
class C implements Runnable{
@Override
public void run() {
while (true) {
synchronized (Num1.lock) {
if (Num1.num == 10) {
}else {
System.out.println("数据已经发生变化");
break;
}
}
}
}
}
class D implements Runnable {
@Override
public void run() {
synchronized (Num1.lock) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Num1.num = 5;
}
}
}
原子性
所谓的原子性是指一次操作或多次操作中,要么所有的操作全部都得到了执行并且不会搜到任何因素的干扰中断,要么所有的操作都不执行,多个操作时一个不可分割的整体
volatile关键字只能保证每次在使用共享数据的时候时最新指,但不能保证原子性
count ++ ,volatile不是一个原子性操作
package cn.cdw.demo;
/**
* @author DW-CHEN
* 原子性
* count ++ ,volatile不是一个原子性操作
*/
public class Demo118 {
public static void main(String[] args) {
MyThread16 myThread16 = new MyThread16();
for (int i = 0; i < 10; i++) {
new Thread(myThread16).start();
}
}
}
class MyThread16 implements Runnable{
private volatile int count = 0;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
count ++;
System.out.println("第" + count + "个HelloWord");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
使用synchronized解决原子性(速度比较慢)
package cn.cdw.demo;
/**
* @author DW-CHEN
* 使用synchronized解决原子性(速度比较慢)
*/
public class Demo119 {
public static void main(String[] args) {
MyThread17 myThread17 = new MyThread17();
for (int i = 0; i < 10; i++) {
new Thread(myThread17).start();
}
}
}
class MyThread17 implements Runnable {
private int count = 0;
private Object lock = new Object();
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
count++;
System.out.println("第" + count + "个HelloWord");
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
AtomicInteger
原理:自旋锁 + CAS算法
构造方法
方法名 | 说明 |
---|---|
public AtomiclInteger() | 初始化一个默认值为0的原子型Integer |
public AtomicInteger(int initialValue) | 初始化一个指定值的原子型Integer |
常用方法
方法名 | 说明 |
---|---|
int get() | 获得值 |
int getAndIncrement() | 以原子方式将当前值加1,注意,这里返回的是自增前的值 |
int incrementAndGet() | 以原子方式将当前值加1,注意,这里返回的是自增后的值 |
int addAndGet(int data) | 以原子方式将输入的数值与实例中的值(Atomicinteger 里的 value)相加,并放回结果 |
int getAndSet(int value) | 以原子方式设置为newValue的值,并返回旧值 |
AtomicInteger解决原子性(速度快)
package cn.cdw.demo;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author DW-CHEN
* AtomicInteger解决原子性(速度快)
*/
public class Demo120 {
public static void main(String[] args) {
MyThread18 myThread18 = new MyThread18();
for (int i = 0; i < 10; i++) {
new Thread(myThread18).start();
}
}
}
class MyThread18 implements Runnable {
AtomicInteger atomicInteger = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 10; i++) {
int count = atomicInteger.incrementAndGet();
System.out.println("第" + count + "个HelloWord");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
synchronized和CAS的区别
相同点:在多线程情况下,都可以保证共享数据的安全性
不同点:synchronized是悲观锁(每次操作共享数据之前都会上锁)
CAS是乐观锁(只有在修改共享数据的时候才会检查一下别人有没有修改数据,如果修改了,就会才直接去修改共享数据的值)
Hashtable
HashMap是线程不安全的(多线程环境下可能出现问题),为了保证数据的安全性可以使用Hashtable,但是效率低
ConcurrentHashMap
线程下,HashMap可能会出现问题,使用并发工具类ConcurrentHashMap,速度快
package cn.cdw.demo;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author DW-CHEN
* 多线程下,HashMap可能会出现问题,使用并发工具类ConcurrentHashMap,速度快
* 也可以使用HashTable,因为它是synchronized控制线程安全的,但是慢
*/
public class Demo121 {
public static void main(String[] args) throws InterruptedException {
ConcurrentHashMap<String,Integer> hashMap = new ConcurrentHashMap();
new Thread(() -> {
for (int i = 0; i < 25; i++) {
hashMap.put("s" + i, i);
}
}).start();
new Thread(() -> {
for (int i = 25; i < 51; i++) {
hashMap.put("s" + i, i);
}
}).start();
Thread.sleep(1000);
for (int i = 0; i < 51; i++) {
int value = hashMap.get("s" + i);
System.out.println(value);
}
}
}
CountDownLatch
然某一条线程等待其他线程执行完毕后再执行
方法名 | 说明 |
---|---|
public CountDownLatch(int count) | 参数传递线程数,表示等待线程数量 |
public void await() | 然线程等待 |
public void countDown() | 当前线程执行完毕 |
老板等3个客人吃包子,3个客人吃完后老板再来收拾桌子
package cn.cdw.demo;
import java.util.concurrent.CountDownLatch;
/**
* @author DW-CHEN
* 并发工具类 CountDownLatch
* 使用场景:让某一条线程等待其他线程执行完毕后再执行
*
* 例如:老板等3个客人吃包子,3个客人吃完后老板再来收拾桌子
*/
public class Demo122 {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);//需要等待3个线程
Thread boos = new Thread(new Boos(countDownLatch));
boos.start();
Thread p1 = new Thread(new People1(countDownLatch));
Thread p2 = new Thread(new People2(countDownLatch));
Thread p3 = new Thread(new People3(countDownLatch));
p1.start();
p2.start();
p3.start();
}
}
//老板
class Boos implements Runnable{
private CountDownLatch countDownLatch;
public Boos(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
countDownLatch.await();//等待3个客人吃完包子
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3个客人都吃完包子了,老板来收拾桌子了");
}
}
//客人1
class People1 implements Runnable{
private CountDownLatch countDownLatch;
public People1(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("客人1了吃了第"+(i+1)+"个包子");
}
countDownLatch.countDown();//告诉老板,我已经吃完了
}
}
//客人2
class People2 implements Runnable{
private CountDownLatch countDownLatch;
public People2(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
for (int i = 0; i < 6; i++) {
System.out.println("客人2了吃了第"+(i+1)+"个包子");
}
countDownLatch.countDown();//告诉老板,我已经吃完了
}
}
//客人3
class People3 implements Runnable{
private CountDownLatch countDownLatch;
public People3(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("客人3了吃了第"+(i+1)+"个包子");
}
countDownLatch.countDown();//告诉老板,我已经吃完了
}
}
Semaphore
可以控制访问特点资源的线程数量
方法名 | 说明 |
---|---|
void acquire() | 发通行证 |
void release() | 收回通行证 |
过桥,一次只能过2辆车
package cn.cdw.demo;
import java.util.concurrent.Semaphore;
/**
* @author DW-CHEN
* 并发工具类 Semaphore
* 使用场景:可以控制访问特定资源的线程数量
*
* 例如:过桥,一次只能过2辆车
*/
public class Demo123 {
public static void main(String[] args) {
MyThread19 m = new MyThread19();
for (int i = 0; i < 10; i++) {//模拟有10辆车要过桥
new Thread(m).start();
}
}
}
class MyThread19 implements Runnable{
private Semaphore semaphore = new Semaphore(2);//里面的参数就是最多多少个线程同时执行
@Override
public void run() {
try {
semaphore.acquire();//获取通行证
System.out.println("获得了通行证,开始行驶了");
Thread.sleep(2000);
semaphore.release();//归还通行证
System.out.println("归还通行证");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}