多线程
1、线程中的概念
1.1、什么是进程、什么进程
进程是一个应用进程(1个进程就是一个软件)
线程是一个进程中的执行场景、执行单元
一个进程可以启动多个线程。
1.2、进程和线程的关系
在java语言中:
线程A和线程B,堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。
2、Java中的多线程
java支持多线程机制,并且java已经将多线程实现了,我们只需要继承就行了。
第一种方式:编写一个类,直接继承java.lang.Thread
,重写run方法。
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 15:40
* 实现线程的第一种方式:
* 编写一个类,直接继承java.lang.Thread,重写run方法
*
* 怎么创建线程对象? new
* 怎么启动线程? 调用线程对象的start()方法
*
* 方法体重的代码永远都是自上而下的顺序依次执行
*/
public class ThreadTest01 {
public static void main(String[] args) {
// 这里是main方法,这里的代码属于主线程,在主栈中运行
// 新建一个分支线程对象
MyThread myThread = new MyThread();
// 启动线程
// start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了
// 这段代码的任务只是开启了一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了
// 启动成功的线程会自动调用run方法,并且run方法在分支栈的底部(压栈)
// run方法在分支栈的底部,main方法在主栈的底部,run和main是平级的
// myThread.run();不会启动线程,不会分配新的分支栈(这种方式就是单线程)
myThread.start();
for(int i = 0;i < 1000;i++) System.out.println("主线程--->" + i);
}
}
class MyThread extends Thread {
@Override
public void run() {
// super.run();
// 编写程序,这段程序运行在分支线程中(分支栈)
for(int i = 0;i < 1000;i++) System.out.println("分支线程--->" + i);
}
}
第二种方式:编写一个类实现java.lang.Runnable
接口
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 16:06
* 实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
*/
public class ThreadTest02 {
public static void main(String[] args) {
// 创建一个可运行的对象
// MyRunnable myRunnable = new MyRunnable();
// 将可运行的对象封装成一个线程对象
// Thread thread = new Thread(myRunnable);
// 合并代码
Thread thread = new Thread(new MyRunnable());
// 启动线程
thread.start();
for(int i = 0;i < 1000;i++) System.out.println("主线程--->" + i);
}
}
// 这并不是一个线程类,只是一个可运行的类,它号不是一个线程。
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0;i < 1000;i++) System.out.println("分支线程--->" + i);
}
}
使用匿名内部类:
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 16:12
* 采用匿名内部类
*/
public class ThreadTest03 {
public static void main(String[] args) {
// 创建线程对象,采用匿名内部类的方法
// 直接new 接口
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i < 1000;i++) System.out.println("分支线程--->" + i);
}
});
// 启动线程
thread.start();
for(int i = 0;i < 1000;i++) System.out.println("主线程--->" + i);
}
}
线程生命周期
获取线程对象
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 16:50
* 1.获取当前线程对象
* Threat threat = static Thread.currentThreat()
* 2.获取线程对象的名字
* String name = 线程对象.getName()
* 3.修改线程对象的名字
* 线程对象.setName(String name)
* 4. 当线程没有设置名字的时候,默认的名字是
* Thread-0
* Thread-1
* Thread-2
* Thread-...
*/
public class ThreadTest05 {
public static void main(String[] args) {
// currentThreat就是当前线程对象
// 这个对象出现再main方法中,所以当前线程就是主线程
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()); // main
// 创建线程对象
MyThread2 myThread1 = new MyThread2();
System.out.println(myThread1.getName()); // Thread-0
// 设置线程对象的名字
myThread1.setName("t2");
// 获取线程的名字
System.out.println(myThread1.getName()); // t2
// 再创建一个新的线程
MyThread2 myThread2 = new MyThread2();
System.out.println(myThread2.getName()); // Thread-1
// 启动线程
myThread1.start(); // t2
myThread2.start(); // Thread-1
}
}
class MyThread2 extends Thread {
@Override
public void run() {
// 当前线程,谁执行run方法,谁就是currentThread
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
// for(int i = 0;i < 100;i++) System.out.println("分支线程-->" + i);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ne0bDgYK-1635059132255)(C:\Users\Crescent_P\AppData\Roaming\Typora\typora-user-images\image-20211023170447329.png)]
线程的sleep()方法
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 17:05
* 关于线程的sleep方法:
* static void sleep(long millis)
* 1. 静态方法 Thread.sleep(1000);
* 2.参数是毫秒
* 3.作用:让当前线程进入休眠,进入“阻塞状态“,放弃占有CPU时间片,让给其他线程使用
* 这行代码出现在A线程中,A进程就会进入休眠
* 这行代码出现在B线程中,B进程就会进入休眠
* 4.Thread.sleep()方法,可以做到这种效果:
* 间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
*/
public class ThreadTest06 {
public static void main(String[] args) {
// 让当前线程进入休眠,睡眠5秒
// 当前线程是主线程
// try {
// Thread.sleep(1000 * 5);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// 五秒后执行
// System.out.println("Hello World");
for(int i = 1;i <= 10;i++){
// 打印一次,休息一秒
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
// 睡眠一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 17:12
*/
public class ThreatTest07 {
public static void main(String[] args) {
// 创建线程对象
Thread t = new MyThreat3();
t.setName("t");
t.start();
// 调用sleep方法
try {
// 这行代码会让线程t进入休眠状态吗?
t.sleep(1000 * 5); // 不会,在执行的时候还是会转化成 Thread.sleep(1000 * 5)
// 这行代码的作用是:让当前代码进入休眠,也就是main线程进入休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(("Hello World"));
}
}
class MyThreat3 extends Thread{
@Override
public void run() {
// super.run();
for (int i = 0;i < 1000;i++) System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 17:18
* sleep睡眠太久了,怎么叫醒一个正在睡眠的线程?
* 不是中断线程的执行,是终止线程的睡眠
*/
public class ThreatTest08 {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable2());
thread.setName("t");
thread.start();
// 希望5秒之后,t线程醒来
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终端t线程的睡眠,这种中断睡眠的方式依靠了java的异常处理机制。
thread.interrupt(); // 干扰
}
}
class MyRunnable2 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---> begin");
// 子类重写不能抛出更多异常
// run()在父类中没有抛出任何异常,子类重写不能比父类抛出更多异常
try {
Thread.sleep(1000 * 60 * 60 * 24 * 365); // interrupt会使这里报异常
} catch (InterruptedException e) {
// 打印异常信息
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->end");
}
}
强制终止线程
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 18:50
* 在java中强行终止一个线程的执行
* 这种方式存在很大的缺点,容易丢数据,因为这种方式直接将线程杀死了
* 线程没有保存的数据会丢失,不建议使用
*/
public class ThreadTest09 {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable3());
thread.setName("t");
thread.start();
// 模拟5秒
try {
Thread.currentThread().sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 5秒收强制终止t线程
thread.stop(); // 容易损坏数据
}
}
class MyRunnable3 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 com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 18:57
* 合理的终止一个线程的执行
*/
public class ThreadTest10 {
public static void main(String[] args) {
MyRunnable4 myRunnable4 = new MyRunnable4();
Thread thread = new Thread(myRunnable4);
thread.setName("t");
thread.start();
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终止线程
// 你想要什么时候终止t的执行,那么你就吧标记修改为false,就结束了
myRunnable4.run = false;
}
}
class MyRunnable4 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;
}
}
}
}
Java中线程调度方法
实例方法:
void setPriority(int newPriority) // 设置线程的优先级
int getPriority() // 获取线程优先级
最低优先级 1
默认优先级 5
最高优先级 10
void join() // 合并线程
class MyThread1 extends Thread{
public void dosome(){
Mythread2 t = new Mythread2();
t.join(); // 当前进程进入阻塞,t线程执行,直到t线程结束,当前线程才可以运行
}
}
class Mythread2 extends Thread{}
静态方法:
static void yield() // 让位方法
暂停当前正在执行的线程方法,并执行其它线程
yield()方法不是阻塞方法,让当前线程让位,让给其它线程使用
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 19:15
*/
public class ThreadTest11 {
public static void main(String[] args) {
// System.out.println("最高优先级: " + Thread.MAX_PRIORITY);
// System.out.println("最低优先级: " + Thread.MIN_PRIORITY);
// System.out.println("默认优先级: " + Thread.NORM_PRIORITY);
// 最高优先级: 10
// 最低优先级: 1
// 默认优先级: 5
// 获得当前线程对象,获取当前线程优先级
// Thread thread = Thread.currentThread();
// System.out.println(thread.getName() + "线程的默认优先级是: " + thread.getPriority());
// main线程的默认优先级是: 5
// Thread thread1 = new Thread(new MyRunnable5());
// thread1.start();
// Thread-0线程的默认优先级是: 5
// 设置主线程的优先级为1
Thread.currentThread().setPriority(1);
Thread thread1 = new Thread(new MyRunnable5());
thread1.setPriority(10);
thread1.start();
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class MyRunnable5 implements Runnable {
@Override
public void run() {
// 获取线程的优先级
// System.out.println(Thread.currentThread().getName() + "线程的默认优先级是: " + Thread.currentThread().getPriority());
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 19:24
* 让位,当前线程暂停,回到就绪状态,让给其它线程
* 静态方法:Thread.yield()
*/
public class TreadTest12 {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable6());
thread.setName("t");
thread.start();
for (int i = 1; i <= 10000; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class MyRunnable6 implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 10000; i++) {
// 每100个让位1次
if(i % 100 == 0) Thread.yield();
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-23 19:30
* 线程合并
*/
public class ThreadTest13 {
public static void main(String[] args) {
System.out.println("main begin...");
Thread thread = new Thread(new MyRunnable7());
thread.setName("t");
thread.start();
// 合并线程
try {
thread.join(); // t合并到当前线程,当前线程受阻塞,t线程执行,直到结束
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main over....");
}
}
class MyRunnable7 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
线程安全
什么时候存在线程安全问题呢?
- 多线程并发
- 有共享数据
- 共享数据有修改行为
满足以上三个条件,就会存在线程安全问题
怎么解决?
线程排队执行,不能并发。称为:线程同步机制
会牺牲一定的效率,但是安全。
模拟取钱出错
Account.java
package com.caopeng.java.thread.threadsafe;
/**
* @author Crescent_P
* @date 2021-10-23 19:46
* 银行账户类
*/
public class Account {
// 账户
private String accountId;
// 余额
private double balance;
public Account() {
}
public Account(String accountId, double balance) {
this.accountId = accountId;
this.balance = balance;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
// 取款方法
public void withDraw(double money){
// 取款前的余额
double before = this.balance;
// 取款后的余额
double after = before - money;
try {
// 模拟网络延迟
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 跟新余额
this.setBalance(after);
}
}
AccountThread.java
package com.caopeng.java.thread.threadsafe;
/**
* @author Crescent_P
* @date 2021-10-23 19:52
*/
public class AccountThread extends Thread{
// 两个线程共享一个账户对象
private Account account;
// 通过构造方法传递过来账户信息
public AccountThread(Account account) {
this.account = account;
}
// 取款
@Override
public void run() {
// super.run();
// 假设取款5000
double money = 5000;
// 取款
account.withDraw(money);
System.out.println(Thread.currentThread().getName() + "对账户" + account.getAccountId() + "取款: "+ money+ " 成功,余额为:" + account.getBalance());
}
}
Test.java
package com.caopeng.java.thread.threadsafe;
/**
* @author Crescent_P
* @date 2021-10-23 19:55
*/
public class Test {
public static void main(String[] args) {
// 创建账户对象
Account account = new Account("act-001",10000);
// 两个线程共享一个账户
AccountThread thread1 = new AccountThread(account);
AccountThread thread2 = new AccountThread(account);
// 启动取款
thread1.start();
thread2.start();
}
}
解决
使用Synchronized关键字
package com.caopeng.java.thread.threadsafe2;
/**
* @author Crescent_P
* @date 2021-10-23 19:46
* 银行账户类
*/
public class Account {
// 账户
private String accountId;
// 余额
private double balance;
public Account() {
}
public Account(String accountId, double balance) {
this.accountId = accountId;
this.balance = balance;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
/*
线程同步机制的语法是:
synchronized(){
// 线程同步代码块
}
synchronized后面的小括号中传的这个”数据“是相当重要的
这个数据必须是多线程共享的数据,才能到达多线排队
()写什么?
那要看你想要哪些进程同步
假设t1、t2、t3、t4、t5,有5个线程
你只希望 t123排队,t45不需要排队
那你在()中填写一个t1、t2、t3共享的对象,而这个对象对于t4 t5不是共享的
这里的共享对象是账户对象,
在java中,任何对象都有一把锁,对象锁。
以下代码的执行原理:
1、假设t1和t2并发,开始执行以下代码的时候,肯定有一个先有一个后,
2、假设t1先执行了,遇到了synchronized,这个时候会去找”共享对象的对象锁“
找到之后,会占有这把锁,然后执行同步代码块的中的代码,在程序执行过程中,
一直都是占有这把锁的,直到同步代码块执行结束,才会放弃这把锁
3、假设t1已经占有这把锁了,此时t2也遇到了synchronized关键字,也会去占有后面共享对象的这把锁
结果这把锁已经被t1占有了,t2只能在同步代码块外面等待t1的结束
直到t1把同步代码块执行完了之后,t1会归还这把锁,此时t2等到了这把锁,
然后t2就会占有这把锁,并执行代码块中的内容
这样就达到了线程排队执行
这里需要注意的是:这个共享对象一定要选好了,这个共享对象一定是你需要排队执行的这些线程所共享的。
*/
// 取款方法
public void withDraw(double money){
// 以下几行代码必须是线程排队,不能并发
// 一个线程把这里的代码执行完,另一个线程才能进来
synchronized(this){
double before = this.balance;
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
}
package com.caopeng.java.thread.threadsafe2;
/**
* @author Crescent_P
* @date 2021-10-23 19:52
*/
public class AccountThread extends Thread{
// 两个线程共享一个账户对象
private Account account;
// 通过构造方法传递过来账户信息
public AccountThread(Account account) {
this.account = account;
}
// 取款
@Override
public void run() {
// super.run();
// 假设取款5000
double money = 5000;
// 取款
account.withDraw(money);
System.out.println(Thread.currentThread().getName() + "对账户" + account.getAccountId() + "取款: "+ money+ " 成功,余额为:" + account.getBalance());
}
}
package com.caopeng.java.thread.threadsafe2;
/**
* @author Crescent_P
* @date 2021-10-23 19:55
*/
public class Test {
public static void main(String[] args) {
// 创建账户对象
Account account = new Account("act-001",10000);
// 两个线程共享一个账户
AccountThread thread1 = new AccountThread(account);
AccountThread thread2 = new AccountThread(account);
// 启动取款
thread1.start();
thread2.start();
}
}
有线程安全问题的变量
Java中有三大变量。
实例变量:堆
静态变量:方法区
局部变量:栈
局部变量一定没有线程安全问题,因为局部变量不共享,一个线程一个栈。
synchronized出现在实例方法上
package com.caopeng.java.thread.threadsafe3;
/**
* @author Crescent_P
* @date 2021-10-23 19:46
* 银行账户类
*/
public class Account {
// 账户
private String accountId;
// 余额
private double balance;
public Account() {
}
public Account(String accountId, double balance) {
this.accountId = accountId;
this.balance = balance;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
// 取款方法
/*
在实例方法上是可以使用 synchronized 的,那此时锁的一定是this
没得挑,因此不灵活
另外,synchronized出现在实例方法上
表示整个方法体都需要同步,看你会无故扩大同步的范围
导致程序的执行效率降低,所以这种方式不常用
使用在实例方法上的优点:代码简洁
如果共享的对象就是this,并且整个方法体都需要同步
建议使用这个
*/
public synchronized void withDraw(double money){
double before = this.balance;
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
synchronized的三种写法:
-
同步代码块
synchronized(线程共享对象){ 同步代码块 }
-
实例方法上使用synchronized,表示共享对象一定是this,并且同步代码块是整个方法体
-
在静态方法上使用synchronized,表示找类锁,类锁只有一把,不管创建了多少个对象,只有一把类锁
Synchronized面试题
题一:
package com.caopeng.java.thread.exam1;
/**
* @author Crescent_P
* @date 2021-10-24 12:04
* doOther方法的执行需要等 doSome 方法的结束
*/
public class Exam01 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
Thread thread1 = new MyThread(myClass);
Thread thread2 = new MyThread(myClass);
thread1.setName("t1");
thread2.setName("t2");
thread1.start();
try {
// 保证thread1先执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
class MyThread extends Thread {
private MyClass myClass;
public MyThread(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
myClass.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
myClass.doOther();
}
}
}
class MyClass{
public synchronized void doSome(){
System.out.println("doSome begin...");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over...");
}
public void doOther(){
System.out.println("doOther begin...");
System.out.println("doOther over...");
}
}
// 不需要,因为 doOther 没有 synchronized 不需要对象锁
题二:
package com.caopeng.java.thread.exam2;
/**
* @author Crescent_P
* @date 2021-10-24 12:04
* doOther方法的执行需要等 doSome 方法的结束
*/
public class Exam01 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
Thread thread1 = new MyThread(myClass);
Thread thread2 = new MyThread(myClass);
thread1.setName("t1");
thread2.setName("t2");
thread1.start();
try {
// 保证thread1先执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
class MyThread extends Thread {
private MyClass myClass;
public MyThread(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
myClass.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
myClass.doOther();
}
}
}
class MyClass{
public synchronized void doSome(){
System.out.println("doSome begin...");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over...");
}
public synchronized void doOther(){
System.out.println("doOther begin...");
System.out.println("doOther over...");
}
}
// 需要,因为 doOther 有 synchronized 需要对象锁,但是对象锁此时已经被 thread1拿走了
// 需要thread1执行完doSome方法,才能拿到对象锁
题三:
package com.caopeng.java.thread.exam2;
/**
* @author Crescent_P
* @date 2021-10-24 12:04
* doOther方法的执行需要等 doSome 方法的结束
*/
public class Exam01 {
public static void main(String[] args) {
MyClass myClass1 = new MyClass();
MyClass myClass2 = new MyClass();
Thread thread1 = new MyThread(myClass1);
Thread thread2 = new MyThread(myClass2);
thread1.setName("t1");
thread2.setName("t2");
thread1.start();
try {
// 保证thread1先执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
class MyThread extends Thread {
private MyClass myClass;
public MyThread(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
myClass.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
myClass.doOther();
}
}
}
class MyClass{
public synchronized void doSome(){
System.out.println("doSome begin...");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over...");
}
public synchronized void doOther(){
System.out.println("doOther begin...");
System.out.println("doOther over...");
}
}
// 不需要,即使两个方法都有 synchronized 修饰,但是两个线程占有的对象不一样
// 有两个 MyClass 类型的对象,thread1拿走的是myClass1这个对象的对象锁,不影响thread2占有myClass2的对象锁
题四:
package com.caopeng.java.thread.exam2;
/**
* @author Crescent_P
* @date 2021-10-24 12:04
* doOther方法的执行需要等 doSome 方法的结束
*/
public class Exam01 {
public static void main(String[] args) {
MyClass myClass1 = new MyClass();
MyClass myClass2 = new MyClass();
Thread thread1 = new MyThread(myClass1);
Thread thread2 = new MyThread(myClass2);
thread1.setName("t1");
thread2.setName("t2");
thread1.start();
try {
// 保证thread1先执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
class MyThread extends Thread {
private MyClass myClass;
public MyThread(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
myClass.doSome();
}
if(Thread.currentThread().getName().equals("t2")){
myClass.doOther();
}
}
}
class MyClass{
public synchronized static void doSome(){
System.out.println("doSome begin...");
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over...");
}
public synchronized void doOther(){
System.out.println("doOther begin...");
System.out.println("doOther over...");
}
}
// 需要,因为 synchronized 出现在静态方法上,此时锁的是类锁
// 不管有几个对象,MyClass这个类的类锁只有这一个
死锁
package com.caopeng.java.thread.deadlock;
/**
* @author Crescent_P
* @date 2021-10-24 12:22
*/
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
// 两个线程共享o1,o2
Thread myThread1 = new MyThread1(o1,o2);
Thread myThread2 = new MyThread2(o1,o2);
myThread1.start();
myThread2.start();
}
}
class MyThread1 extends Thread {
Object o1;
Object o2;
public MyThread1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized(o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(o2){
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized(o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(o1){
}
}
}
}
守护线程
java语言中线程分为两大类:
- 用户线程
- 守护线程(后台线程)
- 垃圾回收线程
守护线程的特点:一般用户线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
主线程main就是一个用户线程。
守护线程一般用在什么地方呢?
每天00:00的时候系统数据自动备份
这个需要使用到定时器,并且我们可以将定时器设置为守护线程。
一直在那看着,每到00:00的时候就自动备份一份,所有的用户线程如果结束了,守护线程自动退出,没必要进行数据备份了。
package com.caopeng.java.thread;
/**
* @author Crescent_P
* @date 2021-10-24 13:55
* 守护线程
*/
public class ThreadTest14 {
public static void main(String[] args) {
Thread thread = new DateThread();
thread.setName("备份数据的线程");
// 启动线程之前,将线程设置成守护线程
thread.setDaemon(true);
thread.start();
// 主线程,主线程是用户线程
for(int i = 0;i < 10;i++){
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class DateThread extends Thread {
@Override
public void run() {
int i = 0;
// 即使是死循环,但由于该进程是守护进程,当用户进程结束,守护线程自动终止
while(true){
System.out.println(Thread.currentThread().getName() + "--->" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
定时器
定时器的作用:
每隔特定的时间,执行特定的程序
package com.caopeng.java.thread;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* @author Crescent_P
* @date 2021-10-24 14:05
*/
public class TimerTest {
public static void main(String[] args) throws ParseException {
// 创建定时器对象
Timer timer = new Timer();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstDate = sdf.parse("2021-10-24 14:09:00");
// 指定定时任务
timer.schedule(new LogTimerTask(),firstDate,1000 * 10);
}
}
// 编写一个定时任务类
class LogTimerTask extends TimerTask {
@Override
public void run() {
// 需要执行的任务
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(new Date());
System.out.println(time+" 完成了一次备份");
}
}
实现线程的第三种方式
这种方式可以获取到线程返回值,之前的两种方式无法获得到线程的返回值。
实现Callable接口
package com.caopeng.java.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; // JUC包下,属于Java的并发包,Java新特性
/**
* @author Crescent_P
* @date 2021-10-24 14:13
* 实现Callable接口
*/
public class ThreadTest15 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 第一步:创建一个"未来任务类"对象
FutureTask futureTask = new FutureTask(new Callable(){
@Override
public Object call() throws Exception { // call()方法相当于run方法,只不过这个有返回时
// 线程执行一个任务,执行之后可能会一有一个执行结果
// 模拟执行
System.out.println("call method begin");
Thread.sleep(1000 * 10);
System.out.println("call method end");
int a = 100;
int b = 200;
return a+b; // 自动装箱
}
});
// 创建一个线程对象
Thread thread = new Thread(futureTask);
// 启动线程
thread.start();
// 这里是main方法,这是在主线程
// 在主线程中,怎么获取到t线程的返回结果?
// get()方法的执行,会导致当前线程的阻塞.
Object o = futureTask.get(); // 会导致当前线程阻塞,到call()方法执行完后,才能拿到执行的执行结果的返回值
System.out.println(o);
}
}
关于Object类中的wait方法和notify方法
-
wait方法和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是Object类自带的
-
wait方法作用
Object o = new Object(); o.wait(); 表示:让正在o对象上活动的线程进入等待,无期限等待,知道被唤醒为止
-
notify方法的作用
唤醒正在o对象上等待的线程
生产者消费者模式
package com.caopeng.java.thread;
import java.util.ArrayList;
import java.util.List;
/**
* @author Crescent_P
* @date 2021-10-24 14:37
* 1.使用wait方法和notify方法,实现生产者和消费者模式
*
* 2.wait和notify方法不是线程对象的方法,是普通java对象都有的方法
*
* 3.wait和notify方法建立在线程同步的基础上,因为多线程要同时操作一个仓库,有线程安全问题
*
* 4.wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放t线程之前占有的o对象的锁
*
* 5.notify方法作用:o.notify()方法让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁
*
* 6.模拟这样一个需求:
* 仓库采用List集合
* List集合中只能存储1个元素
* 1个元素就表示仓库满了,
* 如果List集合中元素个数是0,就表示仓库空了
* 保证List集合中永远最多存储1个元素
*
* 生产一个消费1个
*/
public class ThreadTest16 {
public static void main(String[] args) {
// 共享一个仓库
List list = new ArrayList();
// 创建两个线程
// 生产者线程
Thread producer = new Thread(new Producer(list));
// 消费者线程
Thread consumer = new Thread(new Consumer(list));
producer.setName("producer");
consumer.setName("consumer");
producer.start();
consumer.start();
}
}
// 生产线程
class Producer implements Runnable {
private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
// 一直生产
while(true){
// 给list加锁
synchronized (list) {
// 不为空
if(!list.isEmpty()){
//进入等待状态,当前线程进入等待状态,并且释放 List集合的锁
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 仓库为空
}else{
Object o = new Object();
list.add(o);
System.out.println(Thread.currentThread().getName() + "--->" + o);
// 唤醒消费者进行消费
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.isEmpty()){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// 可以消费
Object o = list.remove(0);
System.out.println(Thread.currentThread().getName() + "--->" + o);
list.notify();
}
}
}
}
}
一直生产
while(true){
// 给list加锁
synchronized (list) {
// 不为空
if(!list.isEmpty()){
//进入等待状态,当前线程进入等待状态,并且释放 List集合的锁
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 仓库为空
}else{
Object o = new Object();
list.add(o);
System.out.println(Thread.currentThread().getName() + “—>” + o);
// 唤醒消费者进行消费
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.isEmpty()){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// 可以消费
Object o = list.remove(0);
System.out.println(Thread.currentThread().getName() + "--->" + o);
list.notify();
}
}
}
}
}
[外链图片转存中...(img-YQndGccp-1635059132268)]