第十七章、多线程基础
1、程序(programm)
1.1、概念:
完成特定的任务、用某种语言编写的一组指令的集合。一段静态代码。
2、进程
1、进程是指运行中的程序,比如qq使用,就是一个进程,迅雷又是一个进程。就会为其分配内存空间
2、进程是查询的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生,存在和消亡的过程。
3、线程相关概念
3.1、线程由进程创建的,是进程的一个实体
3.2、单线程
1、同一时刻,只允许执行一个线程
3.3、多线程
1、同一时刻,可以指向多个线程。(一心N用)
3.4、并发
1、同一时刻,多个任务交通执行。
3.5、并行
1、同一时刻,多个任务同时执行。
4、线程的基本使用(两种方式)
4.1、 继承Thread类
- 创建一个继承于Thread类的子类
- 重写Thread类的run() --> 将此线程执行的操作声明在run()中
- 创建Thread类的子类的对象
- 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
4.1.2、理解低层
1、调用start ()方法,start()方法中有start0()方法。start0()是本地方法,是JVM机调用,低层实现是c/c++实现。。
2、所以是start0才是真正实现多线程的效果
4.1.3、eg
public static void main(String[] args) {
Cat cat = new Cat();
cat.run();
}
}
class Cat extends Thread {
// 重写thread中的run方法,写自己业务逻辑
// run Thread 类实现了Runable 接口的run方法
@Override
public void run() {
int count = 0;
while (true) {
// 每隔一秒,在控制台输出“喵喵,我是小猫咪”
System.out.println("喵喵,我是小猫咪"+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count==10){
break;
}
}
}
}
4.2、实现Runnable接口
4.2.1、说明
1、java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了。
2、java设计者提供了另一个方式创建线程,就是通过实现Runnable接口来创建线程。
4.2.2、eg
public static void main(String[] args) {
T1 t1 = new T1();
Thread thread1 = new Thread(t1);
thread1.start();
}
}
class T1 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
//每隔1秒输出“hello,world”,输出10次
System.out.println("hello,world" + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
5、继承Thread和实现Runnab的区别
1、从java设计上看,继承继承Thread和实现Runnab接口创建线程本质上没有什么区别,从jdk文档看,Thread本身就实现了Runnable接口
2、实现Runnable接口更加适合多个线程共享一个资源的情况,并且避免了单继承的限制。
6、线程终止
1、线程执行完毕
2、通过使用变量来控制run方法退出的方式停止线程,即通知方式。
(1)利用一个变量,设置boolean来控制,循环。
(2)eg本例子使用set()方法修改。
class T extends Thread{
int c =0;
private boolean loop=true;
@Override
public void run() {
while (loop){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数数开始:"+c);
c++;
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
7、线程常用方法
7.1、常用方法1
1、方法
(1)setName //设置线程名称,使其与参数name相同;
(2)getName //返回该线程的名称;
(3)start //使该线程开始执行;Java虚拟机底层调用该线程的start0方法;
(4)run //调用线程对象run方法;
(5)setPriority //更改线程的优先级;
(6)getPriority //获取线程的优先级;
(7)sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行);
(8)interrupt //中断线程,注意不是终止,如果线程正在休眠,则停止休眠,继续执行。
2、例子:
T t = new T();
// 设置线程名称方法
t.setName("阿涛");
t.setPriority(Thread.MIN_PRIORITY);
t.start();//启动子线程
System.out.println(t.getName());
System.out.println("");
// 主线程打印5 hi ,然后中断子线程的睡眠
for(int i =1;i<=5;i++){
Thread.sleep(1000);
System.out.println("hi"+i);
}
t.interrupt();//提前结束休息时间,直接中断线程。开始下一个线程
}
}
class T extends Thread{//自定义线程类
@Override
public void run() {
while(true) {
for (int i = 0; i < 100; i++) {
// getName()获取当前线程名称
System.out.println(Thread.currentThread().getName() + "吃包子~~" + i);
}
try {
System.out.println(Thread.currentThread().getName() + "吃包子累了休息一下");
Thread.sleep(20000);//休眠时间
} catch (InterruptedException e) {
// 当该线程执行到一个interrupt方法时,就会catch 一个异常,可以加入自己的业务代码
// interrupt:被中断
System.out.println(Thread.currentThread().getName() + "被interruptaaaaaaaaaaaaaaaa");
}
}
}
}
7.2、常用方法2—线程插队、礼让
1、yield:如果CPU忙得过来,则体现不吃礼让
2、join:t1 让大哥t2插队,就在t1里调用join方法
3、例子
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();
for(int i=1;i<=20;i++){
Thread.sleep(1000);
System.out.println("主线程吃了(小弟)"+i+"个包子");
if(i==5){
System.out.println("主线程(小弟)让子线程(老大)先吃:");
t2.join();
System.out.println("主线程(小弟吃)");
}
}
}
}
class T2 extends Thread{
@Override
public void run() {
for(int i =1;i<=20;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println("JoinThread————————"+i);
System.out.println("子线程吃了"+i+"个包子");
}
}
}
7.3、注意事项细节
1、start低层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新线程
2、线程优先级的范围
(1)Max_PRIORITY:10
(2)Min_PRIORITY:1
(3)NORM_PRIORITY:5
3、interrupt,中断线程,但并没有真正的结束线程。所以一般用于中断正在休眠线程
4、slee:线程的静态方法,使当前线程休眠
7.4、线程常用方法3—用户线程和守护线程
1、用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2、守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
3、最常见的守护线程:垃圾回收机制。
4、注意:守护线程设置要在线程启动之前
5、eg
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDemonThread myDemonThread = new MyDemonThread();
// 主线程结束后,子线程自动退出。将子线程设置为守护线程即可
myDemonThread.setDaemon(true);
myDemonThread.start();
for(int i=1;i<=10;i++){
System.out.println("宝强在辛苦的工作!!!");
Thread.sleep(1000);
}
}
}
class MyDemonThread extends Thread{
@Override
public void run() {
for(;;){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("马蓉和宋喆快乐的聊天哈哈哈~~~~");
}
}
}
8、线程的生命周期
1、

