目录
线程进程
1.概念
线程类似洗手间
-
进程是一个应用程序(一个进程是一个软件)
-
线程是一个进程中的执行场景/执行单元。
-
一个进程可以启动多个线程。
* 对于java程序来说,当在DOS命令窗口中输入:`java HelloWorld回车`之后。会先启动JVM,而JVM就是一个进程,JVM在启动一个主线程调用main方法。同时在启动一个垃圾回收线程负责看护,回收垃圾。 * 最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。
注意:
进程A和进程B的内存独立不共享。
线程A和线程B,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈
eg. 假设启动十个线程,会有十个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发。
java中之所以有多线程机制,目的就是为了提高程序的处率
2.线程图示
main方法结束不代表程序结束,只能代表主线程结束了,主栈空了
其他线程可能还在执行。线程A和线程B栈内存独立不共享
3.多线程并发的理解
-
对于一个单核的cpu来说,真的可以做到多线程并发吗? 对于多核的CPU电脑来说真正的多线程并发是没有问题的。 如4核cpu表示在同一个时间点上,可以真正的有4个进程并发执行。 对于单核的CPU来说,单核的CPU表示只有一个大脑,不能做到真正的多线程并发,但是可以做到给人一种""多线程并发“的感觉。 在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度很快,多个线程之间频繁切换执行,给人的感觉就是:多个事情同时在做!!! eg.例如 线程A:放音乐 线程B:运行游戏 线程A和线程B之前频繁切换,人会感觉一直在放音乐游戏也一直在运行,给我们的感觉是同时并发的。
什么是多线程并发? t1线程执行t1的。t2线程执行t2的。t1不会影响t2,t2也不会影响t1,这叫做真正的多线程并发。
4.判断有几个线程
package com.Thread;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/09/17/23:04
* @Description:只有两个线程,一个垃圾处理器,一个主线程
*/
public class ThreadTest01 {
public static void main(String[] args) {
m1();
}
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 Begin!");
System.out.println("m3 Over!");
}
}
一个栈中自上而下的顺序一次逐行执行
5.实现线程
java支持多线程机制,并且java已经将多线程实现了,我们只需要继承就行了。
补充:调用方法都会压栈
5.1第一种方式
编写一个类,直接继承Java.lang.Thread,重写run方法。
创建线程对象:new就行了
怎么启动线程:调用线程对象的start()方法
start()方法的作用。
重写run方法要自己重写
package Threadtest;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/25/10:59
* @Description:实现线程的第一种方式
*/
/**
* 更古不变的道理:
* 方法体当中的代码永远都是自上而下的顺序依次逐行执行的,所以这里的myThreads.start()结束后才会继续往下执行,不结束是不会向下执行的,只是myThreads.start()的执行速度很快,一下就结束了
*/
public class MyThread {
public static void main(String[] args) {
//创建线程对象
MyThreads myThreads = new MyThreads();
//启动线程
myThreads.start();
/**
* start方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后就瞬间结束了
* 这段代码的任务就只是为了开辟一个栈空间,只要新的栈空间开出来start方法就结束了,线程就启动成功了
* 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部
* run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的
*/
//myThreads.run();//不会启动线程,不会分配新的分支栈,还是在一个线程当中进行(这种方法就是单线程)这只是一个普通方法的调用
for(int i = 0;i<1000;i++)
{
System.out.println("这是主线程---->"+i);
}
}
}
class MyThreads extends Thread {
@Override//重写run方法
public void run() {
//这段程序运行在分支线程中
for(int i= 0;i<1000;i++){
System.out.println("分支线程---->"+i);
}
}
}
5.1.1run和start的区分
5.1.1.1run
程序走到t.run()会停下来,用run()方法来压栈,t.run()方法栈帧结束之后再回到main方法栈帧继续执行。
是按顺序执行,有先后的
5.1.1.2start
执行到t.start()也会停下来,用t.start()方法栈帧压栈,但是start()方法的作用是开辟一个分支栈,只要开辟完方法就结束,然后main方法栈帧就继续向下执行,同时run方法栈帧也会压分支站,并且主栈和分支栈并行,互不干扰
注意:分支栈里面的run不需要手动调用,是由JVM虚拟机调度机制调度的。
5.1.2线程输出特点
-
有先有后(控制台只有一个,可能是先执行但是后输出)
-
有多有少(某个线程抢到了执行权)
5.2第二种方式
编写一个类,实现java.lang.Runnable接口,实现run方法
package Threadtest;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/25/11:45
* @Description:实现线程第二种方式:编写一个类实现java.lang.runnable接口
*/
public class Mythreadtest01 {
public static void main(String[] args) {
//创建一个可运行的对象
myRunnable m = new myRunnable();
//将可运行的对象封装成一个线程对象
Thread t = new Thread(m);
/**
* 合并代码 Thread t = new Thread(new myRunnable());
*/
//启动线程
t.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 Threadtest;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/26/15:14
* @Description:匿名内部类实现
*/
public class Threadtest02 {
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("分支线程--->"+i);
}
}
});
t.start();
for (int i =0; i<100;i++){
System.out.println("主线程--->"+i);
}
}
}
6.线程对象的生命周期
多线程并发:线程在就绪状态和运行状态之间来回切换。cpu运行速度很快
-
新建状态
-
就绪状态
-
运行状态
-
阻塞状态
-
死亡状态
7.对线程对象的操作
7.1线程取名和线程命名
修改名字:线程对象.setName(“name”);
获取线名字: String name = 线程对象.getName();
默认的名字:Thread-0 Thread-1 Thread-2...
package Threadtest;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/26/15:14
* @Description:取名和拿到名字
*/
public class Threadtest02 {
public static void main(String[] args) {
//创建线程对象
Mythread2 T = new Mythread2();
//设置线程的名字
T.setName("tttt");
//获取线程的名字
String tName = T.getName();
//启动线程
T.start();
}
}
class Mythread2 extends Thread{
public void run(){
for (int i = 0 ;i<100;i++){
System.out.println("分支线程--->"+i);
}
}
}
7.2获取到当前线程
static Thread currentThread() : 返回对当前正在执行的线程对象的引用。
Thread t = Thread.currentThread(); 通过静态方法获取线程对象。返回值 t就是当前线程。
该方法出现在哪就是获取的当前线程对象
package com.Thread;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/09/18/11:46
* @Description:
*/
public class ThreadTest02 {
void test(){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"---->"+i);
//向这里就不能使用this和super因为没有继承关系。
/*System.out.println(this.gatName());
System.out.println(super.getName());*/
}
}
public static void main(String[] args) {
//给主线程取名
Thread.currentThread().setName("主线程");
//给MyThread线程取名(必须创建对象)
MyThread myThread = new MyThread();
myThread.setName("分支线程");
//启动线程,瞬间结束
myThread.start();
ThreadTest02 threadTest02 = new ThreadTest02();
threadTest02.test();
// for (int i = 0; i < 100; i++) {
// System.out.println(Thread.currentThread().getName()+"---->"+i);
// }
}
}
class MyThread extends Thread {
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
//以下这两种有局限性,只能在继承了Thread的类中使用,所以不建议使用
/*System.out.println(this.getName());
System.out.println(super.getName());*/
}
}
}
class MyThread2 implements Runnable{
@Override
public void run() {
Thread.currentThread().setName("线程2");
for (int i = 0; i < 100; i++) {
System.out.println();
}
}
}
7.3sleep方法
关于线程的sleep方法:static void sleep(long millis)
1.静态方法:Thread.sleep(1000);
2.参数是毫秒
3.作用是:让当前的线程进入休眠,进入"阻塞状态",放弃占有的CPU时间片,让给其他的线程。
package Threadtest;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/26/16:37
* @Description:关于线程的sleep方法
*/
/**
* static void sleep(long millis)
* 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
* 1.静态方法
* 2.参数是毫秒
* 3.作用是:让当前的线程进入休眠,进入"阻塞状态",放弃占有的CPU时间片,让给其他的线程。
*
*
* Thread.sleep()方法,可以做到这种效果:
* 间隔特定的时间去执行特定的代码,每隔多久执行一次
*/
public class ThreadTest03 {
public static void main(String[] args) {
try {
//让当前线程进入休眠,睡眠5秒
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒后执行此代码
System.out.println("hello world!");
//每隔一秒执行此代码
for (int i = 0 ; i<10;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
可以做到间隔特定的时间,去执行一段特定的代码,设置每隔多久执行一次
7.3.1sleep面试题
sleep是静态方法:实例化与直接用类名调用是一样的
package Threadtest;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/26/16:53
* @Description:
*/
public class Threadtest04 {
public static void main(String[] args) {
Thread t = new Mythread3();
t.setName("t");
t.start();
try {
t.sleep(5*100);//这里睡的是主线程,在执行的时候还是会转换成:Tread,sleep(1000*5);
//这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠
//因为sleep是静态方法,无论谁调用都是执行的当前对象
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Mythread3 extends Thread{
@Override
public void run() {
}
}
7.3.2中断线程的睡眠
interrupt() 只是叫醒线程不会结束线程
叫醒sleep的线程起来继续执行
而不是结束线程的执行,是暂停线程的睡眠
package Threadtest;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/26/17:05
* @Description:叫醒sleep的线程 不是中断线程的执行,是终止线程的睡眠
*/
public class ThreadTest05 {
public static void main(String[] args) {
Thread t = new Thread( new MyRunnable2() );
t.setName("t");
t.start();
//希望5秒之后醒来
try {
Thread.sleep(5*1000);//主线程睡5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断t线程的睡眠(这种中断睡眠的方式,依靠了java的异常处理机制)
t.interrupt();//干扰
}
}
class MyRunnable2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---->"+"begin");
//睡眠一年
try {
//重点:run方法中的异常只能捕捉不能抛出
//子类重写父类的方法,子类不能比父类抛出更宽泛的异常 因为run()方法在父类中没有抛出任何异常
Thread.sleep(60*60*24*365*1000);
} catch (InterruptedException e) {
//打印异常信息 不要异常打印信息就把这一行给注释
e.printStackTrace();
}
//一年之后才会执行这里
System.out.println(Thread.currentThread().getName()+"---->"+"end");
}
}
7.3.3强行结束线程(不建议使用)
stop()已过时,使用stop()可能会造成数据丢失
package Threadtest;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/26/17:30
* @Description:终止一个线程的执行
*/
public class ThreadTest06 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable3());
t.setName("t");
t.start();
try {//模拟睡眠5秒
Thread.sleep(1000*5);//主线程睡五秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒后强行终止线程
t.stop();
//已过时(不建议使用) 这种方式存在很大的缺点,这种方式直接将线程杀死了,线程没有保存数据将会丢失,容易丢数据
}
}
class MyRunnable3 implements Runnable{
@Override
public void run() {
for (int i = 0 ; i<100;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}】
7.3.4建议的结束线程方式
bool标记
package Threadtest;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/26/17:40
* @Description:
*/
public class Threadtest07 {
public static void main(String[] args) {
MyRunnable4 R = new MyRunnable4();
Thread t = new Thread(R);
t.setName("T");
t.start();
try {//模拟睡眠五秒
t.sleep(5*1000);//睡的是主线程
} catch (InterruptedException e) {
e.printStackTrace();
}
//想要什么时候终止,把标记改为false就结束了。
R.run = false;
}
}
class MyRunnable4 implements Runnable{
public 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就结束了,在结束之前没保存的在这里保存
//终止当前线程
return;
}
}
}
}
8.关于线程的调度(了解)
8.1常见的线程调度模型
-
抢占式调度模型:哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些。java采用的就是这种模型.
-
均分式调度模型:平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。有一些编程语言,线程调度模型采用的是这种方式
8.2与线程调度有关的方法
java中提供了哪些方法是和线程调度有关系的呢?
void setriority (int newPeiority) 设置线程的优先级
int getPriority()获取线程优先级
实例方法: void setriority (int newPeiority) 设置线程的优先级 int getPriority()获取线程优先级 //谁的优先级高获取的CPU时间片可能会多一些(但也不完全是,大概率是高的) 最低优先级:1 默认优先级:5 最高优先级:10 静态方法: static void yield ()让位方法 暂停当前正在执行的线程对象,并执行其他线程 yield()方法不是阻塞方法。让当前线程让位,让给其他线程使用。 yield()方法的执行会让当前线程从”运行状态”回到“就绪状态“ //但是回到就绪状态之后也可以再次抢夺时间片,有可能再次抢到 实例方法: void join() 合并线程 class MyThread1 extends Thread { public void doSome(){ MyThread2 t = new MyThread2(); t.join// 当前线程进入阻塞,t线程执行,直到t线程结束,当前线程才可以继续执行。 } } class MyThread2 extends Thread { }
8.2.1优先级
线程优先级高是指抢到cpu时间片的概率大一些
优先级比较高的线程获取CPU时间片可能会多一些
大概率会偏向优先级高的
cpu时间片多一些是指处于运行状态的时间多一些
package Threadtest; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/07/26/21:23 * @Description:优先级,大概率事件偏向优先级比较高的 */ public class ThreadTest08 { public static void main(String[] args) { System.out.println("最高优先级"+Thread.MAX_PRIORITY); System.out.println("默认优先级"+Thread.MIN_PRIORITY); System.out.println("最低优先级"+Thread.NORM_PRIORITY); Thread T =Thread.currentThread(); System.out.println(T.getName()+"线程默认的优先级是:"+T.getPriority()); //设置优先级 T.setPriority(10); Thread t = new Thread(new Myrunnable5()); t.setName("t"); //设置优先级 t.setPriority(1); t.start(); } } class Myrunnable5 implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程的优先级:"+Thread.currentThread().getPriority()); } }
8.2.2让位 yield
静态方法:static void yield() 让位方法
暂停当前正在执行的对线程对象,并执行其他线程
yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:让位回到"就绪状态"后有可能又抢到执行权
package Threadtest; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/07/26/21:37 * @Description:让位,当前线程暂停,回到就绪状态,让给其他线程 * 静态方法:Thread.yield(); */ public class Threadtest09 { public static void main(String[] args) { Thread t = new Thread( new MyRunnable6()); t.setName("t"); t.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++){ //每一百个让位一次 if (i%100==0){ Thread.yield();//当前线程暂停一下,让给主线程 } System.out.println(Thread.currentThread().getName()+"--->"+i); } } }
8.2.3合并 jion
void join() 是实例方法 作用是合并线程:两个栈之间发生了等待关系
让某个线程中途加入做事,插队执行相当于是多线程变成单线程了
注意不是站的合并,是栈被协调了
package Threadtest; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/07/26/21:48 * @Description:线程合并,相当于是多线程变成单线程了 */ public class Threadtest10 { public static void main(String[] args) { Thread t = new Thread(new Myrunnable7()); t.setName("t"); t.start(); //合并线程 try { t.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<100;i++){ System.out.println(Thread.currentThread().getName()+"--->"+i); } } }
9.线程安全问题(重点!!)
什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件;
1.多线程并发。
2.有共享数据。
3.共享数据有修改的行为。
满足以上三个条件之后就会存在线程安全问题。
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行(不能并发)。
用排队执行解决线程安全问题。
这种机制被称为线程同步机制。
线程同步机制:实际上就是线程不能并发了,线程必须排队执行。 就是线程排队。 线程排队就会牺牲一些效率
9.1线程同步和异步
-
异步编程模型:线程t1和线程t2,各自执行各自的,谁也不用等谁。(一起执行) 就是多线程并发,效率较高。
-
同步变成模型:线程t1和线程t2,在t1执行的时候,必须等待t2线程执行结束。在t2线程执行的时候必须等待t1线程的结束,两个线程之间发生了等待关系,(排队执行,)效率较低
注:异步就是并发,同步就是排队
9.2模拟银行账户
package Threadsafe;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/26/22:20
* @Description:银行账户
*/
public class Account {
private String actno;
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
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;
}
//取款的方法//5000
public void withdraw(double money){
//之前的钱
double before = this.getBalance();//10000
//之后的钱
double after = before- money;
//模拟网络延迟
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新余额
//t1执行到这里。但还没来的及执行这一行代码,t2线程进来withdraw方法了,此时一定出问题
this.setBalance(after);
}
}
package Threadsafe;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/27/7:32
* @Description:
*/
public class AccountThtread extends Thread{
//两个线程必须共享同一个账户对象
private Account act;
public AccountThtread(Account act){
this.act=act;
}
public void run(){
//run方法的执行表示取款操作
//取款5000
double money =5000;
//取款
act.withdraw(money);
System.out.println(Thread.currentThread().getName()+"对 账户"+act.getActno()+"取款成功,余额"+act.getBalance());
}
public Account getAct() {
return act;
}
public void setAct(Account act) {
this.act = act;
}
}
package Threadsafe;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/27/10:11
* @Description:
*/
public class Test {
public static void main(String[] args) {
//创建账户对象 两个用一个
Account act = new Account("act-001",10000);
Thread t1 = new AccountThtread(act);
Thread t2 = new AccountThtread(act);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
9.3synchronized关键字
这个共享对象一定要选好了,这个共享对象一定是你需要排队执行的这些线程对象所共享的。
synchronized(共享对象)
package Threadsafe2;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/26/22:20
* @Description:银行账户 使用线程同步机制来解决线程安全问题
*/
public class Account {
private String actno;
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
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 money){
//一下这几行代码必须是排队的不能并发,一个结束后另一个才能进来
/*
线程同步机制
synchronized (){}
synchronized后面小括号中传的数据是相当关键的,
这个数据必须是多线程共享的数据才能达到多线程排队
假设t1,t2,t3,t4,t5有五个线程
只希望t1,t2,t3排队,t4,t5不排队,怎么办?
要在()中写一个t1、t2、t3共享的对象,而这个对象对于t4、t5来说不是共享的
*/
/*
执行原理:
t1和t2并发,开始执行以下代码的时候,肯定有一个先一个后。
假设t1先执行,遇到了synchronized,这个时候自动找“共享对象”的对象锁,
找到之后,并占有这把锁,然后执行同步代码块中的程序,在执行过程中一直都是占有这把锁的。直到同步代码块代码结束才会释放。
假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面的共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束
直到t1把同步代码块执行结束了,t1会归还这把锁。之后t2才能占有同步代码块执行程序。
这样就达到了线程排队执行。
所以共享对象很关键。(是需要排队的线程所共享的对象)
*/
synchronized (this){//每一个java对象都有一把锁
double before = this.getBalance();
double after = before- money;
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
}
9.4锁池
没找到在锁池中等待,找到就进入就绪状态
9.5同步代码块的多种方式
使用String字符串:所有的线程都会同步
package Threadsafe2;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/26/22:20
* @Description:银行账户 使用线程同步机制来解决线程安全问题
*/
public class Account {
private String actno;
private double balance;
//对象
Object obj = new Object();//只有一个,obj可以作共享对象
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
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 money){
//Object obj2 = new Object();这里不行,obj2不是共享对象
//synchronized("abc"){"abc"字符串在常量池里面 所有线程都会同步
synchronized (this){//每一个java对象都有一把锁
double before = this.getBalance();
double after = before- money;
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
}
重点: java中的三大变量 实例变量:在堆中 静态变量:在方法区中 局部变量:在栈中 以上三大变量中: 局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈) 局部变量在栈中,所以局部变量永远都不会共享。 实例变量在堆中,堆只有一个。 静态变量在方法区中,方法区只有一个, 堆和方法区都是多线程共享的,所以可能存在线程安全问题。 //成员变量才有线程安全问题 //局部变量+常量没有线程安全问题
9.6同步代码块的执行效率
synchronized可以加在调用方法的时候,表示给整个方法上锁。
9.7synchronized写在方法体上
如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。
写在实例对象上,锁只能是this
package Threadsafe3;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/26/22:20
* @Description: 如果共享的对象就是this,并且需要同步打代码是整个方法体。建议使用这种方式。
*/
public class Account {
private String actno;
private double balance;
public Account() {
}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
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;
}
//取款的方法
/*
在实例方法上可以使用synchronized吗?可以的
synchronized出现在实例方法上一定所的是this,不能是其他对象,这种方式不灵活
还有一个缺点:synchronized出现在实例方法上。表示整个方法都需要同步可能会无故扩大同步的范围导致执行效率降低
优点:代码写得少,节俭了。
如果共享的对象就是this,并且需要同步打代码是整个方法体。建议使用这种方式。
*/
public synchronized void withdraw(double money){
double before = this.getBalance();//10000
double after = before- money;
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(after);
}
}
如果使用局部变量的话: 建议使用:StringBuilder。 因为局部变量不存在线程安全问题,选择StringBuilder StringBugger效率比较低。 ArrayList是非线程安全的 Vector是线程安全的 HashMap HashSet是非线程安全的。 Hashtable是线程安全的
9.8总结
synchronized有三种写法: 第一种:同步代码块 • synchronized(线程共享对象){ • 同步代码块 } 第二种:在实例方法上使用synchronized • 表示共享对象一定是this • 并且同步代码块是整个方法 第三种:在静态方法上使用synchronized • 表示找类锁 • 类锁永远只有一把 • 保证静态变量的安全 就算创建了100个对象, 类锁也只有那一把 对象锁:100个对象100把锁 类锁:100个对象也可能是一把锁
9.9synchronized面试题
9.9.1实例一
判断doSome和doOthe执行顺序
一个synchronized,一个共享对象
package com.Thread; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/20/19:26 * @Description:doOther执行必须要等到doSome方法执行结束后才可以吗? */ /* 不需要,因为doOther没有synchronized doSome begin! doOther begin! doOther over! doSome over! */ public class ThreadTests1 { public static void main(String[] args) { MyClass myClass = new MyClass(); MyThreads1 myThreads1 = new MyThreads1(myClass); MyThreads1 myThreads2 = new MyThreads1(myClass); myThreads1.setName("一"); myThreads2.setName("二"); myThreads1.start(); try { Thread.sleep(1000);//为了确保线程一开始在前 } catch (InterruptedException e) { e.printStackTrace(); } myThreads2.start(); } } //出现在实例方法上表示锁的this class MyThreads1 extends Thread{ private MyClass c1; public MyThreads1(MyClass c1) { this.c1 = c1; } @Override public void run() { if (Thread.currentThread().getName().equals("一")) c1.doSome(); if (Thread.currentThread().getName().equals("二")) c1.doOther(); } } /** * 一个对象只有一把锁, * 在第一个线程进入方法后碰到synchronized之后就找锁 * 找到就把该共享对象唯一的锁占了, * 以至于后面的线程走到synchronized时拿不到对象锁,所以就会进入锁池 * 而这里能执行是因为doOther方法没有synchronized修饰,所以不会找锁 */ class MyClass{ public synchronized void doSome(){ System.out.println("doSome begin!"); try { Thread.sleep(1000*5);//睡5秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("doSome over!"); } public void doOther(){ //如果这里加上synchronized //public synchronized void doOther(){ //doOther就必须在doSome执行之后才能执行, /** *因为synchronized出现在实例方法上,所得是this。 *而两个线程用的都是myClass对象所以是同一把锁 *所以如果加上就有了线程同步机制,需要排队了 *加上synchronized ---this都是myClass,所以会锁住 */ System.out.println("doOther begin!"); System.out.println("doOther over!"); } }
9.9.2实例二
两个共享对象,两个都加synchronized
package com.Thread; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/20/20:10 * @Description:也不会线程同步 */ public class ThreadTests2 { public static void main(String[] args) { MyClass_plus myClass1 = new MyClass_plus(); MyClass_plus myClass2 = new MyClass_plus(); MyThreads_plus myThreads11 = new MyThreads_plus(myClass1); MyThreads_plus myThreads22 = new MyThreads_plus(myClass2); myThreads11.setName("一"); myThreads22.setName("二"); //开始运行 myThreads11.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } myThreads22.start(); } } //出现在实例方法上表示锁的this class MyThreads_plus extends Thread{ private MyClass_plus c1; public MyThreads_plus(MyClass_plus c1) { this.c1 = c1; } @Override public void run() { if (Thread.currentThread().getName().equals("一")) c1.doSome(); if (Thread.currentThread().getName().equals("二")) c1.doOther(); } } class MyClass_plus{ public synchronized void doSome(){ System.out.println("doSome begin!"); try { Thread.sleep(1000*5);//睡5秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("doSome over!"); } public synchronized void doOther(){ System.out.println("doOther begin!"); System.out.println("doOther over!"); } }
9.9.3实例三 类锁
两个共享对象,两个synchronized
package exam1;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/27/16:13
* @Description:synchronized出现在静态方法上
*/
/*
面试题:doOther方法执行的时候需要等待doSome方法结束吗?
需要等,因为静态方法是类锁,不管创建了几个,类锁只有1把。
*/
public class Exam {
public static void main(String[] args) throws InterruptedException {
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();
Thread.sleep(1000);//保证t1线程先执行
thread2.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("t1")){
mc.doOther();
}
}
}
class MyClass{
//synchronized出现在静态方法上是类锁
//两个类都只能是用一把锁
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 static void doOther(){
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
9.9.4排他锁
共享一个对象时必须排队
对象锁和类锁
9.10死锁(要会写)
synchronized在开发中最好不要嵌套使用,一不小心可能会导致死锁的发生
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/27/20:56
* @Description:死锁
*/
/*
synchronized在开发中最好不要嵌套使用,一不小心可能会导致死锁的发生
*/
public class deadlock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread thread1 = new MyThread1(o1,o2);
Thread thread2 = new MyThread2(o1,o2);
thread1.setName("t1");
thread2.setName("t2");
thread1.start();
thread2.start();
}
}
class MyThread1 extends Thread{
Object object1;
Object object2;
public MyThread1(Object object1, Object object2) {
this.object1 = object1;
this.object2 = object2;
}
public void run(){
synchronized(object1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(object2){
}
}
}
}
class MyThread2 extends Thread{
Object object1;
Object object2;
public MyThread2(Object object1, Object object2) {
this.object1 = object1;
this.object2 = object2;
}
public void run(){
synchronized(object2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(object1){
}
}
}
}
10.怎么解决线程安全问题
不要一上来就选择线程同步。synchronized会让程序的执行效率降低,用户体验不好,系统的用户吞吐量(并发量)降低,用户体验差,在不得以的情况下再选择线程同步机制。
第一种方案:尽量使用局部变量来代替实例变量和静态变量。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。对象不共享就没有数据安全问题了
第三种方法:如果对象不能创建多个,这个时候就只能选择synchronized了。线程同步机制
11.守护线程
java语言中线程分为两大类:
一类是:用户线程
一类是:守护线程(后台线程)
守护线程中具有代表性的就是:垃圾回收线程
守护线程的特点: 一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main方法是一个用户线程。
11.1模拟守护线程
setDaemon(); 启动线程之前,将线程设置为守护线程
package Threadtest02; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/07/27/22:08 * @Description: */ public class Threadtest { public static void main(String[] args) { Thread t = new BakDataThread(); t.setName("备份数据的线程"); //启动线程之前,将线程设置为守护线程 t.setDaemon(true); t.start(); for (int i = 0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"--->"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } class BakDataThread extends Thread{ public void run(){ int i = 0; while(true){ System.out.println(Thread.currentThread().getName()+"--->"+(++i)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
12.定时器
SpringTask底层就是它
作用:间隔特定的时间执行特定的程序。
sleep是最原始的定时器(low)
在java类库中,已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过目前的开发中也很少用
在实际发的开发中,目前使用较多的是spring框架中的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
java.util.Timer
可以将Timer理解为一个守护线程。
void schedule(TimerTask task, long delay, long period)` 安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
package com.Thread; import java.sql.Time; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; /** * Created with IntelliJ IDEA. * * @Author: Mr.chen * @Date: 2022/09/21/8:23 * @Description:使用定时器指定定时任务 */ public class ScheduleTest { public static void main(String[] args) throws ParseException { //创建定时器对象 Timer timer = new Timer(); //Timer timer = new Timer(true);//true表示以守护线程的方式存在 //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次); //将String转成Date SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date firstTime = simpleDateFormat.parse("2022-09-21 08:50:00"); //指定定时器 timer.schedule(new task(),firstTime,1000*10);//第一次执行时间,每隔十秒执行一次 //可以采用匿名内部类 } } //编写一个定时任务类 //TimerTasks是一个抽象类 class task extends TimerTask { @Override public void run() { //在这里面写要执行的任务 //Date转成String SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String time =sdf.format(new Date()); System.out.println(time+"----->"+"存储完毕"); } }
13.实现线程的第三种方法
实现
Callable接口
,可以获取线程的返回值,Future方式,实现Callable接口。(JDK8新特性)可以得到线程的返回值
委派线程去执行任务,该线程执行完之后返回一个结果
FutureTask task = new FutureTask(Callable接口)
Thread T = new Thread(task);
package thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;//JUC包下的属于java并发包,老jdk中没有这个包
/*
实现线程的第三种方式:
实现Callable接口
这种方式的优点:可以获取到线程的执行结果
缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞。效率较低
*/
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/28/16:53
* @Description:实现线程的第三种方式
*/
public class ThreadTest {
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("call method begin");
Thread.sleep(1000*10);
System.out.println("call method end");
int a = 100 ;
int b = 200 ;
return a+b;//自动装箱
}
});
//第二步:创建线程对象
Thread T = new Thread(task);
//启动线程
T.start();
//在主线程中获取t线程的返回值
//get()方法的执行会导致"当前线程的阻塞"
Object obj = task.get();
//main方法这里的程序要想执行必须等待get()方法执行结束
//而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果
//而另一个线程的执行是需要时间的。
System.out.println("Hello world");
}
}
缺点:效率比较低,在获取分支线程执行结果的时候(task.get()),当前线程会受阻塞
优点:能获取到线程对象的执行结果
14.wait和notify方法(生产者和消费者模式)
wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。
wait和notify都是在对象上的方法,直接用对象来调用
这是一种特殊的业务需求
14.1wait()方法的作用
让正在o对象上活动的线程进入等待状态,无期限的等待,直到被唤醒为止。
Object o = new Object(); o.wait(); //表示:让正在o对象上活动的线程进入等待状态,无期限的等待,直到被唤醒为止。 o.wait();方法的调用,会让"当前线程"(在o对象上活动的线程)进入等待状态。
14.2notify()方法的作用
唤醒正在o对象上等待的线程。
Object o = new Object(); o.notify(); 表示:唤醒正在o对象上等待的线程。
14.2.1notifyAll()方法
还有一个notifyAll()方法: 这个方法是唤醒o对象上处于等待状态的所有线程。
14.3图例
案例:遭群殴时,被打的人突然说一声“等会别打脸”(o.wait()),打的人暂时停手了,被打的人又说“你可以继续了”(o.notify()),打的人就继续揍。
14.4重点
o.wait()
方法会让在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁。
o.notify()
只会通知,不会释放之前占有的o对象的锁。
一个线程生产一个线程消费
仓库对象是多线程共享的,需要考虑仓库对象的线程安全问题
仓库对象最终调用wait和notify方法
wait方法和notify方法建议在
synchronized
线程同步的基础上
14.5生产者与消费者模式
什么是“生产者和消费者模式”? 生产者负责线程生产,消费线程负责消费 生产线程和消费线程要达到均衡 这是一种特殊的业务需求,在这种特殊的情况下,需要使用wait和notify方法。 *wait方法不是线程对象的方法,是普通java对象都有对的方法。 *wait方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库,有线程安全的问题 *wait方法的作用:o.wait()让正在o对象上获取线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。 *notify方法的作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
14.6案例
案例概述:
仓库用List集合,List集合种假设只能存1个元素
1个元素就表示仓库已经满了
0就表示仓库已经空了
保证List集合种存储的最多永远都只是一个元素
生产者生产一个消费者消费一个
package thread;
import java.util.ArrayList;
import java.util.List;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/07/28/22:14
* @Description:使用wait方法和notify方法实现消费者模式
*/
/*
消费者和生产者模式: 生产线程负责消费,消费线程负责消费
生产线程和消费线程达到均衡
*/
public class ThreadTest001 {
public static void main(String[] args) {
//仓库采用List集合,List集合中只能存储一个元素,一个元素就代表仓库满了,0就代表空了
List list = new ArrayList();
//生产者线程
Producer producer = new Producer(list);
Thread thread1 = new Thread(producer);
//消费者线程
Consumer consumer = new Consumer(list);
Thread thread2 = new Thread(consumer);
thread1.setName("生产者线程");
thread2.setName("消费者线程");
thread1.start();
thread2.start();
}
}
//生产线程
class Producer implements Runnable{
//仓库
private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
//一直生产
while(true){
synchronized(list){
if (list.size()>0){//大于0说明有元素了
try {
//当前线程进入等待状态,并且释放当前线程占有的锁,让另一个线程运行
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else{
//程序能够执行到这里,说明是空的可以执行生产
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){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName()+"---->"+obj);
//唤醒生产者生产
list.notify();
}
}
}
}
}
生产者生产十个,消费者消费十个
package com.Thread.ThreadTests;
import java.util.ArrayList;
import java.util.List;
/**
* Created with IntelliJ IDEA.
*
* @Author: Mr.chen
* @Date: 2022/09/22/10:58
* @Description:
*/
public class Consume_Produce {
public static void main(String[] args) {
//创建List仓库
List list = new ArrayList();
//创建生产者、消费者对象
Prodecer prodecer = new Prodecer(list);
Consumer consumer = new Consumer(list);
//创建线程对象
Thread thread1 = new Thread(prodecer);
Thread thread2 = new Thread(consumer);
//给线程命名
thread1.setName("生产者");
thread2.setName("消费者");
//启动线程
thread1.start();
thread2.start();
}
}
class Prodecer implements Runnable{
private List list;
public Prodecer(List list) {
this.list = list;
}
@Override
public void run() {
//一直生产
while(true){
//为了防止两个线程对象都同时到wait方法造成死锁所以加上synchonized
synchronized (list){
if(list.size()>10){
try {
//wait会让线程一直睡眠,并且会释放当前对象占有的锁
list.wait();//如果超过十就不生产,把锁放出去让消费者拿到消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序运行到这里说明可以生产
for (int i = 0; i < 10-list.size(); i++) {
Object object = new Object();
System.out.println(Thread.currentThread().getName()+"添加"+object);
list.add(object);
}
list.notify();//唤醒list对象上wait的线程,无论是消费还是生产谁在wait就释放谁 ,让消费者有消费后
}
}
}
}
//消费者
class Consumer implements Runnable{
private List list;
//构造函数
public Consumer(List list) {
this.list = list;
}
//重写run
@Override
public void run() {
//一直消费
while (true){
//加上对象锁,只能有一个对象同一时间占有list的锁
synchronized(list){
if (list.size()==0) {
try {
//如果没有list集合里面没有元素,当前线程对象就让出锁,并暂停,等待被唤醒
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//移除list集合里面有的元素
for(int i = 0;i<list.size();i++){
System.out.println(Thread.currentThread().getName()+"移除"+list.get(i));
list.remove(i);
}
//唤醒list集合上暂停的线程
list.notify();
}
}
}
}