Java学习14:多线程

概述

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

Java VM启动的时候会有一个进程java.exe,该进程中至少一个线程负责java程序的执行,而且该线程运行的代码存在于main方法中,该线程称之为主线程。

扩展: 更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。

class ThreadDemo
{
    public static void main(String[] args)
    {
        for(int i=0; i<4000; i++)
         System.out.println("hello world!");
    }
}

自定义线程

自定义一个线程-方法1

通过对api的查找,Java已经提供了对线程这类事物的描述,使用的是Thread类。

创建线程的第一种方式,继承Thread类,步骤如下:
(1)定义类继承Thread类;
(2)复写Thread类中的run方法,目的是将自定义的代码存储在run方法,让线程运行;
(3)调用线程的start方法,该方法有两个作用:启动线程和调用run方法。

class ThreadDemo extends Thread
{
    public void run()
    {
        for(int i=0; i<100; i++)
            System.out.println("ThreadDemo run!");
    }
}   

class Demo
{
    public static void main(String[] args) {
        ThreadDemo d = new ThreadDemo();//创建好一个线程
        d.start();//开启线程并执行该线程的run方法
        //d.run()不行,仅仅是对象调用方法,而线程虽然创建了,但是没有运行
        for(int i=0; i<100; i++)//主线程
            System.out.println("hello world!");
    }

}

这里写图片描述

发现运行结果每一次都不同,因为多个线程都获取CPU的执行权,CPU执行到谁,谁就运行,在某个时刻,只有一个程序在运行(多核除外),CPU在做着快速的切换,以达到看上去是同时运行的效果。

多线程运行时在互相抢夺CPU的执行权,此为多线程的一个特性:随机性,谁抢到谁执行,CPU决定执行时间。
为什么要覆盖run方法?
Thread用于描述线程, 该类就定义了一个功能,用于存储线程要运行的代码,该存储功能,就是run方法。
线程运行状态
这里写图片描述
线程都有自己默认的名称:Thread-编号,该编号从0开始。

class ThreadDemo extends Thread
{
    ThreadDemo(String name)
    {
        super(name);
    }
    public void run()
    {
        for(int i=0; i<10; i++)
            System.out.println(this.getName() + " ThreadDemo run!");
    }
}
class Demo {
    public static void main(String[] args)
    {
        ThreadDemo d1 = new ThreadDemo("d1");//创建线程d1
        ThreadDemo d2 = new ThreadDemo("d2");//创建线程d2
        d1.start();
        d2.start();
        for(int i=0; i<10; i++)
            System.out.println("Hello World!");

    }
}

static Thread currentThread():获取当前线程对象;
getName():获取线程名称;
设置线程名称:setName()或者构造函数。

自定义一个线程-方法2

