第十二章 多线程
第十二章 多线程
一、基本概念
1.1、程序、进程、线程
程序
- 为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程
- 进程是一个运行时的应用程
- 一个进程可以启动多个线程任务
线程
- 线程是一个进程中的执行场景。线程,又称轻量级进程(Light Weight Process)
- 程序中的一个顺序控制流程,同时也是CPU的基本调度单位
- 比如:
- 迅雷是一个进程,当中的多个下载任务就是多个线程
- Java虚拟机是一个进程,当中默认包含主线程(Main函数),可通过代码创建多个独立线程,与Main并发执行
- 比如:
1.2、并发与并行
- 并发:多个CPU同时执行多个任务
- 并发:单个CPU同时执行多个任务
1.3、注意
1. 进程之间内存独立不共享
2. 线程之间堆内存和方法区内存共享
- 栈内存独立,一个线程一个栈
- 单核CPU在任何时间点上,只能运行一个进程,宏观并发,微观穿行
- 单核CPU不能够做到多线程并发,但能做到多线程并的感觉,CPU处理速度极快,多个线程频繁切换
二、线程的组成与创建
2.1、基本组成部分:
CPU时间片:
- 操作系统(OS)会为每个线程分配时间
运行数据:
- 堆空间: 存储线程需使用的对象,多个线程可以共享堆中的对象
- 栈空间: 存储线程需使用的局部变量,每个线程都拥有独立的栈
2.2、创建线程的方式
2.2.1、创建线程的第一种方式---Thread
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
MyThread t1 = new MyThread();
t1.start();
//main线程不会阻塞,多核并行单核并发
for (int i = 1; i <= 20; i++) {// 线程任务
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
class MyThread extends Thread {// 线程类
public void run() {
for (int i = 1; i <= 20; i++) {// 线程任务
// 获得当前线程的线程名称
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
2.2.2、创建线程的第二种方式--接口Runnable
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
MyRunnable task = new MyRunnable();
Thread t2 = new Thread(task);
t2.start();
// main线程
for (int i = 1; i <= 20; i++) {// 线程任务
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
class MyRunnable implements Runnable {
public void run() {
for (int i = 1; i <= 20; i++) {// 线程任务
// 获得当前线程的线程名称
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
匿名内部类
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
MyRunnable task = new MyRunnable();
Thread t3 = new Thread(new Runnable(){
public void run() {
for (int i = 1; i <= 20; i++) {// 线程任务
// 获得当前线程的线程名称
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
});
t3.start();
// main线程
for (int i = 1; i <= 20; i++) {// 线程任务
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
2.2.3、创建线程的第三种方式--实现Callable接口(JDK8新特性)
-
优点:这种方式实现的线程可以获得线程的返回值
-
缺点:效率比较低
Callable接口的定义
public interface Callable<V> {
public V call() throws Exception;
}
1. JDK5加入,与Runnable接口类似,实现之后代表一个线程任务
2. 可以获得返回值
3. 可以抛出异常
4. 需要借助FutureTask类
5. 重写call()方法 相当于Runnable接口的run()
import javafx.concurrent.Task;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Thread3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个"未来任务类"对象
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
//call()相当于run()
System.out.println("begin");
Thread.sleep(1000*10);
System.out.println("end");
int a = 100;
int b = 200;
return a + b;//结果自动装箱成Integer
}
});
//创建线程对象
Thread t = new Thread(task);
//启动线程
t.start();
//在mian方法中获得返回结果
Object obj = task.get();//该方法会导致main方法受阻塞,下面的代码必须等get方法结束才能执行
System.out.println("ok");//该代码会等待get方法结束才能执行
}
}
三、线程的生命周期
3.1、线程的基本状态
在运行状态遇到阻塞事件,例如接受用户键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃当前占有的时间片,阻塞解除后回到就绪状态继续抢夺时间片,抢夺后继续上次的代码接着运行。
3.2、线程的调度
1.抢占式调度模型
那个线程的优先级高,抢到的CPU时间片的概率就会高一些,Java采用的就是该模式
2.均分式调度模型
平均分配cup时间片,每个线程占有的cup时间片时间长度一样,平均分配,一切平等
实例方法:
viod setPriority(int newPriority) 设置线程的优先级
int getPriority()获得线程优先级
最低优先级:1
默认优先级:5
最高优先级:10
3.3、常见方法
休眠:
- public static void sleep(long millis)
- 当前线程主动休眠millis毫秒(注意:1000毫秒=1秒)
- 作用:让当前线程进入休眠,进入阻塞状态,放弃占有的CPU时间片,让给其他线程使用
//public static void sleep
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
try {
Thread.sleep(1000*5);// 如果写在run方法里,那么只能通过tryCatch处理异常。因为run方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( " - ");//5秒后输出 -
}
}
面试题:
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Task());
t1.start();
try {
t1.sleep(1000*5);
// 问题:该代码会让t1线程进入休眠吗?
//答案:不会,原因是sleep是静态方法,该行代码会在运行时会制动转换成thread.sleep(1000*5),让当前线程进入休眠,也就是main线程进入休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( " - ");
}
}
class Task implements Runnable {
public void run() {
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + " - " + i);
}
}
}
终止线程的睡眠
终止睡眠,不是终止线程
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Task());
t.start();
//希望5秒后t线程能够醒来
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();//让线程睡眠代码报异常
}
}
class Task implements Runnable {//线程类
public void run() {//线程任务
System.out.println(Thread.currentThread().getName() +"begin");
try {
t1.sleep(1000*60*60*24*365);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"end");
}
}
线程让位:
-
public static void yield()
-
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
-
不是阻塞方法
-
回到就绪状状态,还是有可能再次抢到cup时间片
-
线程的礼让,让出cpu让其他线程执行,在cpu充足的情况下礼让不一定成功
* //public static void yield() public class TestYield { public static void main(String[] args) { Thread t1 = new Thread(new Task()); t1.start(); //main线程 for (int i = 1; i <= 20; i++) {//线程任务 System.out.println(Thread.currentThread().getName() + " - " + i); if (i % 10 == 0) {//放弃的条件(特定时候放弃时间片) System.out.println("main主动放弃了!"); Thread.yield();//放弃!主动放弃当前持有的时间片,回到就绪状态,进入下一次时间片的竞争! } } } } class Task implements Runnable {//线程类 public void run() {//线程任务 for (int i = 1; i <= 20; i++) { System.out.println(Thread.currentThread().getName() + " - " + i); } } }
线程插队:
-
public final void join()
-
实例方法
-
当前线程进入阻塞状态,让加入的线程执行,直到该线程结束再执行原来线程
-
类似把多线程编程单线程
public class TestJoin { public static void main(String[] args) throws InterruptedException { Thread t0 = new Thread(new Task()); t0.start(); for (int i = 1; i <= 10; i++) {//打印10个数字 System.out.println(Thread.currentThread().getName() + " - " + i);//打印线程名字和数字 if (i == 5) {//将t0加入到main线程执行流程中,等待t1线程执行结束后!main再进行竞争时间片! t0.join();//无限期等待!等待条件为调用join方法的线程执行完毕后!再进入就绪状态,竞争时间片! } } } } class Task implements Runnable {//线程实现类——Thread-0 public void run() {//线程任务 for (int i = 1; i <= 10; i++) {//打印10个数字 System.out.println(Thread.currentThread().getName() + " - " + i);//打印线程名字和数字 } } }
修改线程名字
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
Thread t0 = new Thread(new Task());
System.out.println(t0.getName() );//默认名字Thread-0
//修改名字
t0.setName("ttt")
System.out.println(t0.getName() );//ttt
//获取当前线程对象,改代码出现在主线程中,获得线程就是主线程
Thread t = Thread.currentThread()
t0.start();
}
}
class Task implements Runnable {//线程实现类——Thread-0
public void run() {//线程任务
for (int i = 1; i <= 10; i++) {//打印10个数字
System.out.println(Thread.currentThread().getName() + " - " + i);//打印线程名字和数字
}
}
}
终止线程的执行
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
Task ta = new Task()
Thread t = new Thread(ta);
t.start();
//希望5秒后终止t线程
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
方式一:
ta.runKey = false;
方式二:
t.stop();//不建议使用,会丢数据
}
}
class Task implements Runnable {//线程类
boolean runKey = true;
public void run() {//线程任务
for (int i = 1; i <= 10; i++) {//打印10个数字
if(runKey){
System.out.println(Thread.currentThread().getName() + " - " + i);//打印线程名字和数字
try {
t1.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}else{
//为保存的数据可以在这保存
return;
}
}
}
}
四、线程安全
4.1、线程安全问题
- 什么时候数据在多线程并发的环境下会存在安全问题?
三个条件:
1.多线程并发
2.有共享数据
3.共享数据有修改的行为
-
线程安全是处理成员变量问题的(实例变量 :在堆中 静态变量:在方法区)
-
在Java三大变量中
实例变量 :在堆中
静态变量:在方法区
局部变量:在栈中
以上变量,只有局部变量不会存在线程安全问题,因为局部变量不共享(一个线程一个栈)。
4.2、解决线程安全问题的方法
同步和异步的理解
-
异步编程模型
线程t1和线程t2,各自执行各自的,谁也不需要等待谁,其实就是多线程并发,效率比较高
-
同步编程模型
线程t1和线程t2,线程t1执行的时候,必须等待t2线程执行结束,或者线程t2执行的时候,必须等待t1线程执行结束,连哥哥线 程之间发生了等待关系,效率比较低,线程排队执行。
解决线程安全的核心思想:对共享的核心资源加锁,每一次只能允许一个线程访问,访问结束,才允许进入新的线程
4.2.1、同步代码块
注意:继承方式创建线程时,数据要加static实现代码块共享数据,锁对象也要加static实现代码块共享锁对象,而实现方式创建天然共享数据和共享锁对象
synchronized(共享资源的对象) {/
//代码(原子操作)
}
注意:
每个对象都有一个互斥锁标记,用来分给线程的
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块
线程退出同步代码块时,会释放相应的互斥锁标记
线程的状态(阻塞)
对于锁对象要求:理论上,锁对象只要对于当前执行的线程来说是同一个对象即可
如字符串"1",常量池中只有一个"1",但this只会让当前共享资源的线程线程排队
举例:存在线程t1,t2,t3,t4,t5
t1,t2,t3共享银行卡资源对象act
如果锁对象为this,排队的只有t1,t2,t3
如果锁对象为"1",则全部排队
举例:
//创建账户
public class Account {
@SuppressWarnings("all")
private String actno;
private double balance;
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public Account() {
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款方法
public void withdraw(double mongey){
synchronized (this){//取款前余额,此时this表示任何对象都可以来调用这个方法,任何对象都共用一把锁
double before = this.balance;
//取款后余额
double after = before - mongey;
//更新余额
this.setBalance(after);
}
}
}
//创建线程对象
public class AccountThread extends Thread{
private Account act;
public AccountThread(Account act){
this.act = act;
}
public void run (){
double money = 5000;
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "取款成功,余额="+act.getBalance());
}
}
//测试
public class AccountThread extends Thread{
private Account act;
public AccountThread(Account act){
this.act = act;
}
public void run (){
double money = 5000;
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "取款成功,余额="+act.getBalance());
}
}
//扩大同步范围
//测试板块
public class Test {
public static void main(String[] args) {
Account act = new Account("act-01",10000);
//创建2个线程
Thread t1 = new AccountThread(act);
t1.setName("t1");
Thread t2 = new AccountThread(act);
t2.setName("t2");//启动线程取款
t1.start();
t2.start();
}
//线程板块
public class AccountThread extends Thread{
private Account act;
public AccountThread(Account act){
this.act = act;
}
publdouble money = 5000;
synchronized (act){
act.withdraw(money);//把取钱的方法上锁,这里不能写this,this分别是t1,t2不同对象只能是act公用
}
System.out.println(Thread.currentThread().getName() + "取款成功,余额="+act.getBalance());
}
}
// 账户板块
public class Account {
@SuppressWarnings("all")
private String actno;
private double balance;
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public Account() {
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款方法
public void withdraw(double mongey){
//取款前余额
double before = this.balance;
//取款后余额
double after = before - mongey;
//更新余额
this.setBalance(after);
}
}
4.2.2、同步方法
synchronized 返回值类型 方法名称(形参列表0) {
//对当前对象(this)加锁,只能是this
//代码(原子操作)
}
缺点:
1. 整个方法都需要同步,会扩大同步范围,导致程序执行效率降低,所以不常用
2.静态方法 锁加在类上,锁是类名.class
3.非静态方法 锁加在当前对象上 默认this
优点:
代码量少
已知JDK中线程安全的类:
StringBuffer
Vector
Hashtable
以上类中的公开方法,均为synchonized修饰的同步方法synchronized实例
//测试板块
public class Test {
public static void main(String[] args) {
Account act = new Account("act-01",10000);
//创建2个线程
Thread t1 = new AccountThread(act);
t1.setName("t1");
Thread t2 = new AccountThread(act);
t2.setName("t2");//启动线程取款
t1.start();
t2.start();
}
//线程板块
public class AccountThread extends Thread{
private Account act;
public AccountThread(Account act){
this.act = act;
}
publdouble money = 5000;
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "取款成功,余额="+act.getBalance());
}
}
// 账户板块
public class Account {
@SuppressWarnings("all")
private String actno;
private double balance;
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public Account() {
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取款方法
public synchronized void withdraw(double mongey){
//取款前余额
double before = this.balance;
//取款后余额
double after = before - mongey;
//更新余额
this.setBalance(after);
}
}
4.2.3、Lock接口
- JDK5加入,与synchronized比较,显示定义,结构更灵活
- 提供更多使用性方法,功能更强大、性能更优越
- 常用方法:
- void lock() //获取锁,如锁被占用,则等待
- boolean tryLock() //尝试获取锁(成功返回true。失败返回false,不阻塞)
- void unlock() //释放锁
Lock接口的使用
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLocks {
public static void main(String[] args) {
Test obj = new Test();
Thread t1 = new Thread(new MyTask(obj));
Thread t2 = new Thread(new MyTask2(obj));
t1.start();
t2.start();
}
}
class Test {
// 接口引用 实现类对象 创建一把锁
Lock lock = new ReentrantLock();// Test里有一把锁的属性,就是Test自身的锁!
// ReentrantLock rl = new ReentrantLock();
// 第一、使用Lock,需要明确的写上锁和释放锁!
// 第二、为了避免拿到锁的线程在运行期间出现异常,导致程序终止,没有释放锁!应用try{}finally{}来保证,无论正确执行与否,最终都会释放锁!
public void method() {
System.out.println(Thread.currentThread().getName() + "进入到上锁的方法里!");
try {
lock.lock();// 显示的写上 在此处获得锁
// 模拟程序出错!
// int a = 10/0;
// method();不要出现无穷递归!容易内存溢出!导致锁一直没有释放!
System.out.println(Thread.currentThread().getName() + "退出了上锁的方法里!");
} finally {
// 显示的写上,此处释放锁
lock.unlock();
}
}
}
class MyTask implements Runnable {
Test obj;// 共享资源对象
public MyTask(Test obj) {
this.obj = obj;
}
public void run() {
obj.method();
}
}
class MyTask2 implements Runnable {
Test obj;// 共享资源对象
public MyTask2(Test obj) {
this.obj = obj;
}
public void run() {
obj.method();
}
}
重入锁
ReenTrantLock: Lock接口的实现类,与synchronized一样具有互斥锁功能
读写锁
ReentrantReadWriteLock:
一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁
互斥规则:
写-写: 互斥,阻塞
读-写: 互斥,读阻塞写、写阻塞读
读-读: 不互斥、不阻塞
在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率
读写锁案例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestReadWriteLock {
public static void main(String[] args) {
Student student = new Student();//共享资源对象
ExecutorService executorService = Executors.newFixedThreadPool(20);
WriteTask writeTask = new WriteTask(student);//写线程任务
ReadTask readTask = new ReadTask(student);//读线程任务
//执行的两个赋值的线程任务
Long start = System.currentTimeMillis();//开始时间(毫秒值)
executorService.submit(writeTask);
executorService.submit(writeTask);
for (int i = 1; i <= 18; i++) {
executorService.submit(readTask);
}
//停止线程池,但是不停止已提交的任务!等已提交任务都执行完毕!
executorService.shutdown();
//询问线程池,任务结束了吗?
while (true) {
System.out.println("任务结束了吗?");
if (executorService.isTerminated() == true) {//证明线程池里的任务都执行完毕
break;
}
}
Long end = System.currentTimeMillis();//结束时间
System.out.println(end - start);
}
}
class Student {
private int age; //年龄
// Lock lock = new ReentrantLock();//读和写的操作下,都锁住,性能过低!
//有两把锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.WriteLock readLock = reentrantReadWriteLock.writeLock();//读锁
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();//写锁
//赋值——写操作
public void setAge(int age) {
writeLock.lock();
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.age = age;
} finally {
writeLock.unlock();
}
}
//取值——读操作
public int getAge() {
readLock.lock();
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.age;
} finally {
readLock.unlock();
}
}
}
//写操作任务
class WriteTask implements Callable {
Student student;
public WriteTask(Student student) {
this.student = student;
}
public Student call() throws Exception {
student.setAge(100);
return null;
}
}
class ReadTask implements Callable {
Student student;
public ReadTask(Student student) {
this.student = student;
}
public Student call() throws Exception {
student.getAge();
return null;
}
}
面试题1
问:doOther方法执行的时候需要等待doSome方法的结束吗?
答:不需要,下面的方法没有锁
public class Test {
public static void main(String[] args) {
MyClass mc = new MyClass();
Thread t1 = new MyThread(mc);
t1.setName("t1");
Thread t2 = new MyThread(mc);
t2.setName("t2");
t1.start();
t2.start();
}
class MyThread extends Thread{
private MyClass mc;
public MyThread( MyClass mc){
this.mc = mc;
}
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass {
public synchronized void doSome(){
System.out.println("doSome");
}
public void doOher(){
System.out.println("doOher");
}
}
面试题2
问:doOther方法执行的时候需要等待doSome方法的结束吗?
答:需要,都有锁
public class Test {
public static void main(String[] args) {
MyClass mc = new MyClass();
Thread t1 = new MyThread(mc);
t1.setName("t1");
Thread t2 = new MyThread(mc);
t2.setName("t2");
t1.start();
t2.start();
}
class MyThread extends Thread{
private MyClass mc;
public MyThread( MyClass mc){
this.mc = mc;
}
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass {
public synchronized void doSome(){
System.out.println("doSome");
}
public synchronized void doOher(){
System.out.println("doOher");
}
}
面试题3
问:doOther方法执行的时候需要等待doSome方法的结束吗?
答:不需要,锁对象不唯一
public class Test {
public static void main(String[] args) {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new MyThread(mc1);
t1.setName("t1");
Thread t2 = new MyThread(mc2);
t2.setName("t2");
t1.start();
t2.start();
}
class MyThread extends Thread{
private MyClass mc;
public MyThread( MyClass mc){
this.mc = mc;
}
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass {
public synchronized void doSome(){
System.out.println("doSome");
}
public synchronized void doOher(){
System.out.println("doOher");
}
}
面试题3
问:doOther方法执行的时候需要等待doSome方法的结束吗?
答:需要,synchronized出现在静态方法上,为类锁,锁对象为一类,不是一个实例
public class Test {
public static void main(String[] args) {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new MyThread(mc1);
t1.setName("t1");
Thread t2 = new MyThread(mc2);
t2.setName("t2");
t1.start();
t2.start();
}
class MyThread extends Thread{
private MyClass mc;
public MyThread( MyClass mc){
this.mc = mc;
}
public void run(){
if(Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass {
public synchronized static void doSome(){
System.out.println("doSome");
}
public synchronized static void doOher(){
System.out.println("doOher");
}
}
线程的逻辑代码
run()和start()区别
- start()方法的作用是:启动一个分支线程,在jvm中开辟一个新的栈空间,这但代码任务完成后,瞬间就结束了。
- 调用底层:start()》start0()》底层c/c++
- run()等于是一个普通方法的调用,不会开启新线程
4.3、死锁
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象标记时,产生死锁
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new MyThread1(o1,o2);
Thread t2 = new MyThread2(o1,o2);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread{
Object o1;
Object o2;
public MyThread2(Object o1,Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (o1){
}
}
}
}
4.4、开发中解决线程安全的方案
synchronized会让程序执行效率降低,用户体验感不好在不得已的情况下再选择线程同步机制。
- 第一种方案:尽量使用局部变量代替实例变量和静态变量
- 第二种方案:如果是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享
- 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候只能选择synchronized,线程同步机制
4.5、释放锁
-
当前线程的同步方法、同步代码块执行结束释放锁
-
当前线程的同步方法、同步代码块执行遇到break、return
-
当前线程的同步方法、同步代码块执行遇到error、exception导致异常结束
-
当前线程的同步方法、同步代码块执行遇到线程对象执行wait()方法,当前线程暂停释放锁
-
线程执行同步方法、同步代码块时,遇到Thread.sleep()、Thread.yield()当前线程暂停但不会释放锁
-
线程执行同步代码块时,其他线程调用该线程的suspend()方法将该线程挂起,该线程不会释放锁(提示:应避免使用挂起suspend()、结束挂起resume()来控制线程
五、守护线程
守护线程分类
Java语言中的线程分为两个类:
用户线程和守护线程(后台线程)
守护线程最具代表性的是:垃圾回收线程
守护线程特点:
1.一般是个死循环
2.所有的用户线程结束,守护线程自动结束
注意:主线程main方法是一个用户线程
场景
1.定时备份数据,会用到定时器,并且可以将定时器设置为守护线程
语法:
线程启动前加上
线程对象名.setDaemon(true)
六、定时器
作用:间隔特定的时间,执行特定的程序
实现方式:
1.sleep方法
2.在java类库中已经有一个定时器:java.util.Timer 可以直接使用
3.目前使用最多的是Spring框架提供的SpringTask框架
代码演示
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) throws Exception {
//c创建定时器对象
Timer timer = new Timer();
//Timer timer = new Timer(true);//守护线程方式
//指定定时任务
SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sim.parse("2022-07-06 01:14:00");
timer.schedule(new LogTimerTask(),firstTime,1000*1);
}
}
//编写一个定时任务类,记录日志的
class LogTimerTask extends TimerTask{
@Override
public void run() {
//编写需要执行的任务
SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH : mm:ss:SS");
String strTime = sim.format(new Date());
System.out.println(strTime + "ok");
System.out.println("ok");
}
}
六、线程通信
基本介绍
注意:
- 线程通信只能出现在同步代码块或者同步方法中,lock也不行
- 这三个方法的调用者必须是锁对象,当是this的时候可以省略
- 这三个方法定义在Object类
等待:暂停当前线程使得当前线程进入阻塞状态并释放锁
public final void wait()
public final void wait(long timeout)
必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在
obj的等待队列中。释放锁,进入等待队列
通知:唤醒其他线程
public final void notify() //释放一个,唤醒优先级最高的线程
public final void notifyAll() //释放全部,唤醒之前沉睡的所有线程
必须在对obj加锁的同步代码块中。从obj的Waiting中释放一个或全部线程。对自身没有任何影响
注意:
1.wait和notify方法不是线程对象的方法,是Java对象中任何一个Java对象都有的方法,是Object类中自带的
2.wait和notify方法不是通过线程对象调用的
3.Object o = new Object();
o.wait();
表示让正在o对象上活动的线程t进入等待状态,无期限等待,并且释放掉t线程之前占有的o对象的锁,
直到用o.notify()唤醒,该唤醒只会通知,不会释放o对象上之前占有的锁
4.wait和notify方法是建立在线程同步的基础之上
复杂:一个线程持有A对象的锁,需要B对象的锁, 另一个线程持有B、想要A
简单:一个线程持有A对象的锁,另一个线程也想要!阻塞
情况1:三个线程抢锁,其中一个线程或两个线程把所让给了其他线程(wait),
然后另外的线程就开始抢锁直至执行结束——死锁(没有notify唤醒)
情况2:三个线程抢锁,其中一个线程或两个线程把所让给了其他线程(wait),
然后另外的线程就开始抢锁并其中的一个或者最后一个notify唤醒——有可能出现死锁(因为notify是随机唤醒)
情况3:三个线程抢锁,其中一个线程或两个线程把所让给了其他线程(wait),
然后另外的线程就开始抢锁并其中的一个或者最后一个notifyAll唤醒——正常执行(因为notifyAll是唤醒全部等待的 线程)
实现生产者和消费者
import java.util.ArrayList;
import java.util.List;
public class Text {
public static void main(String[] args) {
/*
需求:仓库List集合,且只能存储一个元素,个数为0表示仓库空了,效果是生产一个消费一个
*/
//创建共享仓库对象
List list = new ArrayList();
//创建两个线程对象
Thread t1 = new Thread(new Produce(list));
Thread t2 = new Thread(new Consumer(list));
t1.setName("生产者线程");
t2.setName("消费者线程");
t1.start();
t2.start();
}
}
//生产线程
class Produce implements Runnable{
private List list;
public Produce(List list) {
this.list = list;
}
@Override
public void run() {
//一直生产,使用死循环模拟
while (true){
//给仓库对象加锁
synchronized (list){
if(list.size() > 0){
//当前线程进入等待状态,并且释放list的锁
try {
list.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
Object obj = new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName() + "==="+obj);
//唤醒消费者消费
list.notify();
}
}
}
}
//消费线程
class Consumer implements Runnable{
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
//一直消费
while (true){
synchronized (list){
if(list.size() == 0){
//仓库已空,当前线程进入等待状态,并且释放list的锁
try {
list.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//有数据,能消费
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName() + "==="+obj);
//唤醒生产者生产
list.notify();
}
}
}
}
七、线程池
7.1、线程池概念
- 线程容器,可设定线程分配的数量上限
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象
- 避免频繁的创建和销毁线
7.2、程池原理
将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程
7.3、获取线程池
常用的线程池接口和类(所在包java.util.concurrent):
- Executor: 线程池的顶级接口
- ExecutorService: 线程池接口,可通过submit(Runnable task)提交任务代码
- Executors工厂类: 通过此类可以获得一个线程池
- 通过newFixedThreadPool(int nThreads) 获得固定数量的线程池。参数: 指定线程池中线程的数量
- 通过newCachedThreadPool() 获取动态数量的线程池,如不够创建新的,没有上限
7.3.1、newFixedThreadPool()的使用
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCallable {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(3);
MyTask task = new MyTask();
es.submit(task);
}
}
class MyTask implements Callable<Integer> {
public Integer call() throws Exception {
for (int i = 0; i <= 20; i++) {
if (i == 30) {
Thread.sleep(1000);
}
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return null;
}
}
7.3.2、Future接口
概念: 异步接收ExecutorService.submit() 所返回的状态结果,当中包含了call() 的返回值
方法: V get() 以阻塞形式等待Future中的异步处理结果(call()的返回值)
线程的同步
同步:
形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续
注意:单条执行路径
线程的异步
异步:
形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立即返回。二者竞争时间片,并发执行
注意:多条执行路径
Future接口的使用
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestFuture {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(2);
MyCall1 mc1 = new MyCall1();
MyCall2 mc2 = new MyCall2();
//通过submit执行提交的任务,Future接受返回的结果
Future<Integer> result1 = es.submit(mc1);
Future<Integer> result2 = es.submit(mc2);
//通过Future的get方法,获得线程执行完毕后的结果
Integer value1 = result1.get();
Integer value2 = result2.get();
System.out.println(value1 + value2);
}
}
//计算1~50的和
class MyCall1 implements Callable<Integer> {
public Integer call() throws Exception {
Integer sum = 0;
for (int i = 1; i <= 50; i++) {
sum += i;
}
return sum;
}
}
//计算1~50的和
class MyCall2 implements Callable<Integer> {
public Integer call() throws Exception {
Integer sum = 0;
for (int i = 1; i <= 50; i++) {
sum += i;
}
return sum;
}
}
7.3.3、newCachedThreadPool()的使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
public static void main(String[] args) {
// 线程池(引用) ---->Executors工具类(工厂类)
// ExecutorService es = Executors.newFixedThreadPool(3);//手动限定线程池里的线程数量
ExecutorService es = Executors.newCachedThreadPool();// 动态获取数量线程池
// 1.创建任务对象
MyTask1 task = new MyTask1();
// 2.将任务提交到线程池,由线程池调度、执行
es.submit(task);// Runnable类型的对象
es.submit(task);
es.submit(task);
}
}
// 线程任务
class MyTask1 implements Runnable {
public void run() {
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~