18.1 基本概念
18.1.1 程序和进程的概念
18.1.2 线程的概念
18.2 线程的创建(重中之重)
18.2.1 Thread类的概念
18.2.2 创建方式
18.2.3 相关的方法

18.2.4 执行流程
18.2.5 方式的比较
18.2.6 匿名内部类的方式
18.3 线程的生命周期(熟悉)

18.4 线程的编号和名称(熟悉)
18.4.1 相关方法

18.4.2 案例题目
package com.lagou.task17;
public class ThreadIdNameTest extends Thread{
public ThreadIdNameTest(String name) {
//调用父类的构造方法指定线程名称
super(name);
}
@Override
public void run() { //super.getId() super.getName()
System.out.println("子线程编号是:"+getId()+"; 名称是:"+getName()); //子线程编号是:14; 名称是:zhangfei
//修改后的线程名称
setName("guanyu");
System.out.println("子线程编号是:"+getId()+"; 修改后名称是:"+getName()); //子线程编号是:14; 修改后名称是:guanyu
}
public static void main(String[] args) {
//创建ThreadIdNameTest对象
ThreadIdNameTest tit = new ThreadIdNameTest("zhangfei");
//启动线程
tit.start();
//获取主线程的线程编号和线程名称
//获取当前线程就是主线程
Thread thread = Thread.currentThread();
System.out.println("主线程编号是:"+thread.getId()+" ;主线程名称是:"+thread.getName());
}
}
18.5 线程的常用方法(重点)

