线程
两者定义
- 进程:一个应用程序(一个进程就是一个软件)
- 线程:一个进程中的执行单元
- 一个进程中可以启动多个线程
两者关系
- 进程可以看做一个公司
- 线程可以看做公司员工
注
- 进程之间的内存独立不恭喜啊
- 线程在java语言中:
- 共享堆内存和方法区
- 栈内存独立,一个线程一个栈
- 如果启动10个线程,就会开辟10个栈空间,每个栈之间互不干扰,各自执行,这就是多线程并发
目的
- 提高程序的处理效率
思考
- 在使用线程机制之后,如果main()方法结束了,是不是程序并不结束
- main方法结束只是主线程结束了,即代表主栈空了,其他的栈可能仍旧在弹栈和压栈
实现线程的三种方式
- 判断下述代码有几个线程
package Day024多线程;
public class Test01 {
public static void main(String[] args) {
System.out.println("main begin");
m1();
System.out.println("main over");
}
private static void m1() {
System.out.println("m1() begin");
m2();
System.out.println("m1() over");
}
private static void m2() {
System.out.println("m2() begin");
m3();
System.out.println("m2() over");
}
private static void m3() {
System.out.println("m3() execute");
}
}
三种方式
- 编写一个类,继承java.lang.Thread,重写run()方法
- 编写一个类,实现java.ang.Runnable接口,实现run()方法
- 实现Callable接口
实现线程的第一种方式
- Java支持多线程机制,且Java已经将多线程实现了,只需要继承即可
步骤:
- 编写一个类,继承Thread,重写run
- 创建一个线程对象
- new一个线程对象
- 启动线程
- 使用用start()方法
package Day024多线程;
public class Test02 {
//mian方法在主栈中执行,属于主线程
public static void main(String[] args) {
//2. 创建一个线程对象
MyThread myThread = new MyThread();
//3. 启动线程
myThread.start();
for (int i = 0; i < 100; i++) {
System.out.println("main ---> " + i);
}
}
}
//1. 编写一个类,继承Thread,重写run
class MyThread extends Thread{
@Override
public void run() {
//这段代码在分支线程中执行
for (int i = 0; i < 100; i++) {
System.out.println("MyThread ---> " + i);
}
}
}
start()方法
- start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间,开辟之后瞬间结束,线程也就启动成功了
- 线程启动成功之后就会自动调用run()方法,run方法于分支线程好比是main方法于主线程
- 如果直接调用run方法与调用start方法的区别是:编辑器会认为run方法是一个普通方法,就不会有新的栈空间开辟,也就不存在多线程
实现线程的第二种方式
步骤:
- 编写一个类,实现Runnable,实现run方法
- 该类不是一个线程类,仅仅是一个可运行的类
- 创建一个可运行对象
- 将该对象封装成一个线程对象
- 启动线程
package Day024多线程;
public class Test03 {
public static void main(String[] args) {
//2. 创建一个可运行对象
//MyRunnable myRunnable = new MyRunnable();
//3. 将该对象封装成一个线程对象
//Thread t = new Thread(myRunnable);
//合并2,3
Thread t = new Thread(new MyRunnable());
//4. 启动线程
t.start();
//主线程的程序
for (int i = 0; i < 100; i++) {
System.out.println("main ---> " + i);
}
}
}
//1. 编写一个类,实现Runnable,实现run方法
class MyRunnable implements Runnable{ //这不是一个线程类,只是一个可运行的类
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("MyRunnable ---> " + i);
}
}
}
注:这种实现接口的方式比较常用,因为一个类实现了接口不影响它去继承其他类,更加的灵活
使用匿名内部类的方式实现线程
package Day024多线程;
public class Test04 {
public static void main(String[] args) {
//创建类,使用匿名内部类的方式
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Runnable ---> " + i);
}
}
});
//启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("main ---> " + i);
}
}
}
- 在实例化Thread对象的时候,传入new Runnable对象及其run方法
实现线程的第三种方式
见后续
线程的生命周期
- 新建状态
- 刚new出来的线程对象
- 调用start方法
- 就绪状态
- 就绪状态亦叫“可运行状态”,表示当前的线程具有抢夺CPU时间片的资格(CPU时间片就是执行权)
- 当一个线程抢到CPU时间片后,就开始执行run方法,即代表线程进入运行状态
- 运行状态
- run方法的执行标志线程进入运行状态,当之前的CPU时间片用完之后,就会重新回到就绪状态
- 重复就绪、运行,直到run方法执行结束或者遇到阻塞事件
- 重复运行的时候,是接着上次的代码继续执行
- 情况1:run方法执行结束,线程进入死亡状态
- 死亡状态
- 情况2:遇到阻塞事件,进入阻塞状态
- 阻塞状态
- 线程遇到阻塞状态的时候,就会放弃之前占有的CPU时间片,直至阻塞接触
- 但是因为之前的CPU时间片已经没有了,所以会直接回到就绪状态,重写争抢CPU时间片
基础的三个方法
- 获取当前线程对象 —— currentThread()
- 获取线程对象名字 —— getName()
- 修改线程对象名字 —— setName()
package Day024多线程;
public class Test05 {
public static void main(String[] args) {
//获取当前线程对象
Thread thread1 = Thread.currentThread();
System.out.println(thread1);
//new线程对象
Thread thread = new Thread(new MyRunnable1());
//修改线程对象名字
thread.setName("t1");
//获取线程对象名字
String name = thread.getName();
System.out.println(name);
//启动线程
thread.start();
//再new一个线程对象
Thread thread2 = new Thread(new MyRunnable1());
//修改线程名字
thread2.setName("t2");
//获取并输出线程对象名字
System.out.println(thread2.getName());
//启动线程
thread2.start();
for (int i = 0; i < 100; i++) {
System.out.println("main ---> " + i);
}
}
}
class MyRunnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("MyRunnable1 ---> " + i);
}
//获取当前线程对象
String name = Thread.currentThread().getName();
System.out.println("---> " + name);
/*
* currentThread指的是当前对象,那么在分支线程中,当前对象是谁呢?
* 谁启动run方法,currentThread就指向谁
* t1线程执行了run方法,那么当前线程就是t1
* t2线程执行了run方法,那么当前线程就是t2
* */
}
}
线程的睡眠
sleep方法
- static void sleep(long millis)
- 所以可以知道,sleep方法是静态方法
- Thread.sleep();进行调用
- 参数是毫秒
- 作用:让当前线程进入休眠(阻塞状态),放弃CPU占有时间片,让给其他线程使用
- 可以达到这种效果:间隔特定时间,去执行一段特定的代码
package Day024多线程;
public class Test06 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("over");
}
}
- 面试题
package Day024多线程;
public class Test07 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable3());
t.setName("t");
t.start();
//调用sleep方法
try {
t.sleep(1000);
//会让程序进去阻塞状态(休眠)吗?
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("over");
}
}
class MyRunnable3 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " ---> " + i);
}
}
}
终止线程睡眠
- 是终止线程睡眠,而不是终止线程
package Day024多线程;
public class Test08 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable4());
t.setName("t");
t.start();
//睡眠5秒后醒来
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断t线程睡眠(利用了Java的异常处理机制)
t.interrupt();
}
}
class MyRunnable4 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "begin");
try {
//睡眠一年
Thread.sleep(1000 * 60 * 60 * 24 * 365);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "over");
}
}
终止线程执行
强行终止
注:该方法存在很大的缺点,容易丢失数据,因为这种方式是直接将线程杀死了,所以线程没有保存的数据就会丢失,不建议使用
package Day024多线程;
public class Test09 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable5());
t.setName("t");
t.start();
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后,线程强制终止
t.stop(); //已经过时,不建议使用
}
}
class MyRunnable5 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " ---> " + i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
合理终止
package Day024多线程;
public class Test010 {
public static void main(String[] args) {
MyRunnable6 myRunnable6 = new MyRunnable6();
Thread t = new Thread();
t.setName("t");
t.start();
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止线程(什么时候终止t的执行,把标记改成false就可以了)
myRunnable6.run = false;
}
}
class MyRunnable6 implements Runnable{
//打一个布尔标记
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (run){
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
return;
}
}
}
}
线程调度
常见线程调度模型
- 抢占式调度模型
- 线程的优先级越高,抢到CPU时间片的概率就高一些
- Java就是采用抢占式调度模型
- 均分式调度模型
- 平均分配CPU时间片,每个线程占有CPU时间的时间一样
线程调度的方法
修饰符或类型 | 方法名 | 作用 | |
---|---|---|---|
void | setPriority(int newPriority) | 设置线程的优先级 | |
int | getPriority() | 获取线程优先级 | 最低\高优先级是1\10,默认是5 |
static void | yield() | 暂停当前正在执行的线程对象,并执行其他线 | yield()方法不是阻塞方法,让当前线程从“运行状态”回到“就绪状态”,在回到就绪后可能还会抢到 |
void | join() | 合并线程 |
线程优先级
- 线程的优先级越高,抢到的CPU时间片相对于多一点
package Day024多线程;
public class Test011 {
public static void main(String[] args) {
//获取当前线程对象
Thread thread = Thread.currentThread();
//获取当前线程优先级
System.out.println(thread.getPriority());
//新建线程对象
Thread t1 = new Thread(new MyRunnable7());
//设置t线程的优先级为10
thread.setPriority(10);
t1.setName("t");
t1.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class MyRunnable7 implements Runnable{
@Override
public void run() {
//获取线程优先级
System.out.println("线程" + Thread.currentThread().getName() + "优先级是:" + Thread.currentThread().getPriority());
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
线程让位
- 让当前线程暂停,回到就绪状态,让给其他线程
package Day024多线程;
public class Test012 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable8());
t.setName("t");
t.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
class MyRunnable8 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 100 == 0){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
线程安全问题
注:
- 在以后的开发中,项目都是在服务器中运行的,而服务器已经将线程定义了,线程对象的创建,线程的启动等,都已经完成了。所以这些代码是不需要写的
- 所以程序员应该更加关注的是线程安全问题
- 因为编写的程序是放在一个多线程环境下运行的,所以必须得处理好数据在多线程并发环境下是否安全的问题
线程不安全的条件
- 多线程并发
- 存在共享数据
- 数据有修改的操作
满足上述三个条件后,就会出现线程安全问题
解决线程安全问题
- 解决方法:线程排队执行(不能并发)
- 这种机制被称为:线程同步机制
- 专业术语叫做:线程同步
- 就是线程不能并发,必须排队执行
当线程开始排队执行的时候,就会牺牲一部分效率,但是数据安全了
同步和异步
- 异步编程模型
- 线程t1和线程t2,各自执行,互不相干
- 即:多线程并发(效率较高)
- 同步编程模型
- 线程t1和线程t2,在同一时间只能执行一个线程
- 即:线程排队执行(效率较低)
账户对象的例子
在不适用线程同步机制的情况,多线程对同一个账户进行取款,出现线程安全问题
账户:
package Day024多线程;
public class Account {
//账户
private String account;
//余额
private int balance = 10000;
//构造方法
public Account() {
}
public Account(String account) {
this.account = account;
}
public Account(String account, int balance) {
this.account = account;
this.balance = balance;
}
//set and get
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
//取款的方法
public void withdrawal(int money){
//取钱之后的余额
int after = getBalance() - money;
//睡眠5秒,模拟网络延迟
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额,网络延迟导致t1取完钱后,数据没有上传更新,t2在这段时间取钱了
setBalance(after);
}
}
线程:
package Day024多线程;
public class AccountThread implements Runnable {
//线程共享一个账户对象
private Account account;
public AccountThread(Account account) {
this.account = account;
}
//run方法表示取款操作
public void run() {
//取款5000元
int money = 5000;
account.withdrawal(money);
System.out.println(account.getAccount() + "取款:" + money + ",余额:" + account.getBalance());
}
}
测试:
package Day024多线程;
public class AccountTest {
public static void main(String[] args) {
//实例化Account对象
Account account = new Account("张三");
//实例化线程对象
Thread t1 = new Thread(new AccountThread(account));
//实例化另外一个线程对象,并让两个线程操作同一个账户
Thread t2 = new Thread(new AccountThread(account));
//线程改名
t1.setName("t1");
t2.setName("t2");
//线程启动
t1.start();
t2.start();
/*
张三取款:5000,余额:5000
张三取款:5000,余额:5000
这种情况就导致t1和t2处均取出5000块,而在银行数据缺没有更新
* */
}
}
使用线程同步机制,让数据变安全
用户
package Day024多线程;
public class Account2 {
//账户
private String account;
//余额
private int balance = 10000;
//构造方法
public Account2() {
}
public Account2(String account) {
this.account = account;
}
public Account2(String account, int balance) {
this.account = account;
this.balance = balance;
}
//set and get
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
//取款的方法
public void withdrawal(int money){
//下列代码块中的代码在线程中必须排队
synchronized (this){
//取钱之后的余额
int after = getBalance() - money;
//睡眠5秒,模拟网络延迟
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额,网络延迟导致t1取完钱后,数据没有上传更新,t2在这段时间取钱了
setBalance(after);
}
}
}
线程
package Day024多线程;
public class AccountThread2 implements Runnable{
private Account2 account;
public AccountThread2() {
}
public AccountThread2(Account2 account) {
this.account = account;
}
@Override
public void run() {
int money = 5000;
account.withdrawal(money);
System.out.println(account.getAccount() + "取款:" + money + ",余额:" + account.getBalance());
}
}
测试
package Day024多线程;
public class AccountTest2 {
public static void main(String[] args) {
Account2 account = new Account2("张三");
Thread t1 = new Thread(new AccountThread2(account));
Thread t2 = new Thread(new AccountThread2(account));
t1.start();
t2.start();
}
}
对synchronized的理解
public void withdrawal(int money){
synchronized (this){
int after = getBalance() - money;
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBalance(after);
}
}
- 上述代码使用了线程同步机制,即只有当一个线程执行完成之后,另外一个线程才能继续执行
- 语法:
synchronized( /*共享对象*/ ){
//线程同步代码块
}
-
共享对象是写什么?
- 需要同步线程的共同作用的对象
- 例如:有 t1 t2 t3 三个线程,现在只需要t1和t2排队,t3不同排队
- 共享对象就写成t1和t2的共享对象,而这个对象对于t3来讲是不共享的
- withdrawa方法中共享的对象是:账户对象,
- this代表的是当前对象,当前对象就是账户对象,所以可以填入this
- 要根据情况而论,不一定是this,只要是多线程共享的那个对象即可
-
java语言中,所有对象都有一个标记,习惯称为“锁”
- 100个对象就有100个锁,1个对象就有1个锁
-
上述withdrawa方法的执行原理:
- 线程t1和线程t2并发,执行取款操作的时候,会有一前一后
- 假设t1先执行了,遇到了synchronized,就会自动去找“共享对象”的对象锁,找到之后,在线程同步代码块中的代码执行的过程中,就一直占用这把锁
- t2再遇到synchronized的时候,也会去想去占有这把锁,但是因为t1已经占用了共享对象的锁,所以t2线程只能在同步代码块外面等待t1执行完成
- t1执行完同步代码块后,会归还锁,然后t2再占有这把锁,t2就开始执行同步代码块中的代码
-
所以说,共享对象一定要选取好
变量线程安全问题
- Java的三大变量:实例变量、静态变量、局部变量
- 局部变量永远不会出现线程安全问题,为什么?
使用局部变量的话,建议使用:StringBuilder
synchronized出现在实例方法上
- synchronized出现在实例方法上,就一定是给this上锁了,
- 所以也就导致了调用的时候十分不灵活
- 这样也可能扩大同步的范围,导致程序的执行效率降低,所以并不常用
- 优点:代码写得少了
- 实例:
package Day024多线程;
public class Account3 {
public static void main(String[] args) {
Account3 a = new Account3("张三", 10000);
Thread t1 = new Thread(new AccountThread3(a));
Thread t2 = new Thread(new AccountThread3(a));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
private String name;
private double balance;
public Account3() {
}
public Account3(String name, double balance) {
this.name = name;
this.balance = balance;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
//取钱
public synchronized void withdraw(double money){
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBalance(getBalance() - money);
}
}
class AccountThread3 implements Runnable{
private Account3 account;
public AccountThread3(Account3 account) {
this.account = account;
}
public Account3 getAccount() {
return account;
}
public void setAccount(Account3 account) {
this.account = account;
}
@Override
public void run() {
double money = 5000;
account.withdraw(money);
System.out.println(account.getName() + "取款:" + money + ",余额:" + account.getBalance());
}
}
面试题01
- 在doSome执行的时候,需要等待doOther执行结束吗?
package Day024多线程;
public class Test014 {
public static void main(String[] args) {
Myclass mc = new Myclass();
MyThread01 mt1 = new MyThread01(mc);
MyThread01 mt2 = new MyThread01(mc);
mt1.setName("mt1");
mt2.setName("mt2");
mt1.start();
mt2.start();
}
}
class MyThread01 extends Thread{
private Myclass mc;
public MyThread01(Myclass mc) {
this.mc = mc;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("mt1")){
mc.doSome();
}
if (Thread.currentThread().getName().equals("mt2")){
mc.doOther();
}
}
}
class Myclass {
public synchronized void doSome(){
System.out.println("doSome...begin");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome...over");
}
public void doOther(){
System.out.println("doOther...");
}
}
- 因为doOther方法并没有synchronized关键字锁住对象,所以实行doOther的时候,不需要共享对象锁
面试题02
- 执行doSome的时候,需要等待doOther吗
package Day024多线程;
public class Test015 {
public static void main(String[] args) {
MyClass mc = new MyClass();
MyThread2 t1 = new MyThread2(mc);
MyThread2 t2 = new MyThread2(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class MyThread2 extends Thread{
private MyClass mc;
public MyThread2(MyClass mc) {
this.mc = mc;
}
@Override
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...begin");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome...over");
}
public synchronized void doOther(){
System.out.println("doOther...begin");
}
}
- 会,因为doOther方法也被对象锁锁住了
面试题03
- 执行doSome的时候,需要等待doOther吗
package Day024多线程;
public class Test016 {
public static void main(String[] args) {
MyClass2 mc1 = new MyClass2();
MyClass2 mc2 = new MyClass2();
MyThead3 mt1 = new MyThead3(mc1);
MyThead3 mt2 = new MyThead3(mc2);
mt1.setName("t1");
mt2.setName("t2");
mt1.start();
mt2.start();
}
}
class MyThead3 extends Thread{
private MyClass2 mc;
public MyThead3(MyClass2 mc) {
this.mc = mc;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")){
mc.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
mc.doOther();
}
}
}
class MyClass2{
public synchronized void doSome(){
System.out.println("doSome...begin");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome...over");
}
public synchronized void doOther(){
System.out.println("doOther...begin");
}
}
- 不会,因为作用的对象都不是同一个
面试题04
- 执行doSome的时候,需要等待doOther吗
package Day024多线程;
public class Test017 {
public static void main(String[] args) {
MyClass3 mc1 = new MyClass3();
MyClass3 mc2 = new MyClass3();
MyThead4 t1 = new MyThead4(mc1);
MyThead4 t2 = new MyThead4(mc2);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class MyThead4 extends Thread{
private MyClass3 mc;
public MyThead4(MyClass3 mc) {
this.mc = mc;
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("t1")){
MyClass3.doSome();
}
if (Thread.currentThread().getName().equals("t2")){
MyClass3.doOther();
}
}
}
class MyClass3{
public synchronized static void doSome(){
System.out.println("doSome...begin");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome...over");
}
public synchronized static void doOther(){
System.out.println("doOther...begin");
}
}
- 需要,因为synchronized修饰的静态方法,静态方法是类锁,不管创建了几个对象,类锁只有一把
死锁
- 要求必须会写
- 注:synchronized在开发中最好不要嵌套使用,因为一不小心就很容易导致死锁现象发生
实例
package Day024多线程;
public class Test018 {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
MyThread02 mt1 = new MyThread02(o1, o2);
MyThread03 mt2 = new MyThread03(o1, o2);
mt1.setName("t1");
mt2.setName("t2");
mt1.start();
mt2.start();
}
}
class MyThread03 extends Thread{
Object o1;
Object o2;
public MyThread03(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1){
try {
Thread.sleep(1000 * 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread02 extends Thread{
Object o1;
Object o2;
public MyThread02(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2){
try {
Thread.sleep(1000 * 3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
- 分析:
- 当mt1和mt2两个线程开始的时候,
- mt1锁住了o1对象
- mt2锁住了o2对象
- 然后两个都开始睡眠
- 睡醒之后mt1发现o2对象被锁住,进入等待
- mt2发现o1对象被锁住,继续进入等待
- 于是“死锁”就发生了
解决线程安全问题
- 方案一:使用局部白能力代码“实例变量”和“静态变量”
- 方案二:如何必须使用实例变量,就考虑创建多个对象,如此一来实例变量的内存就不共享了
- 方案三:如果不能使用局部变量,对象也不能创建多个的情况再使用 synchronized 关键字(线程同步机制)
线程的其他内容
守护线程
- Java中线程的种类
- 用户线程
- 守护线程(后台线程,例如:垃圾回收器)
- 特点:
- 一般都是死循环
- 所有的用户线程结束,守护线程自动结束
- 实例
package Day024多线程;
public class Test019 {
public static void main(String[] args) {
MyThread3 t1 = new MyThread3();
t1.setName("t1");
t1.setDaemon(true);
t1.start();
for (int i = 0; i < 100; i++) {
System.out.println("main --->" + i);
}
}
}
class MyThread3 extends Thread{
@Override
public void run() {
int i = 0;
while (true){
System.out.println(Thread.currentThread().getName() + "--->" + (++i));
}
}
}
定时器
- 作用:间隔特定的时间,执行特定的操作
- 在Java的类库中已经写好了一个定时器:java.util.Timer
- 不过这种方式在目前的开发中也很少使用,因为很多高级框架都是支持定时任务的
- 在实际的开发中,使用较多的是Spring框架提供的SpringTask框架,这个框架只需要进行简单的配置,就可以完成定时器的任务
package Day024多线程;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Test020 {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
// Timer timer = new Timer(true); //守护线程的模式
//指定时间任务
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2020-12-27 15:55:30"); //开始时间
// timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
timer.schedule(new LogTimerTask(), firstTime, 1000);
}
}
class LogTimerTask extends TimerTask {
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String StrTime = sdf.format(new Date());
System.out.println(StrTime + " 完成一次数据备份");
}
}
注
- 定时任务类继承是不是Thread而是TimerTask
- schedule()方法的使用
实现线程的第三种方式:实现Callable接口
- 这种方式实现的线程可以获取线程的返回值
- 之前的两种不行,因为run( )方法返回void
- 当需要拿到一个线程执行的结果的时候,使用该方法
- 步骤:
- 创建一个“未来任务类”对象(参数十分重要,需要给Callable接口实现对象)
- 在call方法中写获取结果的步骤
- 使用get()方法获取结果
- 注:get方法会导致“当前“线程进入”阻塞状态“
- 实例
package Day024多线程;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test021 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
System.out.println("call begin");
Thread.sleep(1000 * 3);
System.out.println("call over");
int a = 100;
int b = 200;
return a + b;
}
});
Thread t1 = new Thread(task);
t1.start();
Object object = task.get();
System.out.println("线程的执行结果是:" + object);
System.out.println("阻塞");
}
}
不使用匿名内部类的方式
package Day024多线程;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test022{
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask task = new FutureTask(new MyCallable());
Thread t1 = new Thread(task);
t1.start();
Object o = task.get();
System.out.println("线程执行结果是:" + o);
}
}
class MyCallable implements Callable{
@Override
public Object call() throws Exception {
System.out.println("call begin");
Thread.sleep(1000 * 3);
System.out.println("call over");
int a = 100;
int b = 200;
return a + b;
}
}
注:Callable是一个接口,只能实现,且不能实例对象