多线程
程序是为完成特定任务、用某种语言编写的一组指令的集合。是静态代码
进程是运行中的程序是一个动态的过程:有它自身的产生、存在和消亡的过程
线程:进程中的一个执行路径,一个进程由多个线程组成,同时执行完成不同工作为多线程。
单核CPU同一时刻只运行一个进程:宏观上看是并行,但在微观串行多个进程快速切换,在用户看来时是多个进程同时运行
并行与并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
线程组成:
CPU时间片:操作系统为每个线程分配执行时间
运行数据:
堆空间:存储对象,多个线程共享堆中对象。
栈空间:存储线程需使用的局部变量,每个线程有独立的栈
线程分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
1.守护线程是用来服务用户线程的,通过在start()方法前调用,用户线程执行完毕,守护线程会自动结束
thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
2.Java垃圾回收就是一个典型的守护线程。
线程特点:
1.抢占执行:效率高,防止某一线程独占CPU时间过长
2.宏观并行,微观串行
多线程的优点
1.提高应用程序的响应。增强用户体验。
5. 提高计算机系统CPU的利用率
6. 改善程序结构。将长而复杂的进程分为多个线程,独立运行,利于理解和修改
多线程使用时机
1.程序需要同时执行两个或多个任务。
2.程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
3.需要一些后台运行的程序时
线程创建
线程创建三种方式:
1.继承Thread类
2.实现Runnerable接口
3.实现Callable接口(jdk1.5加入)
线程创建:1.继承Thread
构造器
Thread():创建新的Thread对象
Thread(String threadname):创建线程并指定线程名
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接
口中的run方法
Thread(Runnable target, String name):创建新的Thread对象
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("子线程····"+i);
}
}
}
-----------------------------------------------------------------
public class ThreadTest {
@Test
public void testThread(){
//1.创建线程对象
MyThread myThread=new MyThread();
//2.启动线程
//myThread.run();是调用普通方法
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程····"+i);
}
}
}
--------------------------------------------------------------------
运行结果:
主线程····0
子线程····0
主线程····1
子线程····1
主线程····2
子线程····2
主线程····3
子线程····3
主线程····4
子线程····4
主线程····5
子线程····5
主线程····6
子线程····6
主线程····7
子线程····7
主线程····8
子线程····8
主线程····9
子线程····9
当主线程执行后子线程才会执行,并且是抢占执行。
同一个Thread不能重复调用start方法,跟线程的4中状态有关系。
线程的4种状态:新生状态;可运行状态;阻塞状态;死亡状态
a. 新生状态:在调用start()方法之前
b. 可运行状态:调用start()方法后,系统为该线程分配除cpu外的所需资源,对于只
有一个cpu的机器而言,任何时刻只能有一个处于可运行的线程占用处理机,获得
cpu资源,此时系统正正运行线程的run()方法....
c. 阻塞状态:一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态。这
是【不可运行】的状态,处于这种状态的线程在得到一个特定的事件后会转回可运行状态
d. 死亡状态:一个线程的run()运行完毕,stop()方法被调用或者在运行过程中出现了未捕获的异常时,线程进入死亡状态。
- 线程的4中状态除了【可运行状态】与【阻塞状态】可以来回切换,其余的不可逆转
线程创建:2.实现Runnable接口
Thread类也是实现Runnable接口
接口里面只有一个run()方法
@Test
public void testNm(){
/* Thread thread=new Thread(()->{
System.out.println("子线程");
});
thread.start();
}*/
Runnable runnable=new Runnable() {
@Override
public void run() {
//之前用的jdk11 System.out.println("线程名"+Thread.currentThread().getName());没法输出
//只能输出 System.out.println(Thread.currentThread().getName());之后试了与其它的类型数据一起输出都没法输出
//切到jdk8就能输出了,不明白其中的原理,可能是11版本改了什么
System.out.println("线程名"+Thread.currentThread().getName());
}
};
Thread thread=new Thread(runnable,"MyThread");
thread.start();
}
------------------------------------
结果:
线程名MyThread
线程休眠
sleep方法让当前线程休眠单位:ms
线程放弃
调用yield方法主动放弃当前线程,让其他线程有机会获得线程
线程加入
调用join方法,在一个线程中加入其他线程并阻塞当前线程,当其他线程执行完才会继续执行当前线程。
线程调度
Java的调度方法
同优先级线程组成先进先出队列(先到先服务),使用时间片策略
对高优先级,使用优先调度的抢占式策略
设置线程优先级
setPriority方法设置优先级(1-10)默认5,优先级获得线程概率高
多线程安全问题
多线程访问临界资源(共享对象,一次只允许一个进程使用才能保证一致性),若破坏原子操作,会造成数据不一致
同步代码块
synchronized(临界资源对象){//可以自己创建锁或者用this(当前对象)
同步代码//原子操作
}
synchronized的锁是什么
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
同步方法的锁:静态方法(类名.class)、非静态方法(this)
同步代码块:自己指定,很多时候也是指定为this或类名.class
多个线程必须共用同一把锁。
public class BankTest {
@Test
public void testBank(){
BankCard bankCard=new BankCard();
Runnable add= () -> {
for (int i = 0; i < 5; i++) {
bankCard.setMoney(bankCard.getMoney() + 1000);
System.out.println(Thread.currentThread().getName()+"存了1000,余额 "+bankCard.getMoney());
}
};
Runnable sub= () -> {
for (int i = 0; i < 5; i++) {
if (bankCard.getMoney() >= 1000) {
bankCard.setMoney(bankCard.getMoney() - 1000);
System.out.println(Thread.currentThread().getName() + "取了1000,余额 " + bankCard.getMoney());
}else{
System.out.println("余额不足");
i--;
}
}
};
Thread threadA= new Thread(add, "A");
Thread threadB= new Thread(sub, "B");
threadA.start();
threadB.start();
}
}
---------------------------------------------------------------------------
结果:
A存了1000,余额 0.0
B取了1000,余额 0.0
B取了1000,余额 0.0
余额不足
余额不足
。。。
账户显示余额不足,说明A存完钱还没输出,线程就被B抢占了。
给两个run方法都加上synchronized(bankCard){}
结果:
A存了1000,余额 1000.0
A存了1000,余额 2000.0
A存了1000,余额 3000.0
A存了1000,余额 4000.0
A存了1000,余额 5000.0
B取了1000,余额 4000.0
B取了1000,余额 3000.0
B取了1000,余额 2000.0
B取了1000,余额 1000.0
B取了1000,余额 0.0
同步的范围
1、如何找问题,即代码是否存在线程安全?
(1)明确哪些代码是多线程运行的代码
(2)明确多个线程是否有共享数据
(3)明确多线程运行代码中是否有多条语句操作共享数据
2、如何解决呢?(非常重要)
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其
他线程不可以参与执行。
即所有操作共享数据的这些语句都要放在同步范围中
3、范围太小:没锁住所有有安全问题的代码
范围太大:没发挥多线程的功能
死锁
1.不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
自己需要的同步资源,就形成了线程的死锁
2.出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于
阻塞状态,无法继续
解决方法
1.专门的算法、原则
2.尽量减少同步资源的定义
3.尽量避免嵌套同步
Lock(锁)(jdk5.0)
ReentrantLock类,需手动上锁lock,释放锁unlock
synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是
隐式锁,出了作用域自动释放 - Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有
更好的扩展性(提供更多的子类)
优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源) > 同步方法
(在方法体之外)
死锁问题
public class TheadLock {
@Test
public void testLock(){
StringBuffer numberA=new StringBuffer();
StringBuffer numberB=new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (numberA){
numberA.append("He");
numberB.append("llo");
try {
sleep(1000);//增加死锁的概率,休眠后下面的线程得到执行
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (numberB){
numberA.append("wo");
numberB.append("rld");
}
System.out.println(numberA);
System.out.println(numberB);
}
}
}.start();
new Thread(new Runnable(){
@Override
public void run() {
//上面线程休眠,当前线程得到执行
synchronized (numberB){//握住B
numberA.append("Me");
numberB.append("XXo");
//执行到这需要握住锁A,但是A在上面的线程手里,没有被释放,造成死锁
synchronized (numberA){
numberA.append("Ho");
numberB.append("Mld");
}
System.out.println(numberA);
System.out.println(numberB);
}
}
}).start();
}
}
线程通信
public class Number implements Runnable{
private int num=1;
@Override
public void run() {
while (true) {
//wait,notify,notifyAll必须使用在同步代码块或同步方法中
//三个方法的调用者必须是同步监视器,三个方法定义在Object类中
synchronized (this) {
//唤醒wait线程。先等待先唤醒,有优先级的话 优先级高的被唤醒概率会高
notify();
if (num <= 10) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
//调用wait方法进入阻塞状态吗,并释放锁。
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else break;
}
}
}
}
-------------------------------------------------------------------
public class NumberTest {
@Test
public void testNumber(){
Number number=new Number();
Thread thread1=new Thread(number,"A");
Thread thread2=new Thread(number,"B");
thread1.start();
thread2.start();
}
}
----------------------------------------------------
结果:
B:1
A:2
B:3
A:4
B:5
A:6
B:7
A:8
B:9
A:10
sleep与wait异同
相同点:都会使当前线程阻塞
不同点:声明位置不同,Thread类中声明sleep,Object中声明wait
调用范围不同:wait必须使用在同步代码块中或同步方法中,sleep在哪都能用
如果都在同步代码中使用,sleep不会释放锁,wait会。
线程创建:3.实现Callable接口
与使用Runnable相比, Callable功能更强大些
1.相比run()方法,call()方法可以有返回值
2.方法可以抛出异常
3.支持泛型的返回值
4.需要借助FutureTask类,比如获取返回结果
Future接口
1.可以对具体Runnable、Callable任务的执行结果进行取消、查询是
否完成、获取结果等。
2. FutrueTask是Futrue接口的唯一的实现类
3.FutureTask 同时实现了Runnable, Future接口。它既可以作为
Runnable被线程执行,又可以作为Future得到Callable的返回值
public class CallableTest {
@Test
public void testCall() throws ExecutionException, InterruptedException {
Call call=new Call();
//借助FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(call);
//作为runnable被执行(实现了Runnable接口)
new Thread(futureTask).start();
//使用get获得返回值
Integer sum= futureTask.get();
System.out.println("总和为:"+sum);
}
}
class Call implements Callable<Integer>{
/**
* 实现call方法,将线程具体操作卸载方法中
* @return
* @throws Exception
*/
@Override
public Integer call() throws Exception {
int sumNu=0;
for (int i = 0; i <= 10; i++) {
sumNu+=i;
}
return sumNu;
}
}
线程创建:4.线程池
开发常用,带“池”字的基本都是开发常用的(数据库连接池,线程池)
一次创建多个放入池子中,使用时直接获取,用完再放回池子。可以避免频繁创建销毁、实现重复利用。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
。。。
ExecutorService接口 和 Executors工具类
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
1.void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
2. Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
3.void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
1.Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
2.Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
3.Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
4.Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
public class ExecutorTset {
@Test
public void testEx(){
ExecutorService service = Executors.newFixedThreadPool(5);
//适用于Runnable
service.execute(()-> {
System.out.println("Runnable");
});
//适用于Callable
service.submit(new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
System.out.println("Callable");
return null;
}
}));
service.shutdown();//关闭连接池
}
}
```java
public class ExecutorTset {
@Test
public void testEx(){
ExecutorService service = Executors.newFixedThreadPool(5);
//System.out.println(service.getClass());
ThreadPoolExecutor service1= (ThreadPoolExecutor) service;
//设置最大线程数
service1.setMaximumPoolSize(5);
//设置核心池大小
service1.setCorePoolSize(10);
//适用于Runnable
service.execute(()-> {
System.out.println("Runnable");
});
//适用于Callable
service.submit(new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
System.out.println("Callable");
return null;
}
}));
service.shutdown();//关闭连接池
}
}