Java-多线程与锁机制学习笔记

基本概念

进程与线程

进程是一个应用程序,线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。

两者区别:

进程的资源内存独立,不会共享。
线程的堆内存和方法区内存共享,但栈内存独立。

注意:假设启动10个线程,则会有10个栈空间且互不干扰。多线程并发互不影响,提高程序的处理效率。
使用了多线程后,main方法结束,程序并不一定结束。
如:java 运行 System.out.println("");会先启动JVM,JVM再启动 一个主线程调main方法,一个垃圾回收线程。
区别

实现java多线程

通过继承Thread类实现

编写一个类,直接继承java.lang.Thread,重写run方法。


import javax.rmi.ssl.SslRMIClientSocketFactory;
//线程类实现多线程
public class ThreadTest {
    public static void main(String[] args) {
        Thread mythread=new MyThread();
//        mythread.run();         //如果是这样调用,不会开多线程。

        mythread.start();       //start方法作用:在JVM中开辟一个新的栈空间,开完start()方法结束。 start方法瞬间就会结束。然后 启动成功的线程会自动调用run()方法。

        for(int i=0;i<1000;i++){
            System.out.println("主线程--->"+i);
        }




    }
}
class MyThread extends Thread{
    public void run(){              //这个方法即是其他线程的会运行的任务代码。
        for(int i=0;i<1000;i++){
            System.out.println("分支线程--->"+i);
        }
    }
}

通过Runnable接口实现

编写一个类,实现java.lang.Runnable接口,实现run方法。

//通过实现runnale接口方式实现多线程      ,这种比较常用,因为runnable 还可以继承其它类。
public class Mythread2 {
    public static void main(String[] args) {
        MyRunnable r=new MyRunnable();
        Thread t=new Thread(r);
        t.start();
        for(int i=0;i<1000;i++){
            System.out.println("main线程---》"+i);
        }

    }
}

//这是个可运行的类,非线程类
class MyRunnable implements Runnable{
    public void run(){
        for(int i=0;i<1000;i++){
            System.out.println("分支线程---》"+i);
        }

    }
}

线程的调度

线程运行时的几种状态

线程刚创建出来处于 新建状态,调用start方法后进入 就绪状态,run()方法开始执行标志着线程进入运行状态。当之前占有的cpu时间片占用完后,会重新回到就绪状态抢夺cpu时间片,当再次抢夺到执行权时,继续执行。run完全执行后,此线程进入 死亡状态
当运行状态遇到阻塞事件(例如sleep,接收用户输入),将发生阻塞进行阻塞状态,将把时间片释放掉。
若阻塞解除,会回到就绪状态抢夺cpu时间片。
就绪状态:又叫可运行状态,表示当前线程具有抢夺cpu时间片的权利(也即执行权)。
进入阻塞状态的方法:java中可通过Thread.sleep()方法,让当前线程休眠。当前线程将进入阻塞状态,放弃cpu时间片,让给其他线程使用。
在这里插入图片描述

线程的调度

1.抢占式线程调度模型:哪个线程的优先级比较高,抢到cpu时间片的概率就高一点。java采用的就是这种。
2.均分式线程调度模型:平均分配cpu时间片。每个线程占用的cpu时间片的时间长度相同。

java中线程调度方法:void setPriority(int p)设置线程的优先级
int getPriority()获取线程优先级
最低优先级1
默认优先级5
最高优先级10
t.join() ; 抢位方法,当前线程进入阻塞,t 线程执行。直到t执行完当前线程才可以释放。
静态:yield() 让位方法,当前进程从运行状态回到就绪状态。

线程安全

发生条件

在以下三个条件下可能发生线程安全问题:多线程并发、有共享数据、共享数据有修改的行为。
解决方案:线程排队执行; (线程同步机制)
(线程同步就是线程排队了,牺牲了一部分效率。同步就是需排队,异步就是各自执行各自的,谁也不用等谁。)

线程对象的对象锁

当线程运行遇到sychronized关键字时,其放弃占有的CPU时间片,进入对象锁池找共享对象的对象锁。若找不到进入等待。若找到了便进入就绪状态继续抢夺时间片。
若在实例方法上加sychronized(即类中的方法前加sy),则一定锁的是this,表示整个方法都要同步,可能会无故扩大同步的范围,导致程序执行的效率降低。
sychronized(“abc”),因“abc”在字符串常量池中,所有线程共享,就所有线程全部同步。
Java中三大变量:
实例变量    在
静态变量    在方法区
局部变量    在

用synchronized保证线程安全:

  1. 同步代码块代码中: sychronized(线程共享对象){ 同步代码块 }
public void withdraw(double money){
            synchronized (obj){                //只在执行下面代码的时候锁
            double before=this.getNum();
            double now=before-money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setNum(now);
        }
    }
  1. 实例方法上使用:表示共享对象一定是this
public synchronized void withdraw(double money){		//在执行这个方法的时候锁 直接锁了this的全部变量
            {                
            double before=this.getNum();
            double now=before-money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setNum(now);
        }
    }
  1. 在静态方法上使用:表示 找类锁 。类锁只有一把,不论对象多少。(目的是保证静态变量的安全)(排他锁)
