多线程
- 1、 方式一 Thread
- 2、实现Runnable接口
- 3、实现 Callable接口
- 4、与线程有关的操作方法
- 5、线程安全问题
- 5.1 取钱案例
- 5.2 线程同步
- 5.2.1 同步代码块
- 5.2.2 同步方法
- 5.2.3 Lock锁
- 6、线程池
- 6.2 创建线程池
- 6.2.1 使用ExecutorService创建
- 新任务策略
- 6.2.2 使用Executors工具类创建
- 核心线程数量问题
- 7、并发、并行,线程生命周期
- 7、乐观锁、悲观锁
1、 方式一 Thread
继承 Thread类
优点:编码简单
缺点:java是单继承,继承了一个类,就不能继承其他,可拓展性不强。
注意
:
①子线程一定要调用start方法,而不是run方法,调用run方法,还是相当于在main线程中创建了一个实例对象,在运行她的方法, 而已,是单线程的。调用strat方法,虽然底层还是调用run方法,但是他会告诉cpu说我们另外启动了一个线程。
②子线程启动一定要在主线程任务之前。
③每次执行的结果会不同。
package com.cky.file;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println("子线程"+i);
}
}
}
package com.cky.file;
public class Endecode {
public static void main(String[] args) throws Exception {
Thread thread=new MyThread();
thread.start();
for (int i = 0; i <5 ; i++) {
System.out.println("主线程"+i);
}
}}
2、实现Runnable接口
package com.cky.file;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println("子线程"+i);
}
}
}
package com.cky.file;
public class Endecode {
public static void main(String[] args) throws Exception {
//创建一个任务对象
MyRunnable myRunnable=new MyRunnable();
//调用线程类的start方法
new Thread(myRunnable).start();
//匿名内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程" + i);
}
}
};
new Thread(runnable).start();
//简化形式1
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程" + i);
}
}
}).start();
//形式2
new Thread(()-> {
for (int i = 0; i < 5; i++) {
System.out.println("子线程" + i);
}
}).start();
for (int i = 0; i <5 ; i++) {
System.out.println("主线程"+i);
}
}}
由于Runnable是一个函数式接口,匿名内部类可以使用lambda形式
3、实现 Callable接口
上边两种实现方法,都不能获得线程执行的结果并返回,方法3可以。
实现Callable接口 将该接口 封装为一个FutureTask 任务对象,最后在交给线程。
是一个泛型接口 这里String 是要返回的类型 可以定义为其他类型
package com.cky.file;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum=0;
for (int i = 1; i <= n; i++) {
sum+=i;
}
return "1-"+n+"的和为:"+sum;
}
}
package com.cky.file;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Endecode {
public static void main(String[] args) throws Exception {
//创建一个Callable对象
Callable<String> callable=new MyCallable(100);
//封装为一个FutureTask对象
//FutureTask 是一个任务对象 实现了Runnable接口
// 在执行完毕后 可以通过get方法获得执行结果
FutureTask<String> f1 = new FutureTask<>(callable);
//交给一个Thread对象
new Thread(f1).start();
//获得执行结果 注意:执行未结束 是不会取得结果的
String s = f1.get();
System.out.println(s);//1-100的和为:5050
}}
4、与线程有关的操作方法
package com.cky.file;
public class MyThread extends Thread{
public MyThread( String name){
super(name);
}
@Override
public void run() {
Thread t=Thread.currentThread();
for (int i = 0; i <3 ; i++) {
System.out.println(t.getName()+i);
}
}
}
package com.cky.file;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Endecode {
public static void main(String[] args) throws Exception {
MyThread myThread1=new MyThread("子线程1");
//修改名字
// myThread1.setName("子线程1");
myThread1.start();
MyThread myThread2=new MyThread("子线程2");
// myThread2.setName("子线程2");
myThread2.start();
//哪个线程在执行 就是哪个线程
Thread m = Thread.currentThread();
String name = m.getName();
System.out.println(name);
for (int i = 0; i < 3; i++) {
System.out.println(name+"线程"+i);
}
}}
package com.cky.file;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Endecode {
public static void main(String[] args) throws Exception {
//当输出为3时 ,延缓5秒在运行
for (int i = 0; i < 5; i++) {
System.out.println(i);
if(i==3)
Thread.sleep(5000);
}
}}
package com.cky.file;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Endecode {
public static void main(String[] args) throws Exception {
MyThread myThread1=new MyThread("子线程1");
myThread1.start();
myThread1.join();//当前线程执行完毕 再往下进行
MyThread myThread2=new MyThread("子线程2");
myThread2.start();
myThread2.join();
Thread m = Thread.currentThread();
String name = m.getName();
System.out.println(name);
for (int i = 0; i < 3; i++) {
System.out.println(name+"线程"+i);
}
}}
子线程10
子线程11
子线程12
子线程20
子线程21
子线程22
main
main线程0
main线程1
main线程2
5、线程安全问题
5.1 取钱案例
造成线程安全问题原因:
同时存在多个线程,访问同一个共享资源,且都要修改该共享资源。
同时进入钱够的判断,造成线程 不安全
package com.cky.file;
public class Account {
private double money;
public Account(double money) {
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public void drawmoney(double money){
String name=Thread.currentThread().getName();
if (this.money>=money){
System.out.println(name+"来取钱成功");
this.money-=money;
System.out.println(name+"取后,剩余:"+this.money);
}
else{
System.out.println("钱不够");
}
}
}
package com.cky.file;
public class MyThread extends Thread{
private Account account;
public MyThread( Account account,String name){
super(name);
this.account=account;
}
@Override
public void run() {
//取钱
account.drawmoney(10000);
}
}
package com.cky.file;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class Endecode {
public static void main(String[] args) {
//创建一个共享账户
Account account=new Account(10000);
//两个线程
MyThread myThread1=new MyThread(account,"小红");
MyThread myThread2=new MyThread(account,"小明");
myThread1.start();
myThread2.start();
}}
小红来取钱成功
小明来取钱成功
小红取后,剩余:0.0
小明取后,剩余:-10000.0
5.2 线程同步
解决线程安全问题的办法
5.2.1 同步代码块
public void drawmoney(double money){
String name=Thread.currentThread().getName();
synchronized (this) {
if (this.money>=money){
System.out.println(name+"来取钱成功");
this.money-=money;
System.out.println(name+"取后,剩余:"+this.money);
}
else{
System.out.println("钱不够");
}
}
实例方法 同步代码块 通常加this 代表当前资源 比如小红和小黑共享一个账户 小白和小亮共享一个账户,使用this 保证了 只锁住同一个账户 不被多个访问 但不会去锁住别人的账户。
如果是静态方法 每个类只有一份 只允许一个访问 通常用 类.class 来锁住所有,只允许一个人访问。
public static void text(){
synchronized (Account.class){}
}
5.2.2 同步方法
public synchronized void drawmoney(double money){
String name=Thread.currentThread().getName();
if (this.money>=money) {
System.out.println(name + "来取钱成功");
this.money -= money;
System.out.println(name + "取后,剩余:" + this.money);
}
else{
System.out.println("钱不够");
}
}
5.2.3 Lock锁
package com.cky.file;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private double money;
//为每个账户创建一个锁对象
private final Lock lock=new ReentrantLock();
public Account(double money) {
this.money = money;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
public void drawmoney(double money){
String name=Thread.currentThread().getName();
//加锁
lock.lock();
try {
if (this.money>=money) {
System.out.println(name + "来取钱成功");
this.money -= money;
System.out.println(name + "取后,剩余:" + this.money);
}
else{
System.out.println("钱不够");
}
} catch (Exception e) {
e.printStackTrace();
}
finally {
//写在finally里,为了防止上边代码出错,导致没有解锁
lock.unlock();
}
}
}
6、线程池
如果每一个线程都需要我们去新创建一个线程的话,就会耗资源并且耗时(创建线程是一件耗时的事)
此时就需要线程池,线程池可以复用线程,不用经常去创建线程。
并且线程池也可以确定任务队列中任务的个数。防止任务过多,导致内存溢出。
6.2 创建线程池
6.2.1 使用ExecutorService创建
package com.cky.file;
public class MyRunnable implements Runnable{
private int n;
public MyRunnable(int n) {
this.n = n;
}
@Override
public void run() {
int sum=0;
for (int i = 1; i <= n; i++) {
sum+=i;
}
System.out.println(Thread.currentThread().getName()+"-1-"+n+"的和为:"+sum);
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.cky.file;
import java.util.concurrent.*;
public class Endecode {
public static void main(String[] args) {
//创建一个线程池对象
/* int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler*/
ExecutorService pool=new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
MyRunnable myRunnable=new MyRunnable(100);
pool.execute(myRunnable);
pool.execute(myRunnable);
pool.execute(myRunnable);
//开始往任务队列加
pool.execute(myRunnable);
pool.execute(myRunnable);
pool.execute(myRunnable);
pool.execute(myRunnable);
//开始使用临时线程
pool.execute(myRunnable);
pool.execute(myRunnable);
// 开始使用任务队列满的措施
pool.execute(myRunnable);
}}
D:\JAVA\jdk-17.0.8\bin\java.exe "-javaagent:D:\SOftware\idea\IntelliJ IDEA 2021.3.2\lib\idea_rt.jar=49289:D:\SOftware\idea\IntelliJ IDEA 2021.3.2\bin" -Dfile.encoding=UTF-8 -classpath E:\java_code\project\out\production\hello-app;E:\java_code\project\hello-app\lib\dom4j-2.1.4.jar;E:\java_code\project\hello-app\lib\commons-io-2.15.1.jar com.cky.file.Endecode
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@5b480cf9[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@5f184fc6[Wrapped task = com.cky.file.MyRunnable@3feba861]] rejected from java.util.concurrent.ThreadPoolExecutor@6f496d9f[Running, pool size = 5, active threads = 5, queued tasks = 4, completed tasks = 0]
at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065)
at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833)
at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1365)
at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
at com.cky.file.Endecode.main(Endecode.java:31)
pool-1-thread-1-1-100的和为:5050
pool-1-thread-2-1-100的和为:5050
pool-1-thread-5-1-100的和为:5050
pool-1-thread-3-1-100的和为:5050
pool-1-thread-4-1-100的和为:5050
//我们可以看到 使用了临时线程 并且使用了任务满时的策略。
新任务策略
package com.cky.file;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum=0;
for (int i = 1; i <= n; i++) {
sum+=i;
}
return Thread.currentThread().getName()+"-1-"+n+"的和为:"+sum;
}
}
package com.cky.file;
import java.util.concurrent.*;
public class Endecode {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个线程池对象
/* int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler*/
ExecutorService pool=new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 =pool.submit(new MyCallable(300));
Future<String> f4 =pool.submit(new MyCallable(400));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
}}
pool-1-thread-1-1-100的和为:5050
pool-1-thread-2-1-200的和为:20100
pool-1-thread-3-1-300的和为:45150
pool-1-thread-3-1-400的和为:80200
6.2.2 使用Executors工具类创建
创建单个线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.execute(new MyRunnable(100));
使用该种方式的风险
核心线程数量问题
如果时IO密集型 则一般配置 cpu数量*2
如果是计算密集型 则一般是 cpu数量+1
7、并发、并行,线程生命周期
并发
并行
生命周期
7、乐观锁、悲观锁
悲观锁:一上来就加锁,线程安全,性能较差。
乐观锁:不加锁,等到要开始出现线程安全问题时,才开始控制。
package com.cky.file;
import java.util.concurrent.atomic.AtomicInteger;
public class MyRunnable implements Runnable{
private AtomicInteger count=new AtomicInteger();
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"count===>"+count.incrementAndGet());
}
}
}
package com.cky.file;
import java.util.concurrent.*;
public class Endecode {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//乐观锁
//100个线程 对一个变量 各加100次
MyRunnable myRunnable=new MyRunnable();
for (int i = 0; i <100 ; i++) {
new Thread(myRunnable).start();
}
}}