系列文章目录
文章目录
前言
本人为java初学者,文章内容仅为个人学习总结分享,其中包含了大量Java核心技术卷一里面的文章内容以及一些网站文章内容,由于参考文章过多,在此并不一一列出,如有侵权,请联系删除。
一、多线程
1.1、什么是进程?什么是线程?
1.2、基本线程数量
1.3、进程和线程是什么关系?
举个例子:
注意:
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
1.4、思考一个问题:
1.5、分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?
1.6、java语言中,实现线程有两种方式,那两种方式呢?
java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。
第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。
// 定义线程类 public class MyThread extends Thread{
public void run(){
} } // 创建线程对象 MyThread t = new MyThread(); // 启动线程。 t.start();
代码展示:
public class Test02 {
public static void main(String[] args) {
AZX azx=new AZX(); //启动线程 //start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。 //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了。 //启动成功的线程会自动调用run方法,并且run方在分支的栈底部(压栈)。 //run方法在分支的底部,main方法在主栈的栈底部。run和main是平级的。 azx.start(); for (int i=0;i<1000;i++){
System.out.println("主线程------>"+i); } } } class AZX extends Thread{
@Override public void run() {
for (int i=0;i<1000;i++){
System.out.println("分支线程------>"+i); } } }
第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。
// 定义一个可运行的类 public class MyRunnable implements Runnable {
public void run(){
} } // 创建线程对象 Thread t = new Thread(new MyRunnable()); // 启动线程 t.start();
代码展示:
public class Test02 {
public static void main(String[] args) {
//创建一个可运行的对象 MyRunnable r=new MyRunnable(); //将可运行的对象封装成一个线程对象 Thread t=new Thread(r); //启动线程 t.start(); for (int i=0;i<100;i++){
System.out.println("主线程------>"+i); } } } class MyRunnable implements Runnable{
@Override public void run() {
for (int i=0;i<100;i++){
System.out.println("分支线程------>"+i); } } }
1.7、线程的生命周期

