本章多线程原理能够在操作系统学习记录中找到原理:咖啡ice的操作系统学习记录
第一种:编写一个类,直接继承java.lang.Thread,重写run方法。
创建线程对象:new
启动线程:调用线程对象的start()方法
注:start方法作用:启动一个分支线程,在JVM中开辟一个新的栈空间,启动完这段代码就结束了,启动成功后新的栈空间会自动调用run方法开始执行自己的任务,且run方法在新的分支栈底部。
run方法在分支栈底部,main方法在主栈底部,run和main是平级的。
/*
实现线程的第一种方式:
编写一个类,继承java.lang.Thread,重写run方法
*/
public class ThreadTest {
public static void main(String[] args) {
//这里的main方法,这里代码属于主线程,在主栈中运行
//新建一个分支线程对象
MyThread myThread = new MyThread();
//启动线程
myThread.start(); //start方法作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码启动完就结束了,新的栈空间会开始执行它的任务。
//如果直接调用MyThread.run(),会导致一直执行到run()结束才执行下面的代码,导致不能并发。
//其他代码继续在主线程中执行
for (int i = 0; i <1000; i++) {
System.out.println("主线程——>" + i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
//编写程序,这段程序运行在分支线程中(分支栈)。
for (int i = 0; i <1000; i++) {
System.out.println("分支线程——>" + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//创建一个可运行的对象,封装成一个线程对象
Thread t = new Thread(new MyRunable());
//启动线程
t.start();
//其他代码继续在主线程中执行
for (int i = 0; i <100; i++) {
System.out.println("主线程——>" + i);
}
}
}
//这不是一个线程类,只是一个可运行的类。不是一个线程。
class MyRunable implements Runnable{
@Override
public void run() {
//编写程序,这段程序运行在分支线程中(分支栈)。
for (int i = 0; i <100; i++) {
System.out.println("分支线程——>" + i);
}
}
}
? 通过常用匿名内部类实现
public class ThreadTest {
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("t线程" + i);
}
}
});
//启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("main线程" + i);
}
}
}
第三种:实现Callable接口(JDK8新特性),这种方式优点能够获取返回执行结果,缺点是获取线程执行结果时,本线程会阻塞执行效率低。
这种方式实现线程可以获取线程的返回值,前两种方式的run返回void无法获取返回值。
package studyThread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; //属于JAVA的并发包。
//实现线程的Callable接口
public class ThreadTest13 {
public static void main(String[] args) throws Exception{
//创建一个“未来任务”类对象
//这里需要给一个Callable接口实现类对象。
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 over");
int a = 100;
int b = 200;
return a + b;
}
});
//创建线程对象
Thread t = new Thread(task);
//启动线程
t.start();
//这里main方法是在主线程当中
//在主线程中获取线程的返回结果使用get()方法
System.out.println(task.get());
//main方法下面的程序想要执行必须等待get()方法结束
//get方法可能需要很久,因为get方法为了拿另一个线程的执行结果。另一个线程需要时间。
//get方法执行会导致当前线程执行阻塞
System.out.println("get()获取完毕");
}
}
本小结涉及到操作系统中的进程管理:操作系统学习第二章进程管理
public class ThreadTest {
public static void main(String[] args) {
//创建线程对象
MyThread2 t = new MyThread2();//默认名字:Thread-0
//设置线程名字
t.setName("t1");//修改为t1
//获取线程的名字
System.out.println(t.getName()); //t1
MyThread2 t2 = new MyThread2(); //默认名字:Thread-1
//不修改名称
System.out.println(t2.getName()); //Thread-1
}
}
class MyThread2 extends Thread{
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println("分支线程——>" + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//currentThread表示当前线程对象
//这个代码出现在main方法当中,所以当前线程就是主线程。
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()); //main
//创建线程对象
MyThread2 t = new MyThread2();//默认名字:Thread-0
//设置线程名字
t.setName("t1");//修改为t1
t.start();
//获取线程的名字
System.out.println(t.getName()); //t1
MyThread2 t2 = new MyThread2(); //默认名字:Thread-1
t2.start();
//不修改名称
System.out.println(t2.getName()); //Thread-1
}
}
class MyThread2 extends Thread{
public void run(){
for (int i = 0; i < 100; i++) {
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName() + "——>" + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//让当前进程进入休眠,睡眠时间5秒
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("咖啡加点ice"); //5秒之后才会打印
}
}
public class ThreadTest {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "——>" + i);
//睡眠一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//创建线程对象
Thread t = new MyThread3();
t.setName("t");
t.start();
//调用sleep方法
try {
t.sleep(1000*5); //sleep作为静态方法,在这里调用t.sleep会转换为:Thread.sleep(1000*5)
//这段代码是让当前线程进入休眠,即main方法休眠
//这方法出现在在main方法中,是main方法休眠,而不是t休眠
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("主线程"); //5秒钟后才能打印
}
}
class MyThread3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("分支线程——>" + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
Thread t = new Thread(new MyRunable2());
t.setName("t");
t.start();
//希望5秒后t线程终止休眠
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//终止t线程的睡眠,这种中断睡眠的方式,依靠了Java的异常处理机制
t.interrupt(); //这个方法调用,t中的sleep会捕获到异常,就会提前结束
}
}
class MyRunable2 implements Runnable{
//重点:run()方法当中的异常不能throws,只能try.catch
//因为run()方法在父类中没有任何异常,子类不能比父类抛出更多的异常
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "——> begin" );
try {
//睡眠一小时
Thread.sleep(1000*60*60);
} catch (InterruptedException e) {
//打印异常信息
e.printStackTrace();
}
//1小时后才会执行这里
System.out.println(Thread.currentThread().getName() + "——> over" );
}
}
强制终止线程:stop(),此方法容易丢失数据,不建议使用。
合理的终止一个线程执行,通过设置一个标记进行终止。
public class ThreadTest {
public static void main(String[] args) {
MyRunnable3 r = new MyRunnable3();
Thread t = new Thread(r);
t.setName("t");
t.start();
//主程序延迟5秒执行t线程的终止
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒到了,强制终止t线程
//t.stop();会直接丢失数据不建议使用
r.run=false;
}
}
class MyRunnable3 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) {
throw new RuntimeException(e);
}
}else {
//return就结束了,在return将需要保存的数据进行保存。
return;
}
}
}
}
常见的线程调度模型:
抢占式调度模型:线程的优先级比较高,抢到的CPU时间片概率高。Java采用的就是抢占式调度模型。
均分式调度模型:平均分配CPU时间片,每个线程占有的CPU时间片长度一样,平均分配一切平等。
java中提供与线程调度有关的方法:
实例方法
更改线程优先级:void setPriority(int newPriority)
获取线程优先级:int getPriority()
等待某个线程结束:void join()
//线程合并
public class ThreadTest {
public static void main(String[] args) {
System.out.println("main begin");
Thread t = new Thread(new MyRunnable5());
t.setName("t");
t.start();
try {
t.join();//t合并到当前线程中,当前线程受阻塞,t线程执行到结束
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("main over"); //t执行结束才会执行这里。
}
}
class MyRunnable5 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "——>" +i);
}
}
}
最低优先级1常量:MAX_PRIORITY
默认优先级5常量:MIN__PRIORITY
最高优先级10常量:NORM__PRIORITY
//关于线程优先级
public class ThreadTest {
public static void main(String[] args) {
System.out.println("最高优先级:" + MAX_PRIORITY); //最高优先级:10
System.out.println("最低优先级:" + MIN_PRIORITY); //最低优先级:1
System.out.println("默认优先级:" + NORM_PRIORITY); //默认优先级:5
//获取当前线程对象,获取当前线程优先级
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName() + "线程的默认优先级是:" + currentThread.getPriority()); //main线程的默认优先级是5
Thread t = new Thread(new MyRunable3());
t.setName("t");
t.start();
}
}
class MyRunable3 implements Runnable{
@Override
public void run() {
//获取当前线程优先级
System.out.println(Thread.currentThread().getName() + "线程没有设置时的优先级是:" + Thread.currentThread().getPriority()); //t线程没有设置时的优先级是:5
}
}
静态方法
暂停当时线程,执行其他线程:static void yield()
注:yield方法不是阻塞方法,让当前线程让位从运行状态回到就绪状态,将CPU时间片让给其他线程使用。
//让当前进行暂停,回到就绪状态,让给其他线程
//静态方法:Thread.yield();
public class ThreadTest {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable4());
t.setName("t");
t.start();
for (int i = 1; i <=10000; i++) {
System.out.println(Thread.currentThread().getName() + " ——> " + i);
}
}
}
class MyRunnable4 implements Runnable {
@Override
public void run() {
for (int i = 1; i <=10000; i++) {
//每100让位一次
if(i % 100 == 0){
Thread.yield(); //当前线程暂停一下,让给主线程
}
System.out.println(Thread.currentThread().getName() + " ——> " + i);
}
}
}
线程安全:在多线程并发环境下,数据的安全问题。
多线程并发环境下存在安全问题的条件:①多线程并发、②有共享数据、③共享数据有修改的行为。
线程同步:线程排队执行,用排队执行解决线程安全问题。
同步与异步:
异步(并发)模型:线程t1与线程t2互相不影响,各自执行。即多线程并发。
同步(排队)模型:线程t1和线程t2之间,一个线程在执行时,另一个线程要等待前面线程执行结束才能执行。线程排队执行。
synchronized(共享对象){ }:假设t1与t2线程并发,t1遇到了synchronized,就占用这把锁,直到代码执行结束这把锁才会释放,在未释放前,t2遇到synchronized只能等待t1执行结束synchronized里边的代码才能够执行synchronized里边的代码。
【例子】模拟两个线程对同一个账户取款。
银行账户设置了线程安全synchronized(){ },如果没有设置线程安全将会出现数据安全问题。
//银行账户
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 withdrad(double money){
//线程同步机制
//synchronized ()括号中数据必须是多线程共享的数据,才能达到线程同步。
//()里填需要线程同步的共享对象
synchronized (this){
//线程同步机制代码块
//一个线程把这里代码全部执行结束后,另一个线程才能进来。
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(after>=0){
this.setBalance(after);
}else {
System.out.println("余额不足");
}
}
}
}
设置线程模拟取款
public class AccountThread extends Thread{
//两个线程必须共享一个账户对象
private Account act;
//通过构造方法传过来账户对象
public AccountThread(Account act){
this.act = act;
}
public void run() {
//run表示取款操作
//假设取款5000
double money = 5000;
//取款
act.withdrad(money);
System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款成功,余额:" + act.getBalance());
}
}
实现测试类
public class Test {
public static void main(String[] args) {
//创建账户对象(只创建一个)
Account act = new Account("act-001",10000);
//创建两个线程
Thread t1 = new AccountThread(act);
Thread t2 = new AccountThread(act);
//设置线程name
t1.setName("t1");
t2.setName("t2");
//启动线程取款
t1.start(); //t1对act-001取款成功,余额:5000.0
t2.start(); //t2对act-001取款成功,余额:0.0
}
}
//取款方法
public synchronized void withdrad(double money){
double before = this.getBalance();
double after = before - money;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(after>=0){
this.setBalance(after);
}else {
System.out.println("余额不足");
}
}
synchronized使用在实例方法上,代码比较简洁了,如果共享的对象是this,并且需要同步的是整个方法体,使用这种方式。
synchronized出现在静态方法上(带static方法),是类锁,不管创建几个对象这个方法同步。
package studyThread;
public class ThreadTest {
public static void main(String[] args) {
Thread t = new BakDataThread();
t.setName("这是一个守护线程");
//在启动线程之前将线程设置成一个守护线程,
t.setDaemon(true);//设置成守护线程之后,如果这里线程结束,即使是死循环线程也会结束
t.start();
//主线程:主线程是用户线程
//在没有设置守护线程时,主线程的for循环结束后,死循环的线程还会继续执行
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "——>" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
//写一个死循环的线程
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) {
throw new RuntimeException(e);
}
}
}
}
定时器的作用:间隔特定的时间执行特定的程序。
定时器在java中实现方式:
第一种:使用sleep方法,睡眠。设置睡眠时间,每到这个时间点醒来执行任务。
第二种:java.util.Timer可以直接用,目前开发很少用,很多高级框架支持定时任务。
第三种:目前使用最多的是Spring框架中的SpringTask框架。
public class TimerTest {
public static void main(String[] args) throws Exception {
//创建定时器对象
Timer timer = new Timer();
//Timer timer = new Timer(true);守护线程的方式
//指定定时任务
//timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2024-01-22 16:39:00");
timer.schedule(new LogTimerTask(),firstTime,1000*10);
}
}
//编写一个定时任务类
//一个记录日志的定时任务
class LogTimerTask extends TimerTask{
@Override
public void run() {
//编写需要执行的任务
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date()) + ":完成了一次定时任务");
}
}
——本章节为个人学习笔记。学习视频为动力节点Java零基础教程视频:动力节点—JAVA零基础教程视频