目录
多线程
线程
实现线程的方法
方法一:继承Thread父类
方法二:实现Runnable接口
方法三:Callable接口和FutureTask类来实现
Thread方法
线程安全
线程同步
同步代码块
同步方法
Lock锁
线程池
线程池对象的创建方式一:
线程池处理Runnable任务
线程池处理Callable任务
并发和并行
并发的含义
并行的理解
线程的六种状态
线程(Thread是一个程序内部单独一条执行流程),程序中如果只有一条执行流程,那这个程序就是单线程的程序
我们先写一个MyThread类,表示我们创建的子线程类
public class MyThread extends Thread{
@Override
public void run(){
for (int i = 0; i <= 5; i++) {
System.out.println("子线程MyThread输出"+i);
}
}
}
我们实现main方法
public class main_Thread {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
for (int i = 0; i <= 5; i++) {
System.out.println("主线程main输出:"+i);
}
}
}
输出结果为
说明我们现在有两个线程,每一个线程都先运行
需要我们注意的是:
第一点: 我们在子类中重写了run方法,但是我们在调用创建的线程对象t的方法是start(),如果是run()方法会变成单线程,会先执行完run方法里的才会执行main函数中的
第二点: 我们的子线程要放到主线程的前面
缺点:一个子类只能继承一个父类,不能再继承其他类,所以继承了Thread之后,不能继承其他类,会导致功能减少
(1)定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
public class MyRunnable implements Runnable{
@Override
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println("子线程1---->"+i);
}
}
}
(2)创建MyRunnable任务对象
Runnable r = new MyRunnable();
(3)把MyRunnable任务对象交给Thread处理并且调用对象的start()方法启动线程
new Thread(r).start();
下面是mian方法的完整代码
public class main_Thread {
public static void main(String[] args) {
// 创建MyRunnable任务对象
Runnable r = new MyRunnable();
new Thread(r).start();
for (int i = 0; i < 5; i++) {
System.out.println("mian线程--->"+i);
}
}
}
代码优化
不创建子类,用匿名内部类
public class main_Thread {
public static void main(String[] args) {
// 创建MyRunnable任务对象
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程--->"+i);
}
}
}).start();
for (int i = 0; i < 5; i++) {
System.out.println("mian线程--->"+i);
}
}
}
优点: 任务类只能实现接口,可以继续继承其他类,实现其他接口
缺点: 需要多一个Runable对象
使用原因:因为我们上面两个方法都是调用start方法,进而让调用run方法的,而run方法是void类型的,不会给我们返回值,这时候就要用到方法三
最大优点:可以返回线程执行完毕后的结果
创建线程的步骤:
1.创建任务对象:
(1)定义一个类实现Callable接口,重写call方法,封装要做的事情,和返回的数据
import java.util.concurrent.Callable;
public class MyCall implements Callable {
private int n ;
public MyCall(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
// 描述线程的任务,返回线程执行返回后的结果
// 需求:求1-n的和返回
int sum = 0;
for (int i=1;i
(2)把Callable类型的对象封装成FutureTask(线程任务对象)
//创建一个Callable对象
Callable call = new MyCall(100);
2.把线程任务对象交给Thread对象
// 把Callable的对象封装成一个FutureTask对象
// 未来任务对象的作用?
// 1.是一个任务对象,实现Runnable对象
// 2.可以在线程执行完毕后,
// 用未来线程对象调用get方法获取线程执行完毕后值
FutureTask f1 = new FutureTask<>(call);
3.调用Thread对象的start方法启动线程
// 把任务对象交给一个Thread对象
new Thread(f1).start();
4.线程执行完毕后,通过FutureTask对象的get()方法去获取线程任务执行的结果
// 获取线程执行完毕后返回的结果
// 注意: 如果执行到这,假如说线程还没有执行完毕
// 这里的代码会暂停,等待上面线程执行完毕后才会获取结果
String s = f1.get();
System.out.println(s);
mian主线程中的完整代码
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class main_Thread {
public static void main(String[] args) throws Exception {
//创建一个Callable对象
Callable call = new MyCall(100);
// 把Callable的对象封装成一个FutureTask对象
// 未来任务对象的作用?
// 1.是一个任务对象,实现Runnable对象
// 2.可以在线程执行完毕后,
// 用未来线程对象调用get方法获取线程执行完毕后值
FutureTask f1 = new FutureTask<>(call);
// 把任务对象交给一个Thread对象
new Thread(f1).start();
// 获取线程执行完毕后返回的结果
// 注意: 如果执行到这,假如说线程还没有执行完毕
// 这里的代码会暂停,等待上面线程执行完毕后才会获取结果
String s = f1.get();
System.out.println(s);
}
}
public class main_Thread {
public static void main(String[] args) throws Exception {
Thread t1 = new MyThread();
t1.setName("1号线程");
t1.start();
System.out.println(t1.getName()); // 输出线程1的名字
Thread t2 = new MyThread();
t2.setName("2号线程");
t2.start();
System.out.println(t2.getName()); // 输出线程2的名字
// 主线程对象的名字
Thread m = Thread.currentThread(); // 拿到当前执行线程名字
m.setName("nb线程");
System.out.println(m.getName()); // main
for (int i = 0; i <= 5; i++) {
System.out.println("main主线程输出:"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i <=5 ; i++) {
if(i== 3)
{
// 让当前的线程停止5秒
Thread.sleep(5000);
}
}
// join方法让调用方法的线程先执行完
Thread t1 = new Thread();
t1.start();
t1.join();
}
}
线程安全问题的原因: 多个线程,同时访问同一个共享资源,且存在修改资源
我们来模拟线程安全,假如说现在取钱,小明和小红在同一个账户下取钱,现在账户有10万元,小明在他的手机上取10万元,小红在她的手机取10万元,想有没有一种可能小明的手机判断有钱但是还没有取钱的同时,小红的手机也判断有钱,也要完成取钱操作,这样的话小明取出来了10万元,小红手机也取出了10万元,而账户中只有10万元,这就是线程安全
我们用代码模拟一下这个情况
我们先创建账户类,用来创建账户对象
package surf_Thread;
public class Account {
private String Id;
private int money;
public Account() {
}
public Account(String id, int money) {
Id = id;
this.money = money;
}
public String getId() {
return Id;
}
public void setId(String id) {
Id = id;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public void drawMoney(int i) {
// 先搞清楚谁来取钱?
String name = Thread.currentThread().getName();
// 判断余额是否足够
if(this.money>=i)
{
System.out.println(name+"来取钱"+money+"成功");
this.money-=i;
System.out.println(name+"来取钱后,余额剩余: "+money);
}
else{
System.out.println(name + "来取钱,余额不足");
}
}
}
我们在上面创建了一个类的取钱方法,因为每个线程都要进入这个方法,我们可以拿到这个线程的名字
下面我们创建一个线程类,run里面要把握住,这个线程的行为,因为我们要完成取钱,所以必须有一个账户对象,我们要构建一个有参构造器,来接收对那个账户进行操作,还要接受线程的名字
package surf_Thread;
public class DrawMoney extends Thread{
private Account acc;
public DrawMoney(Account acc,String name)
{
super(name); // super 要放到上面
this.acc = acc;
}
@Override
public void run()
{
//run里面主要实现线程要完成什么事情
acc.drawMoney(100000);
}
}
主线程
package surf_Thread;
public class ThreadTest {
public static void main(String[] args) {
// 1. 创建一个账户,两个人共同的对象
Account acc = new Account("ICBC-120",100000);
// 2.创建取钱线程
new DrawMoney(acc,"小明").start();
new DrawMoney(acc,"小红").start();
}
}
我们对这个案例运行时,发现:
这个就会造成线程安全问题
我们应该怎么解决呢?
看下面
建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象
对于静态方法建议使用字节码(类名.class)对象作为锁对象
有隐含this
JDK 5.0起提供了代表线程池的接口: ExecutorService
但是利用接口我们是不能创建线程池对象的,那我们如何得到线程池对象?
使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
需要我们注意的是:
1.临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程
2.什么时候会开始拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务
下面是创建线程池
import java.util.concurrent.*;
public class Pool {
public static void main(String[] args) {
ExecutorService Pool = new ThreadPoolExecutor(3,5,8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory()
,new ThreadPoolExecutor.AbortPolicy());
}
}
下面是4中不同形式的任务拒绝策略
public class Pool {
public static void main(String[] args) {
ExecutorService Pool = new ThreadPoolExecutor(3,5,8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory()
,new ThreadPoolExecutor.AbortPolicy());
Runnable target = new MyRunnable();
Pool.execute(target); // 线程池自动创建一个新线程,自动处理这个任务,自动执行
Pool.execute(target); // 线程池自动创建一个新线程,自动处理这个任务,自动执行
Pool.execute(target); // 线程池自动创建一个新线程,自动处理这个任务,自动执行
Pool.execute(target); // 复用上面的线程
Pool.execute(target); // 复用上面的线程 // 注意临时线程的创建条件:核心线程都占用了,而且队列中都占满了
// 如果想要创建临时线程,让上面的线程都被占用,而且队列占满
//假如说现在满足条件,那么下面添加任务就开始创建临时线程
Pool.execute(target);
Pool.execute(target); // 上面创建了两个临时线程
// 如果再有任务那么就直接拒绝
}
}
因为线程池创建之后开始运行,他不能自动关闭,我们一般情况下也不希望它关闭
那么怎么让他关闭呢?
两种方法:
package ThreadPoolTest;
import java.util.concurrent.*;
public class Pool {
public static void main(String[] args) throws Exception {
ExecutorService Pool = new ThreadPoolExecutor(3,5,8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory()
,new ThreadPoolExecutor.AbortPolicy());
Future f1 = Pool.submit(new MyCall(100));
Future f2 = Pool.submit(new MyCall(200));
Future f3 = Pool.submit(new MyCall(300));
String s1 = f1.get();
}
}
进程: 正在运行的程序(软件)就是独立的进程
线程是属于进程的,一个进程中可以同时运行很多个线程
进程中的多个线程其实是并发和并行的
进程中的线程是由CPU负责执行的,但CPU能同时处理的线程数量有限,为保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发
在同一个时刻上,同时有多个线程在被CPU调度执行