Java学习笔记(多线程_1)

15 多线程

15.1 概念

进程:正在进行中的程序;每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

线程:进程中负责程序执行的控制单元(执行路径),线程在控制着进程的执行。

  1. 一个进程中至少有一个线程;
  2. 一个进程中可以由多个执行路径,称为多线程;
  3. 开启多线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

15.2 创建线程的方法

15.2.1 方法一:继承Thread类

通过对api的查找,java已经提供了对线程这类事物的描述,即Thread类。

  1. 定义类继承Thread;
  2. 重写Thread类中的run方法;目的:将自定义的方法存储在线程中,让线程运行;
  3. 调用线程的start方法,该方法两个作用:启动线程和调用run方法;

例1

class Demo extends Thread{
    public void run(){
        for(int x =0;x<6;x++){
            System.out.println("demo run"+x);
        }
    }
}
public class ThreadDemo {

    public static void main(String[] args) {
            Demo d = new Demo();//创建了一个线程
            d.start();//开启线程并执行该线程的run方法
            //d.run();//仅仅对象调用方法,而线程创建了并未运行。
            for(int x =0;x<5;x++){
                System.out.println("Hello"+x);
            }
    }

}

运行结果:

创建线程的目的就是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行,而运行的指定代码就是这个执行路径的任务。

为何要重写run方法:
Thread类用于描述线程,该类定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。

多线程运行状态:

例2

class Demo extends Thread{
    Demo(String name){
        super(name);
    }

    public void run(){
        for(int x =0;x<6;x++){
            System.out.println((Thread.currentThread()==this)+"..."+this.getName()+".run.."+x);
        }
    }
}
public class ThreadDemo {

    public static void main(String[] args) {
            Demo d = new Demo("one");//创建了一个线程
            Demo d1= new Demo("two");
            d.start();
            d1.start();

            for(int x =0;x<5;x++){
                System.out.println("Hello"+x);
            }
    }

}

PS:
1. 可通过Thread的getName()方法获取线程的名称,名称格式如下:Thread-编号(0开始);
2. Thread创建时就已经命名了;
3. currentThread();获取当前线程对象;
4. 设置线程名称:setName或者构造函数。

15.2.2 方法二:实现Runnable接口

步骤:

  1. 定义类 Runnable接口;
  2. 重写Runnable接口中的run方法;将线程要运行的代码放在该run方法中;
  3. 通过Thread类建立线程对象;
  4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数;
  5. 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

例3

class Ticket1 implements Runnable{
    private int tick = 8;
    public void run(){
        while(true){
            if(tick>0)
                System.out.println(Thread.currentThread().getName()+
                        "...sale.."+tick--);
        }
    }   
}

public class TicketDemo {

    public static void main(String[] args) {
        Ticket1 t = new Ticket1();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);

        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

实现方式和继承方式的区别:

  1. 实现Runnable接口避免了单继承的局限性,在定义线程时,建议使用实现方式;
  2. 继承Thread,线程代码存放在Thread子类run方法中;实现Runnable,线程代码存在接口的子类run方法中。
15.2.3 线程安全

代码:

class Ticket1 implements Runnable{
    private int tick = 10;
    public void run(){
        while(true){
            if(tick>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+
                        "...sale.."+tick--);
            }
        }
    }
}

public class TicketDemo {

    public static void main(String[] args) {
        Ticket1 t = new Ticket1();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);

        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:
打印出tick小于等于0的结果,

问题原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,另一个线程参与进来执行,导致共享数据的错误。

解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

采用同步代码块:

synchronized(对象){
    需要被同步的代码;
}   

对象如同锁,持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权也进不去,因为没有获取锁。

使用前提必须有多个线程并使用同一个锁。

修改后的代码:

class Ticket1 implements Runnable{
    private int tick = 10;
    Object obj = new Object();
    public void run(){
        while(true){
            synchronized(obj){
                if(tick>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+
                            "...sale.."+tick--);
                }
            }
        }
    }
}

public class TicketDemo {

    public static void main(String[] args) {
        Ticket1 t = new Ticket1();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);

        t1.start();
        t2.start();
        t3.start();
    }
}

同步代码块的弊端:多个线程需要判断锁,较为消耗资源,会降低程
序的运行效率。

如何找问题:

  1. 明确哪些代码是多线程运行代码;
  2. 明确共享数据;
  3. 明确多线程运行代码中哪些语句是操作共享数据的。

例4:

class Bank{
    private int sum;
    public void add(int n){
        sum += n;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sum="+sum);
    }
}

