I
Published on
· Last modified on
· Public

JAVA多线程入门笔记.md

概念

  • 进程:为正在运行中的程序分配的内存空间。
  • 线程:进程中负责程序执行的执行单元。一个进程中至少有一个线程,有多个线程的就称为多线程程序。
  • 多线程:解决多部分代码(多任务)同时执行的需求,合理使用cpu资源。多线程的运行根据cpu切换完成,如何切换由cpu决定,因此多线程运行具有不确定性。线程的任务封装在特定区域,如主线程任务定义在main方法中,垃圾回收器线程回收垃圾时会运行finalize方法。

JVM的多线程垃圾回收

jvm中至少有两个线程:一个负责自定义代码运行,一个负责垃圾回收。

创建线程

方法一:继承Thread类

步骤:

  • 继承Thread类,覆盖run()方法。
  • 创建线程对象并用start()方法启动线程。

:start()开启线程后,都会执行run方法,run方法中是线程要运行的代码(即:自定义的任务都存储在run方法中)。

class Demo extends Thread {
    private String name;
    Demo(String name) {
        this.name = name;
    }
    //override run method
    public void run() {
        for (int x = 1; x <= 10; x++) {
            System.out.println(name + "......" + x);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        Demo d1 = new Demo("aaaaaaa");
        Demo d2 = new Demo("1111111");
        //start the thread
        d1.start();
        d2.start();
    }
}
  • start和run方法的区别?

调用start会开启线程,使线程执行run方法中的任务。 直接调用run,线程并未开启,执行run方法的只有主线程。

方法二:实现Runnable接口

Runnable接口:解耦,降低了线程对象和线程任务的耦合性,即任务和对象分离

  • 定义一个类实现Runnable接口。
  • 覆盖Runnable接口的run方法,将线程要运行的任务代码放置到该方法中。
  • 通过Thread类创建线程对象,并将实现的Runnable接口对象作为Thread类构造函数的参数进行传递。
  • 调用Thread类的start方法,开启线程。
class SaleTicket implements Runnable {
    private int tickets = 100;
    public void run() {
        while(true) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "......" + tickets-- );
            }
        }
    }
}

