多线程,线程同步和锁
- 1:线程的创建
-
- 1-1:继承Thred类
- 1-2:实现Runnable接口
- 1-3:实现Callable<>接口,有返回值的线程
- 2:线程的其他操作
-
- 2-1:线程停止
- 2-2:线程礼让yield
- 2-3:线程强制执行join
- 2-4:查看线程状态
- 2-5:线程优先级priority
- 2-6:守护线程deamon
- 4:并发与锁
-
- 4-1:初识并发问题
- 4-2:synchronized
- 4-3:死锁
- 5:线程通信wait和notify()
-
- 5-1:管程法
- 5-2:信号灯法
- 6:线程池
-
- 6-1:线程池例子
- 6-2:自定义线程池(手动创建线程池,效果会更好)
1:线程的创建
1-1:继承Thred类
package com.lingaolu;
public class CreateThread extends Thread{
@Override
public void run() {
System.out.println("子线程执行");
}
public static void main(String[] args) {
System.out.println(1);
new CreateThread().start();
System.out.println(2);
}
}
1-2:实现Runnable接口
package com.lingaolu;
public class CreateThread implements Runnable{
@Override
public void run() {
System.out.println("子线程执行");
}
public static void main(String[] args) {
System.out.println(1);
new Thread(new CreateThread()).start();
System.out.println(2);
}
}
或者使用lambda表达式
package com.lingaolu;
public class CreateThread{
public static void main(String[] args) {
System.out.println(1);
new Thread(()->System.out.println("子线程执行")).start();
System.out.println(2);
}
}
1-3:实现Callable<>接口,有返回值的线程
package com.lingaolu;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CreateThread implements Callable<String> {
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println(1);
FutureTask<String> futureTask = new FutureTask<>(new CreateThread());
new Thread(futureTask).start();
// 这个get方法是阻塞的
System.out.println(futureTask.get());
System.out.println(2);
}
@Override
public String call(){
System.out.println("子线程执行");
return "OK";
}
}
2:线程的其他操作
线程有5个状态,如下
2-1:线程停止
我们不推荐线程使用**interrupt(),stop(),destroy()**等方法停止线程,而是自己写一个标记位来作为线程的终止变量
package com.lingaolu;
public class CreateThread implements Runnable{
private boolean flag = true;
@Override
public void run() {
while (flag){
System.out.println("线程执行");
}
}
// 调用方法,改变标志位的值来停止线程
public void stop(){
this.flag = false;
}
}
2-2:线程礼让yield
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功,看cpu心情
package com.lingaolu;
public class CreateThread implements Runnable{
public static void main(String[] args) {
System.out.println(1);
new Thread(new CreateThread(),"线程1").start();
new Thread(new CreateThread(),"线程2").start();
System.out.println(2);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行开始");
// 线程礼让
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程执结束");
}
}
2-3:线程强制执行join
join可以让线程强制执行
package com.lingaolu;
public class CreateThread implements Runnable{
public static void main(String[] args) throws InterruptedException {
System.out.println(1);
Thread thread1 = new Thread(new CreateThread());
thread1.start();
for (int i = 1; i <= 5; i++) {
if(i==3){
// 当i=3时,强制让thread1执行
thread1.join();
}
System.out.println("主线程"+i);
}
System.out.println(2);
}
@Override
public void run() {
System.out.println("子线程执行......");
}
}
2-4:查看线程状态
package com.lingaolu;
import lombok.SneakyThrows;
public class CreateThread implements Runnable{
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new CreateThread());
Thread.State state = thread1.getState();
System.out.println("线程创建状态"+state);
thread1.start();
state = thread1.getState();
System.out.println("线程启动状态"+state);
while(state != Thread.State.TERMINATED){
if(state == Thread.State.TIMED_WAITING){
System.out.println("线程等待状态"+state);
}
Thread.sleep(100);
state = thread1.getState();
}
state = thread1.getState();
System.out.println("线程结束状态"+state);
}
@SneakyThrows
@Override
public void run() {
Thread.sleep(100);
System.out.println("子线程执行状态......"+Thread.currentThread().getState());
System.out.println("子线程执行......");
}
}
运行结果
2-5:线程优先级priority
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
- 但是优先级高的只是意味着获得调度的高概率,并不是每次都会比优先级低的先调度,这个得看CPU的调度
package com.lingaolu;
import lombok.SneakyThrows;
public class CreateThread implements Runnable{
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new CreateThread(),"t1");
Thread t2 = new Thread(new CreateThread(),"t2");
Thread t3 = new Thread(new CreateThread(),"t3");
Thread t4 = new Thread(new CreateThread(),"t4");
Thread t5 = new Thread(new CreateThread(),"t5");
t1.setPriority(1);
t1.start();
t2.setPriority(5);
t2.start();
t3.setPriority(Thread.MAX_PRIORITY);
t3.start();
t4.setPriority(2);
t4.start();
t5.setPriority(6);
t5.start();
System.out.println("主线程优先级为"+Thread.currentThread().getPriority());
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"子线程执行......优先级为"+Thread.currentThread().getPriority());
}
}
2-6:守护线程deamon
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线层执行完毕,比如:监控内存,垃圾回收等等
package com.lingaolu;
public class CreateThread{
public static void main(String[] args) throws InterruptedException {
Thread girlFriendThread = new Thread(new GirlFriend());
// 守护主线程,主线程完毕,girlFriendThread也就完毕
girlFriendThread.setDaemon(true);
girlFriendThread.setPriority(10);
girlFriendThread.start();
}
}
class I implements Runnable{
@Override
public void run() {
while (true){
System.out.println("我一直守护女朋友");
}
}
}
class GirlFriend implements Runnable{
@Override
public void run() {
Thread thread = new Thread(new I());
// 默认false,false为正常线程,true为守护线程,守护上级线程
thread.setDaemon(true);
thread.start();
for (int i = 1; i <= 30; i++) {
System.out.println("女朋友线程执行"+i);
}
}
}
4:并发与锁
4-1:初识并发问题
3个线程同时抢20张票,更详细的用法这里有介绍
package com.lingaolu;
import lombok.SneakyThrows;
public class CreateThread implements Runnable{
private Boolean flag = true;
private static int num = 20;
public static void main(String[] args) {
CreateThread createThread = new CreateThread();
new Thread(createThread,"111线程").start();
new Thread(createThread,"222线程").start();
new Thread(createThread,"333线程").start();
}
@SneakyThrows
@Override
public void run() {
while (flag) {
if (num > 0) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"抢了第"+num--+"张票");
} else {
flag = false;
}
}
}
}
从结果可以看出,不仅有线程抢到了相同的票,还有负的票,这就是线程安全问题
4-2:synchronized
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
- 缺陷:若将一个大的方法申明为synchronized将会影响效率
- 方法里面需要修改内容才需要锁,锁太多,浪费资源
package com.lingaolu;
import lombok.SneakyThrows;
public class CreateThread implements Runnable{
private Boolean flag = true;
private static int num = 20;
private Object obj = new Object();
public static void main(String[] args) {
CreateThread createThread = new CreateThread();
new Thread(createThread,"111线程").start();
new Thread(createThread,"222线程").start();
new Thread(createThread,"333线程").start();
}
@SneakyThrows
@Override
public void run() {
while (flag) {
synchronized (obj) {
if (num > 0) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "抢了第" + num-- + "张票");
} else {
flag = false;
}
}
}
}
}
4-3:死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”问题。
package com.lingaolu;
import lombok.Data;
import lombok.SneakyThrows;
@Data
public class CreateThread implements Runnable{
private static Object obj1 = new Object();
private static Object obj2 = new Object();
private int num;
public CreateThread(int num) {
this.num = num;
}
public static void main(String[] args) {
new Thread(new CreateThread(1),"111线程").start();
new Thread(new CreateThread(2),"222线程").start();
}
@SneakyThrows
@Override
public void run() {
if(num==1){
synchronized (obj1) {
System.out.println(Thread.currentThread().getName()+"=====对象1");
Thread.sleep(1000);
synchronized (obj2) {
System.out.println(Thread.currentThread().getName()+"=====对象2");
Thread.sleep(1000);
}
}
}else {
synchronized (obj2) {
System.out.println(Thread.currentThread().getName()+"=====对象2");
Thread.sleep(1000);
synchronized (obj1) {
System.out.println(Thread.currentThread().getName()+"=====对象1");
Thread.sleep(1000);
}
}
}
}
}
发生死锁,if和else的代码块里面都互相等待对方的锁
死锁解决,代码块不同时拥有2个对象锁以上,要分开
package com.lingaolu;
import lombok.Data;
import lombok.SneakyThrows;
@Data
public class CreateThread implements Runnable{
private static Object obj1 = new Object();
private static Object obj2 = new Object();
private int num;
public CreateThread(int num) {
this.num = num;
}
public static void main(String[] args) {
new Thread(new CreateThread(1),"111线程").start();
new Thread(new CreateThread(2),"222线程").start();
}
@SneakyThrows
@Override
public void run() {
if(num==1){
synchronized (obj1) {
System.out.println(Thread.currentThread().getName()+"=====对象1");
Thread.sleep(1000);
}
// 同步代码块锁分开
synchronized (obj2) {
System.out.println(Thread.currentThread().getName()+"=====对象2");
Thread.sleep(1000);
}
}else {
synchronized (obj2) {
System.out.println(Thread.currentThread().getName()+"=====对象2");
Thread.sleep(1000);
}
// 同步代码块锁分开
synchronized (obj1) {
System.out.println(Thread.currentThread().getName()+"=====对象1");
Thread.sleep(1000);
}
}
}
}
成功解决
5-1:管程法
并发协作模型“生产者/消费者模式”
- 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
- 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”
- 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
package com.lingaolu;
import lombok.Data;
@Data
public class CreateThread {
public static void main(String[] args) {
DemandPool demandPool = new DemandPool();
new Productor(demandPool).start();
new programmer(demandPool).start();
}
}
// 生产者,产品
class Productor extends Thread{
DemandPool demandPool;
public Productor(DemandPool demandPool){
this.demandPool = demandPool;
}
// 添加需求
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
demandPool.push(new Demand(i));
}
}
}
// 消费者,程序员
class programmer extends Thread{
DemandPool demandPool;
public programmer(DemandPool demandPool){
this.demandPool = demandPool;
}
// 做需求
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
demandPool.pop();
}
}
}
// 产品,需求
class Demand{
// 需求编号
int id;
public Demand(int id) {
this.id = id;
}
}
// 需求池
class DemandPool{
// 需要一个容器大小
Demand[] demands = new Demand[5];
// 容器计数器
int count = 0;
// 产品加需求
public synchronized void push(Demand demand){
// 如果需求池满了,就需要等待程序员做需求
if(count == demands.length){
// 通知程序员做需求,产品等待加需求
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果没有满,产品就加需求
System.out.println("产品添加了第"+demand.id+"个需求");
demands[count] = demand;
count++;
// 可以通知程序员做需求
this.notifyAll();
}
// 程序员做需求
public synchronized Demand pop(){
// 判断是否有需求
if(count==0){
// 等待产品的需求
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 可以做需求
count--;
Demand demand = demands[count];
System.out.println("程序员做了第"+demand.id+"个需求");
// 通知产品加需求
this.notifyAll();
return demand;
}
}
结果
5-2:信号灯法
信号灯法就是使用一个标记位,判断标记位来等待启动,我们举个例子,产品 出什么需要,程序员就做什么需求
package com.lingaolu;
import lombok.Data;
@Data
public class CreateThread {
public static void main(String[] args) {
Demand demand = new Demand();
new Productor(demand).start();
new programmer(demand).start();
}
}
// 生产者,产品
class Productor extends Thread{
Demand demand;
public Productor(Demand demand){
this.demand = demand;
}
// 添加需求
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
this.demand.push(i);
}
}
}
// 消费者,程序员
class programmer extends Thread{
Demand demand;
public programmer(Demand demand){
this.demand = demand;
}
// 做需求
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
this.demand.pop();
}
}
}
// 需求
class Demand{
private int id;
private Boolean flag = true;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Demand() {
}
// 产品加需求
public synchronized void push(int id){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setId(id);
System.out.println("产品添加了第"+id+"个需求");
// 通知程序员做需求
this.notifyAll();
this.flag = !this.flag;
}
// 程序员做需求
public synchronized void pop(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("程序员做了第"+id+"个需求");
// 通知产品加需求
this.notifyAll();
this.flag = !this.flag;
}
}
结果
6-1:线程池例子
JDK5.0起提供了线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command):执行任务命令。没有返回值,一般用来执行Runnable
- Future submit(Callable task):执行任务命令。有返回值,一般用来执行Callable
- voidshutdown():关闭连接池
Executors:工具类,线程池的工厂,用于创建并返回不同类型的线程池
比较重要的几个类:
- ExecutorService: 真正的线程池接口。
- ScheduledExecutorService: 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
- ThreadPoolExecutor: ExecutorService的默认实现。
- ScheduledThreadPoolExecutor: 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现
package com.lingaolu;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CreateThread implements Runnable {
public static void main(String[] args) {
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 执行线程
executorService.execute(new CreateThread());
executorService.execute(()->System.out.println("线程1执行"));
executorService.execute(()->System.out.println("线程2执行"));
executorService.execute(()->System.out.println("线程3执行"));
// 关闭连接
executorService.shutdown();
}
@Override
public void run() {
System.out.println("线程执行");
}
}
结果
6-2:自定义线程池(手动创建线程池,效果会更好)
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的
package com.lingaolu;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class CreateThread{
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
4, // 核心线程数
8, // 最大线程数
5, // 存活时间
TimeUnit.SECONDS, // 秒
new LinkedBlockingDeque<>(300), // 队列大小300
new MyThreadFactory(new ThreadGroup("myThreadGroup"),"my-thread"), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略(默认),所谓拒绝策略,是指将任务添加到线程池中时,线程池拒绝该任务所采取的相应策略
);
while (true){
Thread.sleep(1000);
threadPoolExecutor.execute(()->{
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
});
}
}
static class MyThreadFactory implements ThreadFactory{
private AtomicInteger number = new AtomicInteger(0);
private ThreadGroup group;
private String namePrefix;
public MyThreadFactory(ThreadGroup group, String namePrefix) {
this.group = group;
this.namePrefix = namePrefix;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group,r,namePrefix+"-"+number.getAndIncrement());
if (t.isDaemon()){
t.setDaemon(false);
}
if (t.getPriority() != Thread.NORM_PRIORITY){
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}
}
定的核心线程数是4个