(本篇文章全面的释义了线程和进程;为了方便大家熟练的掌握,内容中引入了详细的代码;欢迎大家学习讨论和批评指正)
操作系统(OS)中并发(同时)执行的多个程序任务
在一个时间段内,CPU会将时间段划分为若干个时间片,一个时间片是能被一个程序拥有,且只有拥有时间片的程序才能执行自身内容,所以当时间片的划分足够细小,交替频率足够快,就会形成并行的假象,时间上仍然是串行.
是进程中并发执行的多个任务
一个时间片只能被一个进程拥有,一个进程一次又只能执行一个线程. 由于进程之间交替执行,所以线程之间必定也是交替执行
正在执行中的程序才叫进程,其他的都是等待执行的程序
无论是否拥有时间片,线程任务都叫线程
时间片
数据
代码
继承Thread, 重写run方法
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println(i);
}
}
}
package com.by.test;
import com.by.thread.MyThread;
public class Test1 {
public static void main(String[] args) {
Thread t1 = new MyThread();
Thread t2 = new MyThread();
t1.start();
t2.start();
/*t1.run();
t2.run();*/
System.out.println("main结束");
}
}
实现Runnable,重写run方法. 在Thread对象的构造中传入任务对象
package com.by.dao.impl;
/**
* 线程任务
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i=1;i<=100;i++) {
System.out.println(i);
}
}
}
package com.by.test;
import com.by.dao.impl.MyRunnable;
import javax.print.attribute.standard.RequestingUserName;
public class Test2 {
public static void main(String[] args) {
/*//先创建任务对象
Runnable r = new MyRunnable();
//将任务对象传入线程对象
Thread t1 = new Thread(r);*/
//任务只会执行一次时,可以通过匿名内部类或者lambda简化书写
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println("t1:: "+i);
}
}
});
Thread t2=new Thread(()->{
for (int i = 101; i <=200 ; i++) {
System.out.println("t2> "+i);
}
});
t1.start();
t2.start();
}
}
更推荐使用第二种创建方式: 更符合类的单一职责,将线程对象的创建与线程任务的书写分离,更有利于后期的维护
线程对象.start()
方法sleep()
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
//让当前线程休眠3秒钟
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("休眠异常");
}
for (int i = 1; i <=100 ; i++) {
System.out.println("t1:: "+i);
}
}
});
join()
package com.by.test;
public class Test3 {
public static void main(String[] args) {
//以下代码执行顺序:t1->t2->t3
Thread t1=new Thread(()->{
for (int i = 0; i < 30; i++) {
System.out.println("t1: "+i);
}
});
Thread t2=new Thread(()->{
//使t1线程在t2线程之前执行
try {
t1.join();
} catch (InterruptedException e) {
System.out.println("join失败");
}
for (int i = 101; i < 130; i++) {
System.out.println("t2>"+i);
}
});
Thread t3=new Thread(()->{
try {
t2.join();
} catch (InterruptedException e) {
System.out.println("join失败");
}
for (char i = 65; i <=90; i++) {
System.out.println("t3::"+i);
}
});
t1.start();
t2.start();
t3.start();
}
}
sleep和join的区别?
- sleep方法进入的是有限期等待状态,join方法进入的是无限期等待状态
- sleep是静态方法,可以直接通过类名调用,join是非静态方法,必须通过线程对象调用
前言: 当一个任务需要多次执行时,如果将任务放置于线程对象Thread中,会浪费内存空间导致不合理的并发,线程池可以解决该问题
管理盛放线程任务, 将需要执行的任务提交执行,任务结束之后池与任务并不会立即销毁,任务对象会回到池中等待下次执行,直到线程池关闭,内部任务才会失效
ExecutorService: 线程池接口
Executors: 线程池工具类,用来获取线程池对象
不固定并发数量的线程池: 所有提交到池中的任务都会同时并发
固定并发数量的线程池: 对应并发数量的任务先并发执行,超出的任务需要等待执行,等池中执行的任务结束让位之后,超出部分的任务才会进入池中执行
Runnable: run()
package com.by.test;
import com.by.dao.impl.MyRunnable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test4 {
public static void main(String[] args) {
//获取一个不固定并发数量的线程池
// ExecutorService es1 = Executors.newCachedThreadPool();
ExecutorService es1 = Executors.newFixedThreadPool(2);
Runnable r1=new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("r1>>"+i);
}
}
};
Runnable r2=new Runnable() {
@Override
public void run() {
for (int i = 50; i < 100; i++) {
System.out.println("r2:::"+i);
}
}
};
//提交任务执行
es1.submit(r1);
es1.submit(r2);
es1.submit(r2);
//关闭线程池
es1.shutdown();
}
}
Callable: call()
Future对象.get()
获取内部的返回值Callable<返回值类型> c1=new Callable<返回值类型>() {
@Override
public 返回值类型 call() throws Exception {
//...
return 值;
}
};
package com.by.test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test5 {
public static void main(String[] args) throws Exception {
//创建线程池
ExecutorService es = Executors.newCachedThreadPool();
//线程任务1: 计算1-100的和并返回
Callable<Integer> c1=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i < 101; i++) {
sum += i;
}
return sum;
}
};
//提交任务执行并接收
Future<Integer> f =es.submit(c1);
System.out.println(f.get());
System.out.println(es.submit(c1).get());
//关闭线程池
es.shutdown();
}
}
思路: 在被线程同时访问的方法上加锁
访问修饰符 synchronized 返回值类型 方法名(参数列表){
}
package com.by.util;
import java.util.ArrayList;
import java.util.List;
/**
* 工具类-操作集合属性
*/
public class MyList {
private List<Integer> list = new ArrayList<>();
/**
* 给集合属性添加元素
* @param n 添加的元素值
synchronized: 同步方法
*/
public synchronized void insert(int n){
list.add(n);
}
/**
* 查看集合内容
*/
public void query(){
System.out.println("集合长度: " + list.size());
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i)+" ");
}
}
}
思路: 让参与临界资源对象访问的线程自身加锁
synchronized(临界资源对象){
//需要被认定为原子操作的代码
}
使用: 所有访问同一临界资源的线程都需要同时添加同步代码块
package com.by.test2;
import com.by.util.MyList;
public class TestMyList {
public static void main(String[] args)throws Exception {
//创建两个线程,同时操作工具类,线程1负责往集合中添加元素1-5,线程2负责往集合中添加元素6-10
//添加结束之后查看集合内容
//创建工具类对象
MyList m = new MyList();
Thread t1=new Thread(()->{
for (int i = 1; i <=5 ; i++) {
synchronized (m) {
m.insert(i);
}
}
});
Thread t2=new Thread(()->{
for (int i = 6; i <=10 ; i++) {
synchronized (m) {
m.insert(i);
}
}
});
t1.start();
t2.start();
//使t1和t2线程先进行添加操作
t1.join();
t2.join();
//查看集合元素
m.query();
}
}
/*
* 张三上厕所
* 李四上厕所
*
* 原子操作: 脱裤子-->蹲下来-->上厕所-->擦屁股-->穿裤子-->冲水-->走人
*
*临界资源: 厕所-坑位
*
*解决方式1:给厕所大门加锁
*解决方式2:自己给坑位加锁
*
*
* */
悲观锁: 悲观的认为集合一定会出现线程安全问题,所以直接加锁
乐观锁: 乐观的认为集合一定不会出现线程安全问题,如果安全问题发生,再利用算法解决问题(无锁机制)
JDK5.0,发布了一批无锁机制的线程安全的集合类
都来自于java.util.concurrent包
ConcurrentHashMap: CAS算法
compare and swap: 比较并交换
原有值,预期值,结果值: 当原有值与预期值相等时才会将结果值放入内存
int i=1;
i++;
原有值: 1 预期值: 1 结果值:2
CopyOnWriteArrayList:
CopyOnWriteArraySet: