Java学习14:多线程

来源:互联网 发布:perl from json 编辑:程序博客网 时间:2024/05/21 09:54

概述

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

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 5Thread-1 sale 3Thread-3 sale 4Thread-2 sale 2Thread-3 sale 1Thread-1 sale 0Thread-0 sale -1Thread-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 8Thread-3 sale 7Thread-3 sale 6Thread-3 sale 5Thread-3 sale 4Thread-3 sale 3Thread-3 sale 2Thread-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();    }}
原创粉丝点击