1.8、线程的方法
代码展示:
public class Test02 {
public static void main(String[] args) {
//让当前线程进行休眠,5秒后唤醒 try {
//注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用 Thread.sleep(1000*5); } catch (InterruptedException e) {
e.printStackTrace(); } //5秒后执行这里的代码 System.out.println("HelloWorld!!!"); //获取当前对象线程的名字 Thread currentThread =Thread.currentThread(); System.out.println(currentThread.getName()); //创建一个线程对象 AZX r=new AZX(); //设置线程的名字 r.setName("ttttt"); //获取线程的名字 String tName= r.getName(); System.out.println(tName); //启动线程 r.start(); for (int i=0;i<10;i++){
System.out.println("主线程------>"+i); } } } class AZX extends Thread{
@Override public void run() {
for (int i=0;i<10;i++){
//currentThread就是当前线程对象。当前线程是谁呢? //当t1我程执行run方法,那么这个当前线程就是t1 //当t2线程执行run方法,那么这个当前线程就是t2 Thread currentThread =Thread.currentThread(); System.out.println(currentThread.getName()+":分支线程------>"+i); } } }
代码展示(在java中怎么强行终止一个线程的执行):
直接终止:
这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。
public class Test02 {
public static void main(String[] args) {
Thread t=new Thread(new AZX()); t.setName("t"); t.start(); //模拟5秒 try {
//注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用 Thread.sleep(1000*5); } catch (InterruptedException e) {
e.printStackTrace(); } //5秒后强行终止t线程 t.stop();//已过时,不建议使用 } } class AZX extends Thread{
@Override public void run() {
for (int i=0;i<10;i++){
//执行十秒 Thread currentThread =Thread.currentThread(); System.out.println(currentThread.getName()+":分支线程------>"+i); try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } } } }
结果:

间接终止:这种方法安全
public class Test02 {
public static void main(String[] args) {
MyRunnable r=new MyRunnable(); Thread t=new Thread(r); t.setName("t"); t.start(); //模拟5秒 try {
//注意:sleep方法是静态方法,无论是谁调用,最后它都只会让当前线程睡眠,即便是用其它线程的名字调用也没用 Thread.sleep(1000*5); } catch (InterruptedException e) {
e.printStackTrace(); } //5秒后强行终止t线程 r.run=false; } } class MyRunnable implements Runnable{
//打一个布尔标记 boolean run=true; @Override public void run() {
for (int i=0;i<10;i++){
if(run){
//执行十秒 Thread currentThread =Thread.currentThread(); System.out.println(currentThread.getName()+":分支线程------>"+i); try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } }else {
//终止当前线程 return; } } } }
结果:

1.9、线程安全
为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:*)
什么时候数据在多线程并发的环境下会存在安全问题呢?
满足以上3个条件之后,就会存在线程安全问题。
怎么解决线程安全问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)。
用排队执行解决线程安全问题。这种机制被称为:线程同步机制。
专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。
说到线程同步这块,涉及到这两个专业术语:
异步编程模型:
异步就是并发。
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。
效率较低:线程排队执行
同步就是排队。
1.10、模拟俩个线程对同一账号取款
Account类:
public class Account {
//账户 private String actno; //余额 private double balance; //构造方法 public Account(){
} public Account(String actno, double balance) {
this.actno = actno; this.balance = balance; } //set与get方法 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 (this){
//取款之前的余额 double before=this.getBalance(); //取款之后的余额 double after=before-money; try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } //更新余额 this.setBalance(after); } } }
AccountThread类:
public class AccountThread extends Thread{
//俩个线程共享同一个账户对象 private Account act; //构造方法 public AccountThread(Account act) {
this.act = act; } public void run(){
//run方法的执行表示取款操作 double money=5000; //取款 act.withdraw(money); System.out.println(Thread.currentThread().getName()+"对"+act.getActno()+"取款成功,余额:"+act.getBalance()); } }
Test类:
public class AccountTest01 {
public static void main(String[] args) {
//创建账户对象(只创建1个) 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(); t2.start(); } }
结果:

代码执行原理(锁池):


提示:
这里需要注章的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。
小总结:

1.11、死锁
图示:T1从上往下,T2从下往上,T1锁住第一个后再锁第二个;T2锁住第二个后再锁第一个,此时双方都锁住了要锁的第一个,而要锁的第二个被对方锁住,从而造成死锁。

代码展示:
public class DeadLock {
public static void main(String[] args) {
Object o1=new Object(); Object o2=new Object(); //t1和t2俩个线程共享o1、o2 Thread t1=new MyThread1(o1,o2); Thread t2=new MyThread22(o1,o2); t1.start(); t2.start(); } } class MyThread1 extends Thread{
Object o1; Object o2; public MyThread1(Object o1, Object o2) {
this.o1 = o1; this.o2 = o2; } public void run(){
synchronized (o1){
try {
System.out.println("MyThead1-begin"); Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } synchronized (o2){
System.out.println("MyThead1-end"); } } } } class MyThread22 extends Thread{
Object o1; Object o2; public MyThread22(Object o1, Object o2) {
this.o1 = o1; this.o2 = o2; } public void run(){
synchronized (o2){
try {
System.out.println("MyThead22-begin"); Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } synchronized (o1){
System.out.println("MyThead22-end"); } } } }
结果:卡住了

我们以后开发中应该怎么解决线程安全问题?
1.12、守护线程:
图示:

代码展示:
package Package03; public class ThreadTest04 {
public static void main(String[] args) {
Thread t=new BakDataThread(); //启动线程之前,将线程设置为守护线程 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(); } } } }
结果:主线程即main线程结束,守护线程也结束(其死循环结束)

1.13、定时器:
了解:
定时器:Timer类:

注意schedule类:

定时器的作用:
间隔特定的时间,执行特定的程序。
列如:每周要进行银行账户的总账操作、每天要进行数据的备份操作。
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:
可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
代码展示:
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimerTest {
public static void main(String[] args) throws Exception {
//创建定时器对象 Timer timer=new Timer(); //指定定时任务 SimpleDateFormat sdf=new SimpleDateFormat("yyyy-mm-dd HH:mm:ss"); Date firstTime=sdf.parse("2022-02-14 11:15:00"); timer.schedule(new LogTimerTask(), firstTime, 1000*10); } } //编写一个定时任务类 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+":成功完成了一次数据备份!"); } }
结果:

1.14、关于Object类中的wait和notify方法。(生产者和消费者模式!)

图示:

(生产者和消费者模式!):



代码展示:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class ThreadTest01 {
public static void main(String[] args) throws Exception{
//创建1个仓库对象,共享的 List list=new ArrayList(); //创建俩个线程对象 //生产者线程 Thread t1=new Thread(new Producer(list)); //消费者线程 Thread t2=new Thread(new Consumer(list)); t1.setName("生产者线程"); t2.setName("消费者线程"); //启动线程 t1.start(); t2.start(); } } //生产线程 class Producer implements Runnable{
//仓库 private List list; //数组大小 private int size=0; //构造方法 public Producer(List list) {
this.list = list; } @Override public void run() {
//一直生产(使用死循环来模拟一直生产) while (true){
//给仓库对象List加锁 synchronized (list){
if (list.size()>0){
try {
list.wait(); } catch (InterruptedException e) {
e.printStackTrace(); } } //程序能够执行到这里说明仓库是空的,可以生产 Object obj=new Object(); //获取数组大小 for(int i = 0 ; i < list.size() ; i++) {
size=i+1; } obj=size; list.add(obj); System.out.println(Thread.currentThread().getName()+"--->"+obj); //唤醒消费者进行消费 list.notify(); } } } } //消费线程 class Consumer implements Runnable{
//仓库 private List list; //数组大小 private int size=0; //构造方法 public Consumer(List list) {
this.list = list; } @Override public void run() {
//一直消费 while (true){
//给仓库对象List加锁 synchronized (list){
if (list.size()==0){
try {
//仓库已经空了 //消费者线程等待 list.wait(); } catch (InterruptedException e) {
e.printStackTrace(); } } //程序能够执行到这里说明仓库是有数的,可以消费 //获取数组大小 for(int i = 0 ; i < list.size() ; i++) {
size=i; } Object obj=list.remove(size); System.out.println(Thread.currentThread().getName()+"--->"+obj); //唤醒生产者进行消费 list.notify(); } } } }
结果:(生产和消费交替运行)

总结
以上就是本文的内容,记录了一些关于java“多线程”的内容,本人也是刚开始接触java,不能保证总结内容的正确性,若是有错误的话,欢迎大家指出,谢谢!
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/210573.html原文链接:https://javaforall.net
