java线程基础(韩顺平)

1.程序、进程、线程、并发、并行的概念

1.程序指的是代码,静态
2.进程是一个程序的执行过程
3.线程是由线程创建的,是进程的一个实体,一个进程可以拥有多个线程
举例:一个迅雷的多个下载任务,一个qq的多个聊天窗口
4.并发:宏观并行,微观串行
并行:多个同时,多核cpu可以

2.线程的基本使用

(1)java中线程的两种创建方式

1.继承Thread类,重写run方法
2.直接绕过Thread类,实现Runnable接口,重写run方法
在这里插入图片描述

(2)线程使用

一个类继承Thread类(或实现Runnable接口),重写run方法,写上自己的业务代码,就成为了线程。
在主函数里,调用这个对象的start方法(相当于调run里面的内容),就可以执行线程,代码如下

public class xiancheng {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
    }
}

class Cat extends Thread{
    @Override
    public void run() {
        super.run();
        System.out.println("哈喽,猫咪");
    }
}

(3)线程的结构

进程执行main方法,会创造一个main线程,然后有多线程,那就在main下创建一个线程,接下来也可以在子线程创建线程,也可以在main继续创建
注意:main里面的执行结束,main线程终结,但如果子线程没有结束,那么不会退出,直到所有线程执行完,进程才退出(main终结,孩子不会终结),结构如图:
在这里插入图片描述

(4)线程的特点

所以线程都是并发(多核考虑并行),子线程和main线程也是并发,线程不会阻塞其他线程,如图,main和cat线程交替执行

public class xiancheng {
    public static void main(String[] args) throws InterruptedException {
        Cat cat = new Cat();
        cat.start();
        while(true) {
            System.out.println("哈喽");
            Thread.sleep(1000);
        }
    }
}