步骤如下:
(1)定义类实现Runnable接口;
(2)覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中
(3)通过Thread类建立线程对象;
(4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数,因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该run方法所属的对象
(5)调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

class Ticket implements Runnable
{
    private int tick = 100;//票数共享
    public void run()
    {
        while(true)
        {
            if(tick > 0)
            {
                System.out.println(Thread.currentThread().getName() + " sale " + tick--);
            }
        }
    }
}
class Demo
{
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t);//创建一个线程
        Thread t2 = new Thread(t);//创建一个线程
        Thread t3 = new Thread(t);//创建一个线程
        Thread t4 = new Thread(t);//创建一个线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

两种方法(继承方式和实现方式)的区别

继承Thread:线程代码存放在Thread子类run方法中;
实现Runnable:线程代码存在接口的子类的run方法中,上例中tick变量共享;
实现方式的好处:避免了单继承的局限性,在定义线程时,建议使用实现方式。
**比如,**Student类已经继承了Person类,但是此时Student类中有个run方法需要多进程运行,此时不能再继承Thread,需要实现Runnable实现多进程。

多线程的安全问题

/*
...
Thread-0 sale 5
Thread-1 sale 3
Thread-3 sale 4
Thread-2 sale 2
Thread-3 sale 1
Thread-1 sale 0
Thread-0 sale -1
Thread-2 sale -2
*/
class Ticket implements Runnable
{
    private int tick = 100;//票数共享
    public void run()
    {
        while(true)
        {
            if(tick > 0)
            {
                try {Thread.sleep(100);}catch(Exception e){}
                System.out.println(Thread.currentThread().getName() + " sale " + tick--);
            }
        }
    }
}
class Demo
{
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t);//创建一个线程
        Thread t2 = new Thread(t);//创建一个线程
        Thread t3 = new Thread(t);//创建一个线程
        Thread t4 = new Thread(t);//创建一个线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

通过分析发现打印出0, -1, -2等错票,多进程的运行出现了安全问题。
问题原因:
当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块

多线程同步代码块

实现

synchronized(对象)
{
    需要被同步的代码//哪些语句在操作共享数据
}
/*
...
Thread-3 sale 8
Thread-3 sale 7
Thread-3 sale 6
Thread-3 sale 5
Thread-3 sale 4
Thread-3 sale 3
Thread-3 sale 2
Thread-3 sale 1
*/
class Ticket implements Runnable
{
    private int tick = 100;//票数共享
    Object obj = new Object();
    public void run()
    {
        while(true)
        {
            synchronized(obj)//同步锁置1,上锁,其他线程无法执行synchronized中的内容
            {
                if (tick > 0) {
                    try {Thread.sleep(100);} catch (Exception e) {}
                    System.out.println(Thread.currentThread().getName() + " sale " + tick--);//同步锁置0,解锁,其他线程可以执行synchronized中的内容
                }
            }
        }
    }
}
class Demo
{
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t);//创建一个线程
        Thread t2 = new Thread(t);//创建一个线程
        Thread t3 = new Thread(t);//创建一个线程
        Thread t4 = new Thread(t);//创建一个线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

原理

synchronized(对象)
{
    需要被同步的代码//哪些语句在操作共享数据
}

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

同步的前提

(1)必须要有两个或者两个以上的线程;
(2)必须是多个线程使用同一个锁,必须保证同步中只能有一个线程运行。
好处:解决了多线程的安全问题。弊端:多个线程都需要判断锁,较为消耗资源。

如何找问题

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

同步函数的锁是this

将卖票的例程改为同步函数形式:

class Ticket implements Runnable
{
    private int tick = 100;//票数共享
    public void run()
    {
        while(true)
        {
            show();//调用同步函数
        }
    }
    public synchronized void show()//同步函数
    {
        if(tick > 0)
        {
            try {Thread.sleep(100);}catch(Exception e){}
            System.out.println(Thread.currentThread().getName() + " sale " + tick--);
        }
    }
}
class Demo
{
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t);//创建一个线程
        Thread t2 = new Thread(t);//创建一个线程
        Thread t3 = new Thread(t);//创建一个线程
        Thread t4 = new Thread(t);//创建一个线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

函数需要被对象调用,那么函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this。见下例:

//使用2个线程来买票,一个线程在同步代码块中,另一个线程在同步函数中,都在执行买票动作
class Ticket implements Runnable
{
    private int tick = 100;//票数共享
    boolean flag = true;
    //Object object = new Object();
    public void run()
    {
        if(flag)
        {
            while (true)
            {
                synchronized(this)//需要将object换为this,使用同一个锁,都使用this锁,不然不同步
                {
                    if (tick > 0) {
                        try {Thread.sleep(100);} catch (Exception e) {
                        }
                        System.out.println(Thread.currentThread().getName() + " 同步代码块 " + tick--);
                    }
                }

            }
        }
        else
            while (true)
                show();//调用同步函数
    }
    public synchronized void show()//同步函数
    {
        if(tick > 0)
        {
            try {Thread.sleep(100);}catch(Exception e){}
            System.out.println(Thread.currentThread().getName() + " 同步函数 " + tick--);
        }
    }
}
class Demo
{
    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);}catch (Exception e){}//目的是让t1跑起来,判断完flag后再将其置为false
        t.flag = false;
        t2.start();

    }
}

静态同步函数的锁是Class对象