2、eg
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName()+"状态"+t.getState());
t.start();
while(Thread.State.TERMINATED !=t.getState()){
System.out.println(t.getName()+"状态"+t.getState());
Thread.sleep(500);
}
}
}
class T extends Thread{
@Override
public void run() {
while(true) {
for (int i = 0; i < 10; i++) {
System.out.println("hi" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
//结果
Thread-0状态NEW
Thread-0状态RUNNABLE
hi0
Thread-0状态TIMED_WAITING
hi1
Thread-0状态TIMED_WAITING
Thread-0状态TIMED_WAITING
hi2
Thread-0状态TIMED_WAITING
Thread-0状态TIMED_WAITING
hi3
Thread-0状态TIMED_WAITING
Thread-0状态TIMED_WAITING
hi4
Thread-0状态TIMED_WAITING
Thread-0状态TIMED_WAITING
hi5
Thread-0状态TIMED_WAITING
Thread-0状态TIMED_WAITING
hi6
Thread-0状态TIMED_WAITING
Thread-0状态TIMED_WAITING
hi7
Thread-0状态TIMED_WAITING
Thread-0状态TIMED_WAITING
hi8
Thread-0状态TIMED_WAITING
Thread-0状态TIMED_WAITING
hi9
Thread-0状态TIMED_WAITING
Thread-0状态TIMED_WAITING
9、Synchronized关键字
9.1、线程同步机制
1、同步:在多线程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,保证数据的完整性。
9.2、同步具体方法-Synchronized
1、同步代码块
synchronized(对象){//得到对象的锁,才能操作同步代码
//需要被同步代码;
}
2、synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m(String name){
//需要被同步的代码
}
3、理解
(1)小伙伴上厕所,关厕所门,上完厕所后开门,其他小伙伴就可以上厕所了
4、例子
class SellTicket03 implements Runnable{
private int tickNum =100;
private boolean loop =true;//定义一个变量,控制run方法
//同步方法,在同一时刻,只能一个线程来执行同步方法
public synchronized void m(){
if(tickNum<=0){
System.out.println("售票结束");
loop = false;
return;
}
// 休眠一会50毫秒
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
--tickNum;
System.out.println("窗口:"+Thread.currentThread().getName()+"售出一张票"+"剩余票数:"+tickNum);
}
@Override
public void run() {
while(loop){
m();//卖票方法,同步
}
}
}
10、互斥锁
10.1、基本介绍
1、java引入对象互斥锁的概念,来保证共享数据操作的完整性
2、每个对象都对应一个可称为”互斥锁“的标记,这个标记用来保证在任一时刻,只能有一个线程访问对象(理解:我拿到别人就没有)
3、关键字sychronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
4、同步的局限性:导致出现中的执行效率要降低
5、同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一对象),锁只在该对象
6、同步方法(静态的)的锁为当前类本身,所在该类中
10.2、eg
public class SynchronizedDemo {
// 修饰实例方法
synchronized void updateData() {
// 业务代码
}
// 修饰静态方法
synchronized static void retrieveData() {
// 业务代码
}
// 修饰代码块
Object obj = new Object();
void createData() {
synchronized(obj) {
// 业务代码
}
}
}
public class DemoSellTicket {
public static void main(String[] args) {
SellTicket04 sellTicket04 = new SellTicket04();
SellTicket04 sellTicket041 =new SellTicket04();
SellTicket04 sellTicket042 = new SellTicket04();
sellTicket04.start();
sellTicket041.start();
sellTicket042.start();
}
}
class SellTicket04 extends Thread {
private int tickNum =100;
private boolean loop =true;
public synchronized void sell1(){
if(tickNum<=0){
System.out.println("售票结束");
loop =false;
return;
}
// 休眠一会50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
--tickNum;
System.out.println("窗口:"+Thread.currentThread().getName()+"售出一张票"+"剩余票数:"+tickNum);
}
@Override
public void run() {
while(loop){
sell1();
}
}
}
10.3、注意事项和细节
1、同步方法如果没有使用static修饰:默认锁对象为this
2、如果方法使用static修饰,默认锁对象:当前类.class
3、实现的落地步骤
(1)分析需要上锁的代码
(2)选择同步代码块或同步方法
(3)要求多个线程的锁对象为同一个
11、线程的死锁
11.1、基本介绍
1、多个线程都占用了对方的锁资源,但不肯想让,导致了死锁,在编程是一定要避免死锁的发生。
11.2、eg
public class DeadLock {
public static void main(String[] args) {
// 模拟死锁现象
DeadLockDemo deadLockDemo =new DeadLockDemo(true);
deadLockDemo.setName("A线程");
DeadLockDemo deadLockDemo1 = new DeadLockDemo(false);
deadLockDemo1.setName("B线程");
deadLockDemo1.start();
deadLockDemo.start();
}
}
class DeadLockDemo extends Thread{
static Object o1= new Object();//保证多线程共享一个对象,使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag){
this.flag= flag;
}
@Override
public void run() {
if(flag){
synchronized (o1){//对象互斥锁,下面就是同步代码
System.out.println(Thread.currentThread().getName()+"进入1");
synchronized (o2){//让li获得监控
System.out.println(Thread.currentThread().getName()+"进入2");
}
}
}else{
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"进入3");
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"进入o4");
}
}
}
}
}
12、释放锁
12.1、下面操作释放锁
1、当前线程的同步方法、同步代码块执行完毕
2、当前线程在同步代码块、同步方法中遇到break、return
3、当前线程在同步代码块、同步方法中出现了为出现的Error或Exception,导致异常结束
4、当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
12.2、下面操作不会释放锁
1、线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
2、线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
3、尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用
13、课后练习
1、第一题
(1)在main方法中启动两个线程
(2)第1个线程循环随机打印100以内的证书
(3)直到第2个线程从键盘读取了“Q"命令。
2、第二题
(1)有2个用户分别从同一张卡上取钱(总额10000)
(2)每次都取1000,当余额不足时,就不能取款
(3)不能超取现象—》线程同步问题