class TicketDemo2 {
    public static void main(String[] args) {
        SaleTicket st = new SaleTicket();
        // via Thread class
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        Thread t3 = new Thread(st);
        Thread t4 = new Thread(st);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
  • 继承Thread类和实现Runnable接口对比

Runnable接口的作用:

  • 避免了继承Thread单继承的局限性。
  • Runnable接口更符合面向对象,将线程单独进行封装。
  • Runnable降低了线程对象和线程任务的耦合性。

多线程的运行状态

  • 线程状态图

多线程安全问题

  • 问题原因: 线程任务中有处理到共享数据 线程任务中有多个对共享数据的操作。一个线程在操作数据过程中,其他线程参与了运算,造成数据错误。
  • 解决: 保证多条操作共享数据的代码在某一时段,仅被一条线程操作,其他线程不参与运算。
  • 保证: 同步代码块synchronized(obj),目前情况下保证了一次只能有一个线程在执行,即同步的锁机制。弊端是降低了效率。 若安全问题依旧,肯定是同步出现问题,只要遵守同步前提——多个线程在同步中必须使用同一个锁——就可以解决。仅需要同步共享数据的代码。
  • 分析多线程的安全隐患 1.查看线程任务有无共享数据。 2.查看是否有多条操作共享数据的代码。 例如:检测一下函数
// 两个储户同时到同一个银行存钱,每人存3次,一次存100
class Bank {
    private int sum;
    public void add(int n) {
        sum = sum + n;
        System.out.println("sum = " + sum);
    }
    //两条操作共享数据的命令
    //修改,封装为同步
}
class Customer implements Runnable {
    private Bank b = new Bank();
    public void run() {
        for (int x = 0; x < 3; x++) {
            b.add(100);
            //b和b中的sum属性为共享数据
        }
    }
}
class BankDemo {
    public static void main(String[] args) {
        Customer c = new Customer();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}

修改

class Bank {
    private int sum;
    private Object obj = new Object();
    public void add(int n) {
        //同步代码块,但前对象就是当前的锁,即this
        synchronized(obj) {
            sum = sum + n;
            try{Thread.sleep(10)} catch(Exception e){}
            System.out.println("sum = " + sum);
        }
    }
}

class Bank {
    private int sum;
    //同步函数
    public synchronized void add(int n) {
        sum = sum + n;
        try{Thread.sleep(10)} catch(Exception e){}
        System.out.println("sum = " + sum);
    }
}
class Customer implements Runnable {
    private Bank b = new Bank();
    public void run() {
        for (int x = 0; x < 3; x++) { 
            b.add(100);//b和b中的sum属性为共享数据
        }
    }
}
class BankDemo {
    public static void main(String[] args) {
        Customer c = new Customer();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}

同步的表现形式

  • 同步代码块

  • 同步函数即函数前加synchronized关键字

同步函数的验证

  • 验证同步函数的锁是“this” 验证的需求: 启动两个线程。一个线程负责执行同步代码块(明锁),另一个执行同步函数(this)。 通过切换方式,实现让一个线程在同步代码块中,一个在同步函数中。

同步函数和同步代码块

  • 同步代码块使用任意对象作为锁,同步函数使用this作为锁。
  • 若一个类中需要多个锁,或多个类使用一个锁,只能使用同步代码块

静态同步函数的锁是类名

  • static方法随着类的加载,会生成一个该类的字节码文件对象(Classe类),表示为类名.class。

单利模式的并发访问

饿汉式,相对较为安全

class Single {
    private static final Single SINGLE_INSTANCE = new single();
    private single() {};
    public static Single getInstance() {
        return SINGLE_INSTANCE;
    }
}

懒汉式(延迟加载模式)-在多线程并发访问时,会出现线程安全问题。解决方式--同步

class Single {
    private static Single s = null;
    private Single() {}
    public static Single getInstance() {
        if(s == null)
            s = new Single();
        return s;
    }
}

class Demo implements Runnable {
    public void run() {
        Single.getInstance();
    }
}

class ThreadSingleTest {
    public static void main(String[] args) {
        System.out,println("Hello, world!");
    }
}

解决同步的效率问题,可以通过if对单例对象双重判断。

class Single {
    private static Single s = null;
    private Single() {}
    public static Single getInstance() {
        if(s == null) {
            synchronized(Single.class) {
                if(s == null)
                    s = new Single();    
            }
        }
        return s;
    }
}

死锁

场景1 :同步嵌套

class SaleTicket implements Runnable {
    private int tickets = 100;
    // define a boolean tooken
    boolean flag = true;
    Object obj = new Object();
    public void run() {
        if(flag)
            while(true) {
                 //syn中嵌套sale()
                synchronized(obj){
                    sale();
                }
            }
        else {
            while(true)
                sale();
        }
    }

    public synchronized void sale() {
        //sale()中嵌套syn
        synchronized(obj) { 
            if (tickets > 0) {
                try{Thread.sleep(10);}catch(InterruptedException e) {}
                System.out.println(Thread.currentThread().getName() + "...METHOD..." + tickets-- );
            }
        }
    }
}

class DeadLockDemo {
    public static void main(String[] args) throws InterruptedException {
        SaleTicket st = new SaleTicket();
        // via Thread class
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);

        t1.start();
        Thread.sleep(100);
        st.flag = false;
        t2.start();
    }
}

场景二:线程都处于wait状态

多线程间的通信

  • 多个线程在处理同一资源,但处理任务不一样。

等待唤醒机制

  • 同步解决了没有生产就消费的问题,但是连续生产没有消费的情况,与生产一个消费一个的情况不符。可以使用等待唤醒机制。
  • wait()、notify()、nitifyAll()必须使用于同步中。Code Block中要标识 锁对象.wait()、所对象.notify()、所对象.nitifyAll()。
class Mac {
    private String Macinfo;
    private int seriesNumer = 1;

    // define a flag
    private boolean flag;
    public synchronized void set(String name) {
        if(flag)
            try{wait();} catch(InterruptedException e ) {};
        this.Macinfo = name + "---" + seriesNumer;
        seriesNumer++;
        System.out.println(Thread.currentThread().getName() + "...Producer..."+ this.Macinfo);

        flag = true;
        notify();
    }

    public synchronized void get() {
        if(!flag)
            try{wait();} catch(InterruptedException e ) {};
        System.out.println(Thread.currentThread().getName() + "...Consumer..."+ this.Macinfo);

        flag = false;
        notify();
    }
}

class Producer implements Runnable {
    private Mac m;
    Producer(Mac m) {
        this.m = m;
    }
    public void run() {
        while(true)
            m.set("MacBookPro");
    }
}

class Consumer implements Runnable {
    private Mac m;
    Consumer(Mac m) {
        this.m = m;
    }
    public void run() {
        while(true)
            m.get();
    }
}

class ProducerConsumerDemo {
    public static void main(String[] args) {
        Mac m = new Mac();
        Producer p = new Producer(m);
        Consumer c = new Consumer(m);
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);
        t1.start();
        t2.start();
    }
}

多生产多消费

  • 问题1:重复生产,重复消费。 原因:被唤醒的线程没有经过判断flag就开始工作(生产、消费)。 解决:被唤醒的形成需判断flag。可使用while()循环。

  • 问题2:线程都处于等待状态。 原因:本方线程在唤醒时,又一次唤醒了本方线程,循环判断flag,又继续等待,导致所有线程都等待了。 解决:使用notifyAll(),全部唤醒既有本方线程,又有对方线程,但本方醒后会继续判断flag继续等待,对方线程就可以执行。

class Mac {
    private String Macinfo;
    private int seriesNumer = 1;

    // define a flag
    private boolean flag;
    public synchronized void set(String name) {
        while(flag)
            try{wait();} catch(InterruptedException e ) {};
        this.Macinfo = name + "---" + seriesNumer;
        seriesNumer++;
        System.out.println(Thread.currentThread().getName() + "...Producer..."+ this.Macinfo);

        flag = true;
        notifyAll();
    }

    public synchronized void get() {
        while(!flag)
            try{wait();} catch(InterruptedException e ) {};
        System.out.println(Thread.currentThread().getName() + "...Consumer..."+ this.Macinfo);

        flag = false;
        notifyAll();
    }
}

class Producer implements Runnable {
    private Mac m;
    Producer(Mac m) {
        this.m = m;
    }
    public void run() {
        while(true)
            m.set("MacBookPro");
    }
}

class Consumer implements Runnable {
    private Mac m;
    Consumer(Mac m) {
        this.m = m;
    }
    public void run() {
        while(true)
            m.get();
    }
}

class ProducerConsumerDemo {
    public static void main(String[] args) {
        Mac m = new Mac();
        Producer p = new Producer(m);
        Consumer c = new Consumer(m);
        Thread t0 = new Thread(p);
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);
        Thread t3 = new Thread(c);
        t0.start();     
        t1.start();
        t2.start();
        t3.start();
    }
}

Z
Published on

自己可以给自己加一星哈。

I
Published on
zhicheng:

自己可以给自己加一星哈。

好的,还在编辑,嘿嘿。

Z
Published on

写的真好

M
Published on

已星,求星

I
Published on
zhicheng:

写的真好

谢谢,小白入门中。

B
Published on

已星

D
Published on

来回星了

N
Published on

已星

Sign in or Sign up Leave Comment