class Cat extends Thread {
    @Override
    public void run(){
        super.run();
        while(true) {
            System.out.println("哈喽,猫咪");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

(5)线程的终止

如何在main中停止一个线程
1.在线程里加一个控制变量,并加上set方法:

    private boolean loop = true;
    public void setLoop(boolean loop) {
       this.loop = loop;
   }

然后在循环里加一个判断loop是否为真
在main方法里合适的时机把他设置为假就可以了

(6)线程常用方法

在这里插入图片描述
还有yield:线程的礼让
join:线程的插队

a.中断线程不是终止线程

一般中断的是正在休眠的线程,相当于唤醒

b.线程优先级的范围

三种常用优先级:

	public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;
c.方法的代码举例

1.线程名字:

//在线程内部
//currentThread的意思是当前线程
Thread.currentThread().setName("线程1");
Thread.currentThread().getName();				//用于接口调用的方式,在内部
//也可直接this.setName();			//用于继承调用的方式,在内部
//在线程外部,直接用对象名字
t1.setName("线程1");
t2.getName();		

2.中断:

//效果就是把正在沉睡的线程唤醒
        t1.interrupt();

3.yield(礼让)
让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
根据操作系统判断,如果资源紧张礼让成功的概率大

4.join(插队)
插队的线程一旦插队成功,则肯定先执行完插入的线程的所有任务
使用就是,我调用t2.join,意思是让t2执行来(只有他自己串行),直到执行完在执行别的

5.用户线程和守护线程
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
守护线程:一般是为了工作线程服务的,当所有的用户线程结束,守护线程自动结束
常见的守护线程:垃圾回收机制

如果我们想主线程结束,子线程自动结束,只需要把子线程设置成守护线程即可。

t1.setDaemon(true);

(7)线程的生命周期(六状态)

1.创建态,
2.经过start后,进入就绪态和运行态
3.阻塞态(这里分为三个)分别为timewaiting(等待sleep苏醒,也叫超时等待)、wait(join等阻塞)、block(等待进入同步代码块的锁,即资源)
4.结束态

//查看状态
t1.getState();

(8)线程同步机制Synchronized(加锁)

1.概念:一些敏感数据不允许多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,保证数据的完整性 ,其实就是互斥。
2.具体使用:
a.同步代码块

private static final Object obj = new Object(); //唯一锁,static代表对类加锁,不加代表对对象加锁
synchronized(A.class) { //同步代码块
					XXXX
           }

b.synchronized还可以放在方法声明中,表示整个方法为同步方法

public synchronized static void sell(){
        System.out.println("___"+ --X); //减减后输出
    }

(9)线程通信

1.A线程想控制B线程,可以把B的对象传入A,调B对象里的参数进行控制

(9)问题:

a.为什么不直接调run,而是调start方法

1.因为直接调run不是启动线程,而是正常的调用方法,也就是串行
2.调用start的代码:

public synchronized void start() {
        start0();
    }

3.start0是本地方法,用jvm调用,底层是c/c++实现的
4.真正实现多线程的效果,是start0,不是run
实现原理:
在这里插入图片描述
比如对于什么时候上处理机,什么时候分配io资源,由操作系统决定
(先来先服务,时间片等等)

b.为什么有继承和接口两种线程方式

1.继承的优势:直接通过继承的方式,可以直接通过start方式创建线程,但是接口没有start这个方法,所以用起来没继承方便

2.接口的优势:第一:java是单继承,在某些情况下一个类可能已经继承了某个父类,这时再用继承的方式创建线程显然不太可能,这时候就可以通过接口,第二,接口方式更适合多个线程共享一个资源的情况,也就是说同一个对象分为两个线程执行,。

3.接口使用线程的方式:new一个thread对象,构造器参数是那个 “实现了接口的,想要成为线程的类的对象”,也就是说先把线程的类实例化,再传进Thread构造器,再通过thread.start;来创建和执行线程

		Cat cat = new Cat();
        Thread thread = new Thread(cat);
        thread.start();

4.·接口使用线程的原理代理设计
1.代理设计,即用一种设计方式来让一个类代理自己做某件事
2.执行过程为:实例化Thread时传入的对象,类型是Runnable,然后调用Thread这个对象的start方法,start方法会调用start0方法来创建线程,start0会执行传入的对象重写的run方法执行具体内容。
3.接下来用一段代码来模拟Thread代理设计的执行过程

class ThreadProxy implements Runnable {

    private Runnable target = null;

    @Override
    public void run() {
        if(target != null){	//第四步
            target.run();    //即没被重写的run方法,是为这种代理设计模式服务的 
        }
    }

    public ThreadProxy(Runnable target) {
        this.target = target;		//第一步
    }

    public void start(){
        start0();	//第二步
    }

    public void start0(){
        run();	//第三步
    }

}

上述解释未免太过啰嗦,以下一言而敝之:
把类传进去,在里面做原来有应该在外面做的事

c.同步互斥问题导致的卖票问题

问题1.会重复卖一张票
问题2.最后卖完了还会超卖

在类里定义一个商品的静态变量,然后多个线程并发卖出,最后会多卖,因为最后一轮判断的时候,没来得及修改然后都进去for循环,导致每个都减一,最后卖多了(秒杀经典问题)

还有一个重要原因:

首先要肯定的是ThreadLocal和局部变量是线程安全的,静态和实例变量都是不安全的。

内存机制中的 "副本"概念
多个线程访问一个成员变量时 每个线程都会得到一个该变量的副本 在自己的线程的栈中保存、计算 以提高速度。 但是这样就会有同步的问题了。 当一个线程修改了自己栈内副本的值 还没有立即将同步到主存中, 其他线程再来获取主存中的该变量时 就会得到过期数据。

解决办法
1.解决重复减:

synchronized(obj) { //同步代码块
                System.out.println(Thread.currentThread().getName() +"___"+ --num);
            }

2.解决卖多的问题:加一层判断

while (num >0){
            synchronized(obj) { //同步代码块
                if(num >0)
                System.out.println(Thread.currentThread().getName() +"___"+ --num);
            }
        }

3.锁

(1)互斥锁

a.基本介绍

1.java中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
2.每个对象都对应一个可称为”互斥锁“的标记,这个标记用来保证在某一时刻,只能有一个线程访问对象
3.关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
4.同步的局限性:导致程序的执行效率降低
5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6.同步方法(静态)的锁为当前类本身
(在静态方法中,实现同步代码块,就需要传Sell.class了,不能传对象)

b. 注意事项

1.同步方法如果密钥使用static修饰,默认锁对象为this对象
2.如果方法使用了staic修饰,默认锁对象为当前类.calss
3.步骤:
分析上锁的代码
选择同步代码块或同步方法
要求多个线程的锁对象为同一个即可

(2)死锁

就和pv操作一样,这里的变量为静态或者非静态的对象

(3)释放锁

释放锁的操作:
1.同步代码块执行结束
2.当前线程在同步代码块、同步方法中遇到break、return。
3.当线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
4.当前线程在同步代码块、同步方法中执行了线程对象的wait方法,当前线程暂停,并释放死锁。(会进Timewit阻塞,也就是暂停的意思)

不会释放锁的操作
1.线程执行同步代码块或者同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,但是不会释放死锁。
(案例:上厕所太困了,在坑位睡了也不会出来)
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。(该方法已过时)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值