class Cus implements Runnable{
    private Bank b = new Bank();

    public void run() {
        for(int x=0;x<3;x++){
            b.add(100);
        }
    }   
}

public class BankDemo {
    public static void main(String[] args) {
        Cus c = new Cus();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        Thread t3 = new Thread(c);
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

修改后:

class Bank{
    private int sum;
    Object obj = new Object();
    public synchronized void add(int n){//同步函数
        //synchronized(obj){
            sum += n;
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("sum="+sum);
        //}
    }
}

class Cus implements Runnable{
    private Bank b = new Bank();

    public void run() {
        for(int x=0;x<3;x++){
            b.add(100);
        }
    }   
}

public class BankDemo {
    public static void main(String[] args) {
        Cus c = new Cus();
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        Thread t3 = new Thread(c);
        t1.start();
        t2.start();
        t3.start();
    }
}

同步的两种表现形式:同步代码块和同步函数。

Ps:
1. 同步函数的锁是固定的this;
2. 同步代码块的锁是任意的对象。

由于同步函数的锁是固定的this,同步代码块是锁的任意的对象,如果同步函数和同步代码块都使用this作为锁,就可以实现同步。

class Ticket implements Runnable{
    private int tick = 10;
    boolean flag = true;

    //Object obj = new Object();
    public void run(){
        if(flag){
            while(true){
                synchronized(this){
                    if(tick>0){
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+
                                "...code.."+tick--);
                    }
                }
            }
        }else{
            while(true)
                show();
        }
    }

    public synchronized void show(){
        if(tick>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+
                    "..function.."+tick--);
        }
    }
}

public class TicketDemo {
    public static void main(String[] args) {
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);

        t1.start();
        try {
            Thread.sleep(10);//线程休眠目的是为了使t1真正启动后,flag才设置为false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.flag=false;
        t2.start();
    }
}

运行结果:

如果同步函数被静态修饰后,使用的锁不是this,而是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。

class Ticket implements Runnable{
    private static int tick = 10;
    boolean flag = true;
    public void run(){
        if(flag){
            while(true){
                synchronized(Ticket.class){//this.getClass()
                    if(tick>0){
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+
                                "...code.."+tick--);
                    }
                }
            }
        }else{
            while(true)
                show();
        }
    }

    public static synchronized void show(){
        if(tick>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+
                    "..function.."+tick--);
        }
    }
}

public class TicketDemo {
    public static void main(String[] args) {
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);

        t1.start();
        try {
            Thread.sleep(10);//线程休眠目的是为了使t1真正启动后,flag才设置为false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.flag=false;
        t2.start();
    }
}
15.2.4 多线程中单例模式

1. 饿汉式

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

饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。

2. 懒汉式

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;
    }
}

懒汉式存在安全问题,可以使用同步函数解决。

方式一:

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

该方式使用效率较低,每次均需要判断;

方法二:

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

原因在于任何一个线程在执行到第一个if判断语句时,如果Single对象已经创建,则直接获取即可,而不用判断是否能够获取锁,相对于上面使用同步函数的方法就提升了效率。如果当前线程发现Single对象尚未创建,则
再判断是否能够获取锁。

  1. 对于单例而言,只有一个对象,因此如果对象已经创建则可直接获取;
  2. 如果没有对象,则进行判断获取锁,
  3. 如果能够获取锁,那么就通过第二个if判断语句判断是否需要创建Single对象。因为可能当此线程获取到锁之前,已经有一个线程创建完Single对象,并且放弃了锁。此时它便没有必要再去创建,可以直接跳出同步代码块,放弃锁,获取Single对象即可。如果有必要,则再创建。
  4. 如果不能获取到锁,则等待,直至能够获取到锁为止,再按步骤一执行。
15.2.5 死锁

同步嵌套的死锁
class Ticket implements Runnable{
private static int tick = 10;
boolean flag = true;
Object obj = new Object();

    public void run(){
        if(flag){
            while(true){
                synchronized(obj){//this.getClass()
                    show();
                }
            }
        }else{
            while(true)
                show();
        }
    }

    public synchronized void show(){
        synchronized(obj){
            if(tick>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+
                        "..function.."+tick--);
            }
        }
    }
}

public class TicketDemo {
    public static void main(String[] args) {
        Ticket t = new Ticket();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);

        t1.start();
        try {
            Thread.sleep(10);//线程休眠目的是为了使t1真正启动后,flag才设置为false;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.flag=false;
        t2.start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值