(1) sleep 线程阻塞
package com.lagou.task17;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThreadSleepTest extends Thread {
public boolean flag = true;
@Override
public void run() {
//每隔一秒钟打印系统时间,模拟时钟效果
while(flag){
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("当前系统时间:"+sdf.format(date));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//创建ThreadSleepTest对象
ThreadSleepTest tst = new ThreadSleepTest();
//启动线程
tst.start();
//主线程等待5秒之后结束子线程
System.out.println("主线程开始等待....");
try{
Thread.sleep(5000);
} catch(InterruptedException e){
e.printStackTrace();
}
//tst.stop();
tst.flag = false;
System.out.println("主线程等待结束");
}
}
(2)线程优先级管理
优先级高的线程不一定先执行, 但是优先级高的线程整体一定是先执行完成的.
Thread.MAX_PRIORITY 高 10
Thread.MIN_PRIORITY 低 1
Thread.NORM_PRIORITY 默认 5
package com.lagou.task17;
public class ThreadPriorityTest extends Thread{
//子线程和主线程默认的优先级是5
@Override
public void run() {
//System.out.println("子线程的优先级是:"+getPriority());
//设置子线程优先级为MAX_PRIORITY 10
setPriority(Thread.MAX_PRIORITY);
for (int i = 0; i < 30; i++) {
System.out.println("run方法中i="+i);
}
}
public static void main(String[] args) {
ThreadPriorityTest tpt = new ThreadPriorityTest();
tpt.start();
for (int i = 0; i < 30; i++) {
System.out.println("--main方法中i="+i);
}
//Thread td = Thread.currentThread();
//System.out.println("主线程的优先级是:"+td.getPriority());
}
}
(3)线程等待
package com.lagou.task17;
public class ThreadJoinTest extends Thread {
@Override
public void run() {
//模拟倒数10个数的效果
System.out.println("倒计时开始...");
for (int i = 10; i > 0; i--) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("新年快乐");
}
public static void main(String[] args) {
ThreadJoinTest tjt = new ThreadJoinTest();
tjt.start();
System.out.println("主线程开始等待......");
try {
//表示当前正在执行的线程对象等待调用线程的对象, 也就是主线程等待子线程
//tjt.join();//主线程一直等待tjt线程执行完成才加入,等待谁就让谁调用join()方法
tjt.join(5000); //主线程等待子线程5秒钟,不管子线程执行完成与否,主线程都加入
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("终于等到你,还好没放弃!");
}
}
打印结果
主线程开始等待......
倒计时开始...
10
9
8
7
6
终于等到你,还好没放弃!
5
4
3
2
1
新年快乐
(4)线程守护
当子线程不是守护线程时,虽然主线程先结束了,但是子线程依然会继续执行,直到打印完毕所有数据为止。
当子线程是守护线程时,当主线程结束后,则子线程随之结束,但是不会马上停止,因为刹车也是需要时间的。
package com.lagou.task17;
public class ThreadDaemonTest extends Thread {
@Override
public void run() {
//System.out.println(isDaemon()? "该线程是守护线程": "该线程不是守护线程"); // 默认不是守护线程
// 当子线程不是守护线程时,虽然主线程先结束了,但是子线程依然会继续执行,直到打印完毕所有数据为止
// 当子线程是守护线程时,当主线程结束后,则子线程随之结束
for (int i = 0; i < 50; i++) {
System.out.println("子线程中:i = " + i);
}
}
public static void main(String[] args) {
ThreadDaemonTest tdt = new ThreadDaemonTest();
// 必须在线程启动之前设置子线程为守护线程
tdt.setDaemon(true);
tdt.start();
for (int i = 0; i < 20; i++) {
System.out.println("-------主线程中:i = " + i);
}
}
}
(5)案例题目
18.6 线程同步机制(重点)
18.6.1 基本概念
18.6.2 解决方案
18.6.3 实现方式
(1)实现方式1:同步代码块锁定部分代码
package com.lagou.task17;
public class AccountRunnableTest implements Runnable {
//私有化成员变量 账户余额
private int balance;
//为synchronized关键字声明一个引用类型变量
private Demo dm = new Demo();
public AccountRunnableTest(int balance) {
this.balance = balance;
}
public AccountRunnableTest() {
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public void run() {
System.out.println("线程名称"+Thread.currentThread().getName()+"正在执行");
synchronized (dm) {
//synchronized (new Demo()) { 锁不住,相当于每开一个线程都会创建新的对象加一把新锁,必须是同一个对象才可以
//模拟银行取款的过程
//获取账户余额
int tempBalance = getBalance();
//余额大于200进行取款
if (tempBalance >= 200) {
System.out.println("线程"+Thread.currentThread().getName()+"正在出钞......");
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tempBalance -= 200;
System.out.println("出钞结束");
}else{
System.out.println("余额不足,请核对账户余额");
}
//取款后重新设置账户余额
setBalance(tempBalance);
}
}
public static void main(String[] args) {
AccountRunnableTest art = new AccountRunnableTest(1000);
Thread td1 = new Thread(art);
Thread td2 = new Thread(art);
td1.start();
td2.start();
System.out.println("主线程正在等待..........");
try {
td1.join();
//td2.start();//等待线程1结束,在启动线程2,但是这样和创建两个方法分别执行线程1和线程2的代码没什么区别
td2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程等待结束,取款完毕!!!");
System.out.println("最终账户余额为:"+art.getBalance());
}
}
class Demo{}
代码2:继承Thread类 主要是因为用static修饰dm变量,将dm变量提升为类层级,所有对象共用一个dm变量。
package com.lagou.task17;
public class AccountThreadTest extends Thread{
//私有化成员变量 账户余额
private int balance;
//为synchronized关键字声明一个引用类型变量
private static Demo dm = new Demo(); //用static修饰后,dm变量就提升为类层级, 所有对象都公用一个
public AccountThreadTest(int balance) {
this.balance = balance;
}
public AccountThreadTest() {
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public void run() {
System.out.println("线程名称"+Thread.currentThread().getName()+"正在执行");
synchronized (dm) {//如果dm不用static修饰,att1和att2会对应两个dm对象,要想锁住相同代码块,两次调用的dm必须是同一个对象,dm用static修饰后提升到类层级,所有该类对象共用一个dm
//synchronized (new Demo()) { 锁不住,相当于每开一个线程都会创建新的对象加一把新锁,必须是同一个对象才可以
//模拟银行取款的过程
//获取账户余额
int tempBalance = getBalance();
//余额大于200进行取款
if (tempBalance >= 200) {
System.out.println("线程"+Thread.currentThread().getName()+"正在出钞......");
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tempBalance -= 200;
System.out.println("出钞结束");
}else{
System.out.println("余额不足,请核对账户余额");
}
//取款后重新设置账户余额
setBalance(tempBalance);
}
}
public static void main(String[] args) {
AccountThreadTest att1 = new AccountThreadTest(1000);
AccountThreadTest att2 = new AccountThreadTest(1000);
att1.start();
att2.start();
System.out.println("主线程正在等待..........");
try {
att1.join();
//td2.start();//等待线程1结束,在启动线程2,但是这样和创建两个方法分别执行线程1和线程2的代码没什么区别
att2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程等待结束,取款完毕!!!");
System.out.println("最终账户att1余额为:"+att1.getBalance());
System.out.println("最终账户att2余额为:"+att2.getBalance());
}
}
(2)实现方式二:同步方法锁定方法内所有代码
package com.lagou.task17;
public class AccountRunnableTest implements Runnable {
//私有化成员变量 账户余额
private int balance;
//为synchronized关键字声明一个引用类型变量
private Demo dm = new Demo();
public AccountRunnableTest(int balance) {
this.balance = balance;
}
public AccountRunnableTest() {
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public /*synchronized*/ void run() {
synchronized (this) {//由源码可知:启动线程最终是art调用run(),因此当前正在调用的对象就是art,即this=art;因为this表示的是同一个对象,所以可以锁住代码
System.out.println("线程名称"+Thread.currentThread().getName()+"正在执行");
//synchronized (dm) {
//synchronized (new Demo()) { 锁不住,相当于每开一个线程都会创建新的对象加一把新锁,必须是同一个对象才可以
//模拟银行取款的过程
//获取账户余额
int tempBalance = getBalance();
//余额大于200进行取款
if (tempBalance >= 200) {
System.out.println("线程"+Thread.currentThread().getName()+"正在出钞......");
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tempBalance -= 200;
System.out.println("出钞结束");
}else{
System.out.println("余额不足,请核对账户余额");
}
//取款后重新设置账户余额
setBalance(tempBalance);
}
}
public static void main(String[] args) {
AccountRunnableTest art = new AccountRunnableTest(1000);
Thread td1 = new Thread(art);
Thread td2 = new Thread(art);
td1.start();
td2.start();
System.out.println("主线程正在等待..........");
try {
td1.join();
//td2.start();//等待线程1结束,在启动线程2,但是这样和创建两个方法分别执行线程1和线程2的代码没什么区别
td2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程等待结束,取款完毕!!!");
System.out.println("最终账户余额为:"+art.getBalance());
}
}
class Demo{}
代码1:继承Thread类
att1和att2是两个对象 synchronized(this){} this分别代表att1和att2 所以锁不住
方案:在run()前加synchronized修饰是锁不住的, 需要在加一个static修饰,但是run()是继承父类Thread来的,父类中run()方法没有static,
可以新创建一个test()并用synchronized 和 static修饰,然后在run方法中调用test方法,等价于synchornized(类名.class)
原因在于:静态方法锁的是类对象(类层级)而非静态方法锁的是当前方法所属对象(对象层级)。
package com.lagou.task17;
public class AccountThreadTest extends Thread{
//私有化成员变量 账户余额
private int balance;
//为synchronized关键字声明一个引用类型变量
private static Demo dm = new Demo(); //用static修饰后,dm变量就提升为类层级, 所有对象都公用一个
public AccountThreadTest(int balance) {
this.balance = balance;
}
public AccountThreadTest() {
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
//dm用static修饰,理论上用static修饰run()也可以,但是run()是父类继承来的,父类中run()没有用static修饰,所以run()不能用static修饰;
//方案:我们可以把run()中的代码写到另一个用static修饰的方法test()中,在run()内部调用test();
@Override
public /*synchronized*/ void run() {
/*System.out.println("线程名称"+Thread.currentThread().getName()+"正在执行");
synchronized (dm) {
//synchronized (new Demo()) { 锁不住,相当于每开一个线程都会创建新的对象加一把新锁,必须是同一个对象才可以
//模拟银行取款的过程
//获取账户余额
int tempBalance = getBalance();
//余额大于200进行取款
if (tempBalance >= 200) {
System.out.println("线程"+Thread.currentThread().getName()+"正在出钞......");
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tempBalance -= 200;
System.out.println("出钞结束");
}else{
System.out.println("余额不足,请核对账户余额");
}
//取款后重新设置账户余额
setBalance(tempBalance);
}*/
test();
}
public static /*synchronized*/ void test(){
synchronized (AccountThreadTest.class) { //该类型对象的class对象:因为类型是固定的,所以class对象也是唯一的,因此可以实现同步
System.out.println("线程名称"+Thread.currentThread().getName()+"正在执行");
//synchronized (dm) {
//synchronized (new Demo()) { 锁不住,相当于每开一个线程都会创建新的对象加一把新锁,必须是同一个对象才可以
//模拟银行取款的过程
//获取账户余额
int tempBalance = 1000; //getBalance(); getBalance()没有static修饰
//余额大于200进行取款
if (tempBalance >= 200) {
System.out.println("线程"+Thread.currentThread().getName()+"正在出钞......");
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tempBalance -= 200;
System.out.println("出钞结束");
}else{
System.out.println("余额不足,请核对账户余额");
}
//取款后重新设置账户余额
//setBalance(tempBalance); 因为test方法用static修饰,但是setBalance没有static修饰,所以会报错,先注释掉
}
}
public static void main(String[] args) {
AccountThreadTest att1 = new AccountThreadTest(1000);
AccountThreadTest att2 = new AccountThreadTest(1000);
att1.start();
att2.start();
System.out.println("主线程正在等待..........");
try {
att1.join();
//td2.start();//等待线程1结束,在启动线程2,但是这样和创建两个方法分别执行线程1和线程2的代码没什么区别
att2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程等待结束,取款完毕!!!");
System.out.println("最终账户att1余额为:"+att1.getBalance());
System.out.println("最终账户att2余额为:"+att2.getBalance());
}
}
18.6.4 静态方法的锁定
18.6.5 注意事项
18.6.6 线程安全类和不安全类
18.6.7 死锁的概念
18.6.8 使用Lock(锁)实现线程同步
(1)基本概念
(2)常用的方法

(3)与synchronized方式的比较
18.6.9 Object类常用的方法 实现线程间的通信

package com.lagou.task17;
public class ThreadCommunicateTest implements Runnable {
private int cnt = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();//唤醒之前阻塞的线程, 每次进到循环在判断条件之前就唤醒阻塞的线程
//打印1-100之间的整数
if (cnt <= 100) {
System.out.println("线程" + Thread.currentThread().getName() + "中cnt=" + cnt);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
cnt++;
//当前线程打印完一个整数后,为了防止打印下一个整数,调用wait()阻塞
try {
wait(); //线程进入阻塞状态后,自送释放对象锁,wait和notify必须在锁定的代码中调用
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
public static void main(String[] args) {
ThreadCommunicateTest tct = new ThreadCommunicateTest();
Thread td1 = new Thread(tct);
Thread td2 = new Thread(tct);
td1.start();
td2.start();
}
}
案例二:生产者和消费者共用一个仓库,产品数量<10就生产, 产品数量>0就消费
package com.lagou.task17;
/**
* 仓库类: 用于生产产品 和 消费产品
* 注意: 生产者线程 和 消费者线程 共用一个仓库
* 仓库产品总数量不能超过10个
*/
public class StoreHouse {
private int cnt = 0; // 用于记录产品的数量
//生产产品方法
public synchronized void produceProduct() {
notify();
if (cnt < 10) {
System.out.println("线程" + Thread.currentThread().getName() + "正在生产第" + (cnt+1) + "个产品...");
cnt++;
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品方法
public synchronized void consumerProduct() {
notify();
if (cnt > 0) {
System.out.println("线程" + Thread.currentThread().getName() + "消费第" + cnt + "个产品");
cnt--;
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
------------------------
package com.lagou.task17;
/**
* 生产者线程 sleep 100
*/
public class ProduceThread extends Thread {
//声明一个仓库类对象,为了可以调用仓库类的生产产品的方法
private StoreHouse sh;
//创建生产者线程对象时给仓库类引用赋值
public ProduceThread(StoreHouse sh){
this.sh = sh;
}
@Override
public void run() {
while(true){
//调用仓库类生产产品的方法
sh.produceProduct();
//睡一会继续100
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
--------------------
package com.lagou.task17;
/**
* 消费者线程 sleep 2000
*/
public class ConsumerThread extends Thread {
//为了调用仓库类中消费产品的方法
private StoreHouse sh;
//创建生产者线程对象时给仓库类引用赋值
public ConsumerThread(StoreHouse sh){
this.sh = sh;
}
@Override
public void run() {
while(true){
//调用仓库类中消费产品的方法
sh.consumerProduct();
//睡一会继续 2000
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
--------------------
package com.lagou.task17;
public class StoreHouseTest {
public static void main(String[] args) {
//创建仓库类
StoreHouse sh = new StoreHouse();
//创建生产者线程
ProduceThread ptd = new ProduceThread(sh);
//创建消费者线程
ConsumerThread ctd = new ConsumerThread(sh);
ptd.start();
ctd.start();
}
}
18.6.10 线程池(熟悉)
(1)实现Callable接口
方法声明 功能介绍V call() 计算结果并返回
(2)FutureTask类
方法声明 功能介绍FutureTask(Callable callable) 根据参数指定的引用来创建一个未来任务V get() 获取call方法计算的结果
package com.lagou.task17;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadCallableTest implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 10000; i++) {
sum = sum+i;
}
System.out.println("计算1-10000累加的和是:"+sum);
return sum;
}
public static void main(String[] args) {
ThreadCallableTest tct = new ThreadCallableTest();
FutureTask ft = new FutureTask(tct);
Thread td = new Thread(ft);
td.start(); //调用call()方法
/*//利用FutureTask的get()方法获取call()的返回值
Object obj = null;
try {
obj = ft.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("线程中call方法的返回值是:"+obj);*/
}
}
(3)线程池的由来
(4)概念和原理
(5)相关类和方法


(6)测试代码
package com.lagou.task18;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolTest {
public static void main(String[] args) {
// 1.创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 2.向线程池中布置任务
executorService.submit(new ThreadCallableTest());
// 3.关闭线程池
executorService.shutdown();
}
}
该博客围绕线程展开,介绍了程序、进程和线程的基本概念,重点讲解线程的创建方式、生命周期、编号和名称等。还阐述了线程的常用方法,如阻塞、优先级管理等。着重探讨线程同步机制,包括实现方式、静态方法锁定、死锁概念等,最后提及线程池相关内容。
889

被折叠的 条评论
为什么被折叠?



