Java核心技术卷一 -第一章:java“白皮书”的关键术语
Java核心技术卷一 -第三章:数据类型
Java核心技术卷一 -第三章:变量与常量
Java核心技术卷一 -第三章:运算符
Java核心技术卷一 -第三章:字符串
Java核心技术卷一 -第三章:输入与输出
Java核心技术卷一 -第三章:数组
Java核心技术卷一 -第四章:类之间的关系-依赖
Java核心技术卷一 -第四章:预定义类-LocalDate类小应用
Java核心技术卷一 -第四章:构造器
Java核心技术卷一 -第四章:null引用
Java核心技术卷一 -第四章:方法参数
Java核心技术卷一 -第四章:对象构造
Java核心技术卷一 -第五章:覆盖方法与super
Java核心技术卷一 -第五章:super关键字
Java核心技术卷一 -第五章:类的强制类型转换与instanceof操作符
Java核心技术卷一 -第五章:抽象类与abstract关键字
Java核心技术卷一 -第五章:Object类的toString方法
Java核心技术卷一 -第五章:数组列表初识
Java核心技术卷一 -第五章:装箱和拆箱
Java核心技术卷一 -第五章:枚举类再认识
Java核心技术卷一 -第七章:异常
Java核心技术卷一 -第九章:集合
Java核心技术卷一 -第六章:抽象类和接口
Java核心技术卷一 -第十章:IO流
本人为java初学者,文章内容仅为个人学习总结分享,其中包含了大量Java核心技术卷一里面的文章内容以及一些网站文章内容,由于参考文章过多,在此并不一一列出,如有侵权,请联系删除。
进程是一个应用程序(1个进程是一个软件)
线程是一个进程中的执行场景/执行单元。
一个进程可以启动多个线程。
对于java程序来说,当在DOS命令窗口中输入:java HelloWorld回车之后。会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法。
同时再启动一个垃圾回收线程负责看护,回收垃圾。
最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。
阿里巴巴:进程
马云:阿里巴巴的一个线程
童文红:阿里巴巴的一个线程
…
京东:进程
强东:京东的一个线程
妹妹:京东的一个线程
进程可以看做是现实生活当中的公司,
线程可以看做是公司当中的某个员工。
进程A和进程B的内存独立不共享。(阿里巴巴和京东资源不会共享的!)线程A和线程B呢?
在java语言中:
线程A和线程B,堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
火车站,可以看做是一个进程:
火车站中的每一个售票窗口可以看做是一个线程。
我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。
所以多线程并发可以提高效率。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。
使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。
对于多核的CPU电脑来说,真正的多线程并发是没问题的。
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
什么是真正的多线程并发?
t1线程执行t1的。
t2线程执行t2的。
t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。
单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。
对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于
CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是:多个事情
同时在做!!!!!
线程A:播放音乐
线程B:运行魔兽游戏
线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,
给我们的感觉是同时并发的。
电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,
人类的眼睛产生了错觉,感觉是动画的。这说明人类的反应速度很慢,就像
一根钢针扎到手上,到最终感觉到疼,这个过程是需要“很长的”时间的,在
这个期间计算机可以进行亿万次的循环。所以计算机的执行速度很快。
java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。
// 定义线程类
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);
}
}
}
// 定义一个可运行的类
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);
}
}
}
注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承
其它的类,更灵活。
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);
}
}
}
这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。
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:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)。
用排队执行解决线程安全问题。这种机制被称为:线程同步机制。
专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。
其实就是:多线程并发(效率较高。)
异步就是并发。
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。
效率较低:线程排队执行
同步就是排队。
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);
}
}
}
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());
}
}
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();
}
}
这里需要注章的是:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。
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");
}
}
}
}
是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
…
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)
…
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。
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线程结束,守护线程也结束(其死循环结束)
间隔特定的时间,执行特定的程序。
列如:每周要进行银行账户的总账操作、每天要进行数据的备份操作。
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在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+":成功完成了一次数据备份!");
}
}
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,不能保证总结内容的正确性,若是有错误的话,欢迎大家指出,谢谢!