public synchronized static void withdraw(double money){			//锁住后,别人操作不了这个静态方法
}

死锁

sychronized嵌套使用,容易发生死锁现象。


public class Deadlock {
    public static void main(String[] args) {
        Object o1=new Object();
        Object o2=new Object();
        Thread t1=new Mythread1(o1,o2);
        Thread t2=new Mythread2(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 {
                Thread.sleep(1000);		//sleep保证了死锁,如果不加,也有可能死锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){
        }
    }
}}
class Mythread2 extends Thread{
    Object o1;
    Object o2;

    public Mythread2(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){
        }
    }
    }
}

线程安全问题解决方案

线程安全问题如何解决: 直接选择线程同步会严重影响用户体验。
方案一: 尽量使用局部变量代替实例变量和成员变量。
方案二: 如果必须是实例变量,则可以考虑创建多个对象,这样实例变量的内存就不共享了。
方案三:若不可用局部变量,且不可创建多个对象,这个时候就只能选择sychronized了。线程同步机制。

守护线程

守护线程: 指后台运行的线程
代表性守护线程:垃圾回收
(定时数据自动备份)
守护线程的特点:一般守护线程是一个死循环,用户线程只要结束,守护线程自动结束。

public class GuardThread {
    public static void main(String[] args) {
        Thread t=new BakDataThread();
        t.setName("备份数据的线程");
        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();
            }
        }
    }
}

Callable方法获取线程的返回值

用Callable接口实现多线程, 这种方式可以获取线程的返回值。( run是void无返回值)

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask; //JUC包下的属于java并发包

public class Callable_Thread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask task=new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {     //相当于run方法,但有返回值。
                System.out.println("begin !");
                Thread.sleep(1000);
                System.out.println("end !");
                return 10;
            }
        });

        Thread t=new Thread(task);
        t.start();
        System.out.println(t);
        Object o=task.get();
        System.out.println(o);
    }
}

线程的等待与唤醒

线程的等待与唤醒使用Object类中的wait方法和notify方法。
Obeject o=new Object();
o.wait(); 表示让正在执行对象的线程 (可能若干个线程在操作这个对象)进入等待,直到notify唤醒为止。此操作会释放o对象上的锁。
o.notify(); 唤醒对象o上等待的线程,只是通知等待的线程可以继续操作了。不会释放o对象上的锁。
o.notifyAll();唤醒o对象上的所有线程。
使用场景: 生产者生产商品到仓库;消费者从仓库中消费商品;
要达到供需关系平衡,需要仓库 来调用wait与notify方法。
当然,仓库还存在线程安全的问题,这两个方法需要建立在线程同步的基础之上。

import Java_bilibili.Collections_and_Map.ArrayListTest;		//生产者--消费者--仓库  模型代码
import java.util.ArrayList;
import java.util.List;

public class PC_Thread {
    public static void main(String[] args) {
        List l=new ArrayList<>();

        Thread t1=new Thread(new Producer(l));
        Thread t2=new Thread(new Consumer(l));
        t1.setName("生产者线程");
        t2.setName("消费者线程");
        t1.start();
        t2.start();
    }
}
class Producer implements Runnable{
    private List list;

    public Producer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        while(true){
            synchronized (list){
                if(list.size()>=10){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                else{
                    list.add(1);
                    System.out.println(Thread.currentThread().getName()+"---->生产+1"+"           剩余--"+list.size());
                    list.notify();
                }
            }


        }

    }
}
class Consumer implements Runnable{
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    public void run() {
        while(true){
            synchronized (list){
                if(list.size()==0){
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
                else {
                    list.remove(0);
                    System.out.println(Thread.currentThread().getName()+"---->消费+1"+"           剩余--"+list.size());
                    list.notify();
                }
            }
        }

    }

}

多线程取钱样例代码

在并发取钱条件下,若不使用锁机制,可能造成取钱的bug


import java.util.Objects;

public class AccountThread extends Thread{
    private Bank_Accout act;


    public AccountThread(Bank_Accout act) {
        this.act=act;
    }

    public void run(){
        double m=5000;
        act.withdraw(5000);

        System.out.println("取款成功,余额"+act.getNum());
    }
}
class Bank_Accout {
    private String actno;
    private double  num;
    Object obj=new Object();

    public Bank_Accout(String actno, double num) {
        this.actno = actno;
        this.num = num;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getNum() {
        return num;
    }

    public void setNum(double num) {
        this.num = num;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Bank_Accout that = (Bank_Accout) o;
        return
                actno.equals(that.actno);
    }

    @Override
    public int hashCode() {
        return Objects.hash(actno);
    }
    public void withdraw(double money){
//        //并发取钱,出现bug
//        double before=this.getNum();
//        double now=before-money;
//        try {
//            Thread.sleep(1000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//        this.setNum(now);

        //排队取钱,方为上策。
//        synchronized ("ss"){                                    //       所有线程全部同步,一人取钱,全球等待
//        synchronized (this){                //this是排队线程共享的对象      多线程对this同一账户操作时等待

        synchronized (obj){                //obj是 排队线程共享的对象    多线程对同一账户操作时等待
            double before=this.getNum();
            double now=before-money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setNum(now);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值