同步函数被静态修饰后,使用的锁不是this,而是该方法所在类的字节码文件对象:类名.class。
原因:静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象,类名. class,该对象的类型是Class。

class Ticket implements Runnable
{
    private static int tick = 100;//票数共享
    boolean flag = true;
    public void run()
    {
        if(flag)
        {
            while (true)
            {
                synchronized(Ticket.class)//同步锁为类的字节码文件对象
                {
                    if (tick > 0) {
                        try {Thread.sleep(100);} catch (Exception e) {
                        }
                        System.out.println(Thread.currentThread().getName() + " 同步代码块 " + tick--);
                    }
                }

            }
        }
        else
            while (true)
                show();//调用同步函数
    }
    public static synchronized void show()//同步函数
    {
        if(tick > 0)
        {
            try {Thread.sleep(100);}catch(Exception e){}
            System.out.println(Thread.currentThread().getName() + " 同步函数 " + tick--);
        }
    }
}
class Demo
{
    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);}catch (Exception e){}//目的是让t1跑起来,判断完flag后再将其置为false
        t.flag = false;
        t2.start();

    }
}

单例设计模式-懒汉式

单例设计模式-懒汉式参考:http://blog.csdn.net/gsh_hello_world/article/details/78196310
懒汉式特点为实例的延迟加载,多线程访问懒汉式会出现安全问题,使用同步代码块或者同步函数解决,使用双重判断提高效率;加同步时,使用对象为该方法所在类的字节码文件对象:类名.class。

//饿汉式
/*
class Student
{
    private int age;
    private Student(){}//1. 构造函数私有化
    private static final Student s = new Student();//2. 在类中创建一个本类对象
    public static Student getInstance()//3. 提供一个方法可以获取到该对象
    {
        return s;
    }
    void setAge(int age)
    {
        this.age = age;
    }
    int getAge()
    {
        return this.age;
    }
}
*/

//懒汉式  
class Student {
    private int age;
    static Student s = null;

    public static Student getInstance() {
        if (s == null)//外层再次判断一下就不会每次都进行同步判断了
        {
            synchronized (Student.class)//同步代码块
            {
                if (s == null)//假如进了if后,进程1挂起,进程2执行新建了一个对象,进程1继续运行时会再次新建一个对象,造成安全问题
                {
                    s = new Student();
                }
            }
        }
        return s;
    }

//    public static synchronized Student getInstance() {//同步函数,每次都要判断锁,比较耗费资源
//
//        if (s == null)//假如进了if后,进程1挂起,进程2执行新建了一个对象,进程1继续运行时会再次新建一个对象,造成安全问题
//        {
//            s = new Student();
//        }
//        return s;
//    }
    void setAge(int age)
    {
        this.age = age;
    }
    int getAge()
    {
        return this.age;
    }
}

class Demo {
    public static void main(String[] args) {
        Student s = Student.getInstance();
        s.setAge(10);
        System.out.println(s.getAge());
    }
}

死锁

进程t1在请求lockb,lockb被进程t2锁住;进程t2在请求locka,locka被进程t1锁住,造成死锁。

/*
    if locka
    else lockb
*/
class test implements Runnable
{
    private boolean flag;
    test(boolean flag)
    {
        this.flag = flag;
    }
    public void run()
    {
        if(flag)
        {
            synchronized(lock.locka)
            {
                System.out.println("if locka");
                synchronized(lock.lockb)
                {
                    System.out.println("if lockb");
                }
            }
        }
        else
        {
            synchronized(lock.lockb)
            {
                System.out.println("else lockb");
                synchronized(lock.locka)
                {
                    System.out.println("else locka");
                }
            }
        }
    }
}
class lock
{
    static Object locka = new Object();
    static Object lockb = new Object();
}
class Demo
{
    public static void main(String[] args) {
        Thread t1 = new Thread(new test(true));
        Thread t2 = new Thread(new test(false));
        t1.start();
        t2.start();
    }
}

猜你喜欢

转载自blog.csdn.net/gsh_hello_world/article/details/78837614
今日推荐