javaSE基础知识之线程
欢迎留言学习交流
文章目录
一、基础概念
二、创建线程的三种方式
三、线程的基本方法学习
四、线程模型基础学习
五、创建同步(synchronize)
六、线程死锁示例
七、线程经典范例:两个线程实现交替执行
八、线程同步4个问题的探讨
九、线程生命周期示意图
一、基础概念
1.进程:指一个程序的运行,它是操作系统进行资源分配的最小单元。
2.线程:指一个程序中的执行场景或执行单元。
3.单核CPU:同一个时间点,只能执行一个线程。
4.多核CPU:同一个时间点,可以并发执行多个线程。
二、创建线程的三种方式
1.继承 extends Thread,需要重写run()方法
package com.study.thread;
/**
* 实现线程的方式1:继承java.lang.Thread,重写其run()方法
*/
public class SecondThreadDemo {
public static void main(String[] args) {
/ 1.创建线程对象
ExtendsThreadDemo extendsThreadDemo=new ExtendsThreadDemo();
// 2.启动线程
/*
start()方法的作用:启动一个分支线程,在JVM中开辟一个新的栈空间,代码执行的任务是开辟栈空间即结束
线程启动成功后会自动调用run()方法,并且run方法在分支栈的栈底部(压栈)
run方法所在的栈和main方法所在的栈是平级的
如果使用extendsThreadDemo.run()调用就是普通方法的调用,即是在main线程中
*/
// 普通调用类的方法
//extendsThreadDemo.run();
// 启动线程
extendsThreadDemo.start();
for(int i=0;i<20;i++){
System.out.println("主线程执行序号为:"+i);
}
}
}
/**
* 实现线程的方式1:继承java.lang.Thread,重写其run()方法
*/
class ExtendsThreadDemo extends Thread{
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println("继承Thread执行线程序号为:"+i);
}
}
}
2.实现 Runnable接口,需要重写run()方法
package com.study.thread;
/**
* 实现线程的方式2:实现java.lang.Runnable接口。推荐使用第二种方式
*/
public class SecondThreadDemo {
public static void main(String[] args) {
// 1.创建实例线程对象
ImplementsRunnableDemo implementsRunnableDemo=new ImplementsRunnableDemo();
// 2.创建线程
Thread thread=new Thread(implementsRunnableDemo);
// 3.启动线程
thread.start();
for(int i=0;i<20;i++){
System.out.println("主线程执行序号为:"+i);
}
}
/**
* 实现线程的方式2:实现java.lang.Runnable接口
*/
class ImplementsRunnableDemo implements Runnable{
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println("实现接口Runnable执行线程序号为:"+i);
}
}
}
3.实现Callable接口,重写其call()方法
package com.study.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 第三种方式创建线程,实现Callable接口:
* 该方式可以获取线程的返回值
* 缺点是效率低,造成当前线程阻塞
*/
public class ThreeThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.创建一个Callable实现类对象
CallableExample callableExample = new CallableExample();
// 2.创建一个未来任务类
FutureTask task=new FutureTask(callableExample);
// 3.创建线程
Thread t=new Thread(task);
// 4.启用线程
t.start();
// 5.获取返回结果(会导致当前线程阻塞)
Object obj = task.get();
// 6.输出获取的结果
System.out.println("返回的结果:"+obj);
}
}
/**
*创建实现类
*/
class CallableExample implements Callable{
@Override
public Object call() throws Exception {
System.out.println("返回结果值:");
return 9;
}
}
4.总结:
(1)线程的创建也可以用匿名对象,减少代码量。
(2)三种创建线程的方式,一般推荐使用第二种,实现接口的方式,因为java不能多继承,但是可以多实现。第三种方式是在对执行结果需要返回值时才使用。
(3)main方法执行也是一个线程,默认名是main,进程执行时,垃圾回收线程也会执行
三、线程方法的基本学习
1.常用线程的相关属性及其方法
package com.study.thread;
/**
* 线程方法学习
*/
public class ThreadFieldDemo {
public static void main(String[] args){
// 创建实现接口的实例对象
ThreadFieldExample threadFieldExample=new ThreadFieldExample();
// 创建线程
Thread thread=new Thread(threadFieldExample);
// 属性:获取类加载器
ClassLoader contextClassLoader = thread.getContextClassLoader();
// 属性:获取线程的标志符
long id = thread.getId();
// 静态属性:获取当前线程的名字(这是一个静态方法)
String name1 = Thread.currentThread().getName();
System.out.println("输出当前线程(即main线程)的名字为:"+name1);
// 名字属性:设置线程的名字
thread.setName("实例线程");
// 名字属性:获取线程的名字
String name = thread.getName();
// 优先级属性:获取优先级
int priority = thread.getPriority();
// 优先级属性:优先级设置(默认是5,即Thread.NORM_PRIORITY,最小是1=Thread.MIN_PRIORITY,最大是10=Thread.MAX_PRIORITY)
thread.setPriority(Thread.MAX_PRIORITY);
// 状态属性:判断是否还是活动的
boolean alive = thread.isAlive();
// 启动属性:启动线程
thread.start();
// 中断属性:判断是否中断
boolean interrupted = thread.isInterrupted();
// 中断属性:中断线程
thread.interrupt();
try {
// 属性:睡眠,可以间隔特定的时间执行,阻塞的是当前线程,即main线程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1秒之后执行这句话");
// 执行主线程的语句
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"正在执行....,正在打印:"+i);
}
}
class ThreadFieldExample implements Runnable{
@Override
public void run() {
for(int i=1;i<=10;i++){
try {
// 属性:睡眠,可以间隔特定的时间执行(这里是1000毫秒,即1秒),阻塞的是当前线程
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前执行线程的名字
String name = Thread.currentThread().getName();
System.out.println(name+"执行中.......,正在打印数字:"+i);
}
}
}
2.结束线程执行的另一种方式
package com.study.thread;
/**
* 1.终止线程执行的一种方式:使用标记符
*
* 2.线程调度:
* 抢占式调度模型:线程的优先级越高,抢占到的CPU时间片的概率就高一些/多一些,java采用的就是抢占式调度模型
* 均分布调度模型:平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样
* 3.常用的java线程调度方法:setPriority(int newPriority)设置线程的优先级
* 最低1,最高10,默认是5
* 4.static void yield() 让位方法:暂停当前正在执行的线程,回到就绪状态,之后可能又会马上执行
*
* 5.合并线程:void join(),当前线程阻塞,加入线程执行,直至执行结束
*
*/
public class ThreadStopDemo {
public static void main(String[] args) throws InterruptedException {
StopExample stopExample = new StopExample();
Thread thread=new Thread(stopExample);
thread.start();
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
stopExample.flag=false;
Thread t2=new Thread(new YieldExample());
t2.start();
for(int i=0;i<10;i++){
if(i==5){
// 主线程让步,重新抢CPU时间片执行
Thread.yield();
}else if(i==3){
// t2线程先执行完,才继续执行main线程:join(long millis,int nanos),millis是毫秒,nanos是纳秒
t2.join();
}
System.out.println(Thread.currentThread().getName()+"执行中....,,正在打印数字:"+i);
}
}
}
class StopExample implements Runnable{
// 执行标记
boolean flag=true;
@Override
public void run() {
for(int i=0;i<20;i++){
if(flag){
System.out.println("当前线程执行中..."+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
System.out.println("保存最后的数据");
return;
}
}
}
}
class YieldExample implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行中....,,正在打印数字:"+i);
}
}
}
3.守护线程
package com.study.thread;
/**
* 守护线程:后台线程,类似于垃圾回收线程
* 用户线程和守护线程
*
* 1.守护线程的特点:
* 1)一般守护线程是一个死循环
* 2)自动结束
* 2.使用
* 1)数据的自动备份(配合定时器)
* 2)所有线程结束,它也会自动结束
*/
public class ThreadDaemonDemo {
public static void main(String[] args) throws InterruptedException {
Thread t=new DaemonExample();
t.setName("备份数据线程");
// 属性:设置为守护线程
t.setDaemon(true);
t.start();
// 主线程
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"正在执行中....."+i);
Thread.sleep(1000);
}
}
}
class DaemonExample 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();
}
}
}
}
4.定时器学习
package com.study.thread;
/**
* 间隔特定的时间,执行特定的程序
*
* 1.使用sleep
* 2.使用java.util.Timer
* 3.使用spring的springTask
*/
public class TimerDemo {
public static void main(String[] args) {
// 1.创建定时对象
Timer timer = new Timer();
// 2.创建守护定时对象
//Timer timer1 = new Timer(true);
// 3.指定定时任务(定时任务、执行时间、间隔多久执行一次)
Date date = new Date();
// 创建定时任务方式1:
timer.schedule(new MyTimerTask(),date,1000);
// 创建定时任务方式2:
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello you");
}
},date,1000);
}
}
class MyTimerTask extends TimerTask{
@Override
public void run() {
System.out.println(LocalDate.now().getDayOfYear());
}
}
四、线程模型基础学习
* 1.线程安全问题发生的条件
* 1)多线程并发
* 2)有共享数据
* 3)共享数据有修改行为
* 2.解决方法(线程同步机制)
* 1)线程排队执行,即不能并发(专业术语是线程同步)
* 2)会导致效率有点降低
* 3.线程模型
* 1)异步编程模型:线程并发执行
* 2)同步编程模型:线程排队执行(同步执行)
*
* 4.开发中如何解决线程安全问题
* 1)在必须使用的情况才选择同步机制
* 2)尽量使用局部变量代替实例变量和静态变量
* 3)对于实例变量,使用创建多个对象
五、线程同步(synchronize)
package com.study.thread;
import com.study.bean.Account;
/**
* 模拟两个线程对同一个账户执行
*
* 1.java中的三大变量
* 1)实例变量:在堆中(多线程共享时,可能存在线程安全问题)
* 2)静态变量:在方法区(多线程共享时,可能存在线程安全问题)
* 3)局部变量:在栈中(不会有线程安全问题)
* 2.synchronize()可以存放的位置:方法、内部区域块
* 1)放在方法上,共享对象只能是this,缺点,扩大同步代码块,效率会降低,好处就是减少代码量。使用原则:整个方法体需要同步,且同步对象是this则使用
* 2)在静态方法上使用:表示类锁永远只有一把
* 3)代码块上:提高执行效率且灵活
* 3.局部变量使用:StringBuilder,StringBuffer效率低
* 4.ArrayList/HashMap/HashSet是非线程安全的
* 5.Vector/HashTable是线程安全的
*
*/
public class ThreadAccountDemo {
public static void main(String[] args) {
// 1.创建账户对象
Account account = new Account("preston", 20000);
// 2.创建两个线程
Thread t1=new AccountExample(account);
Thread t2=new AccountExample(account);
t1.setName("t1");
t2.setName("t2");
// 3.启动线程
t1.start();
t2.start();
}
}
class AccountExample extends Thread{
private Account account;
public AccountExample(Account account){
this.account=account;
}
@Override
public void run() {
double money=2000;
account.withDraw(money);
System.out.println(Thread.currentThread()+" 对账户:"+account.getName()+"取款成功,余额为:"+account.getBalance());
}
}
// Account类
package com.study.bean;
public class Account {
private String name;
private double balance;
public Account(){}
public Account(String name, double balance) {
this.name = name;
this.balance = balance;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
/*
* 当多线程对同一个账户操作时,可能发生线程安全问题
* 线程同步语法:
* synchronized(括号中的数据必须是多线程共享的数据,才能达到多线程排队){线程同步块}
*/
public void withDraw(double money){
// 测试发现放在这里并不是完全安全的(需要添加Thread.sleep())解决
synchronized (this) {
// 取款之前
double before = this.getBalance();
// 取款之后
double after = before - money;
try {
Thread.sleep(20);// 没有该语句还是存在问题
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新账户余额
this.setBalance(after);
}
}
}
六、线程死锁示例
package com.study.thread;
/**
* 死锁实例:该例子程序中的两个线程已进入死锁状态
* 尽量不要嵌套使用synchronize
*/
public class ThreadDeadLockDemo {
public static void main(String[] args) {
Object object1 = new Object();
Object object2 = new Object();
Thread t1=new DeadLockExample1(object1,object2);
Thread t2=new DeadLockExample2(object1,object2);
t1.start();
t2.start();
}
}
class DeadLockExample1 extends Thread{
private Object object1;
private Object object2;
public DeadLockExample1(Object object1,Object object2){
this.object1=object1;
this.object2=object2;
}
@Override
public void run() {
synchronized (object1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2){
System.out.println("实验1......");
}
}
}
}
class DeadLockExample2 extends Thread{
private Object object1;
private Object object2;
public DeadLockExample2(Object object1,Object object2){
this.object1=object1;
this.object2=object2;
}
@Override
public void run() {
synchronized (object2){
synchronized (object1){
System.out.println("实验2......");
}
}
}
}
七、线程经典范例:两个线程实现交替执行
package com.study.thread;
import java.util.List;
/**
* 1.wait方法调用是对象调用,使当前对象上的线程进入等待状态,直到最终调用notify方法
* 2.notify方法是唤醒在该对象上等待的线程
*/
public class ThreadProductDemo {
public static void main(String[] args) {
Num num=new Num();
Thread t1=new Thread(new Productor(num));
Thread t2=new Thread(new Consumer(num));
t1.start();
t2.start();
}
}
class Productor implements Runnable{
private Num num;
public Productor(Num num){
this.num=num;
}
@Override
public void run() {
while (true){
synchronized (num) {
if(num.i %2==0){// 偶数释放锁
try {
// 当前线程进入等待状态,并且释放num对象的锁
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"是奇数线程输出:"+num.i);
num.i++;
num.notify();
}
}
}
}
class Consumer implements Runnable{
private Num num;
public Consumer(Num num){
this.num=num;
}
@Override
public void run() {
while (true){
synchronized (num) {
if(num.i %2 !=0){// 奇数释放锁
try {
// 当前线程进入等待状态,并且释放num对象的锁
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"是偶数线程输出:"+num.i);
num.i++;
num.notify();
}
}
}
}
class Num{
int i;
}
八、线程同步4个问题的探讨
1.实例1:doPlay()方法的执行不需要等待doStudy()方法执行完。因为doPlay()方法并没有被锁,不需要获得锁就可以执行
package com.study.thread;
public class ThreadExamDemo {
public static void main(String[] args) throws InterruptedException {
ExamExample examExample = new ExamExample();
Thread t1=new ThreadExample(examExample);
Thread t2=new ThreadExample(examExample);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class ThreadExample extends Thread{
private ExamExample examExample;
public ThreadExample(ExamExample examExample){
this.examExample=examExample;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
examExample.doStudy();
}
if(Thread.currentThread().getName().equals("t2")){
examExample.doPlay();
}
}
}
class ExamExample{
public synchronized void doStudy(){
System.out.println("开始学习......");
try {
Thread.sleep(1000*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("学习结束.....");
}
public void doPlay(){
System.out.println("开始玩耍......");
System.out.println("玩耍结束.....");
}
}
======================================测试结果============================================
开始学习......
开始玩耍......
玩耍结束.....
学习结束.....
Process finished with exit code 0
2.实例2:doPlay()方法的执行需要等待doStudy()方法执行完。因为doPlay()方法也加了锁
package com.study.thread;
public class ThreadExamDemo {
public static void main(String[] args) throws InterruptedException {
ExamExample examExample = new ExamExample();
Thread t1=new ThreadExample(examExample);
Thread t2=new ThreadExample(examExample);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class ThreadExample extends Thread{
private ExamExample examExample;
public ThreadExample(ExamExample examExample){
this.examExample=examExample;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
examExample.doStudy();
}
if(Thread.currentThread().getName().equals("t2")){
examExample.doPlay();
}
}
}
class ExamExample{
public synchronized void doStudy(){
System.out.println("开始学习......");
try {
Thread.sleep(1000*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("学习结束.....");
}
// 加锁
public synchronized void doPlay(){
System.out.println("开始玩耍......");
System.out.println("玩耍结束.....");
}
}
=====================================测试结果=============================================
开始学习......
学习结束.....
开始玩耍......
玩耍结束.....
Process finished with exit code 0
3.实例3:doPlay()方法的执行不需要等待doStudy()方法执行完。因为调用doPlay()方法对象和调用doStudy()方法的对象不一样
package com.study.thread;
public class ThreadExamDemo {
public static void main(String[] args) throws InterruptedException {
ExamExample examExample = new ExamExample();
ExamExample examExample1 = new ExamExample();
Thread t1=new ThreadExample(examExample);
Thread t2=new ThreadExample(examExample1);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class ThreadExample extends Thread{
private ExamExample examExample;
public ThreadExample(ExamExample examExample){
this.examExample=examExample;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
examExample.doStudy();
}
if(Thread.currentThread().getName().equals("t2")){
examExample.doPlay();
}
}
}
class ExamExample{
public synchronized void doStudy(){
System.out.println("开始学习......");
try {
Thread.sleep(1000*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("学习结束.....");
}
// 加锁
public synchronized void doPlay(){
System.out.println("开始玩耍......");
System.out.println("玩耍结束.....");
}
}
=====================================测试结果=============================================
开始学习......
开始玩耍......
玩耍结束.....
学习结束.....
Process finished with exit code 0
4.实例4:doPlay()方法的执行需要等待doStudy()方法执行完。因为doPlay()方法和doStudy()方法均加上了锁,而且使用了static修饰,这相当于给类加上了锁(类锁只有一把),无论创建多少个对象,结果都一样,一个一个执行
package com.study.thread;
public class ThreadExamDemo {
public static void main(String[] args) throws InterruptedException {
ExamExample examExample = new ExamExample();
ExamExample examExample1 = new ExamExample();
Thread t1=new ThreadExample(examExample);
Thread t2=new ThreadExample(examExample1);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class ThreadExample extends Thread{
private ExamExample examExample;
public ThreadExample(ExamExample examExample){
this.examExample=examExample;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("t1")){
examExample.doStudy();
}
if(Thread.currentThread().getName().equals("t2")){
examExample.doPlay();
}
}
}
class ExamExample{
public synchronized static void doStudy(){
System.out.println("开始学习......");
try {
Thread.sleep(1000*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("学习结束.....");
}
// 加锁
public synchronized static void doPlay(){
System.out.println("开始玩耍......");
System.out.println("玩耍结束.....");
}
}
=====================================测试结果=============================================
开始学习......
学习结束.....
开始玩耍......
玩耍结束.....
Process finished with exit code 0
九、线程生命周期示意图
本文地址:https://blog.csdn.net/preston555/article/details/112298240
上一篇: CMD命令窗口运行Java桌面程序
下一篇: ps图片怎么快速制作成3D模型?