java多线程基础

来源:互联网 发布:ping用到的网络协议 编辑:程序博客网 时间:2024/04/27 15:31

1.多线程的创建

多线程创建的方式有两种:
1.通过继承Thread类来实现多线程
2.通过实现Runnable接口来实现多线程
下面我们分别介绍这两种多线程的创建方式

下面我们来看这两种实现方式:

package gt;/** * Created by Cronous on 2017/10/30. * 创建线程的第一种方式继承Thread类 */public class day02 {    /*    Thread类用于描述线程,线程是需要任务的,所以Thread类也对任务的描述    这个任务就是Thread类中的run方法,也就是说,run方法就是封装自定义线程    运行任务的函数    run方法中定义的就是线程要运行的任务代码    */    public static void main(String[] args){            Demo2 d1 = new Demo2("旺财");            Demo2 d2 = new Demo2("小强");            ////d1.start();            ///d2.start();            Demo1 demo1 = new Demo1("tom");            Demo1 demo2 = new Demo1("jerry");            Thread t1 = new Thread(demo1);            Thread t2 = new Thread(demo2);            t1.start();            t2.start();    }}/*第一种方式 1.定义一个类继承Thread类 2.覆盖Thread类中的run方法 3.直接创建Thread类的子类对象 4.调用 start()方法并调用线程的任务第一种方式有个弊端,如果这个类已经有父类了,就存在这个这个问题了 可以通过 Thread.getName()获取线程名称 我们来看第二种方式,实现 Runnable接口 1.实现接口 2.创建Thread对象,然后将new的对象放入 Thread中 让线程对象调用*/class Demo2 extends Thread{    private String name;    Demo2(String name){        super(name);    }    @Override    public void run() {        show();    }    public void show(){        for(int i = 0;i < 10;i++){            System.out.println(name + "...i=" + i + " "+ Thread.currentThread());        }    }}//第二种方式 实现 Runnable接口class Demo1 implements Runnable{    private String name;    Demo1(String name){        this.name = name;    }    @Override    public void run() {        show();    }    public void show(){        for(int i = 0;i < 10;i++){            System.out.println(name + "...i=" + i + " "+ Thread.currentThread());        }    }}

这里我们可以发现:如果某个类已经有父类了,我们就无法继承Thread类,
这时就存在这个问题了,我们需要使用实现Runnable接口
1.我们需要把覆盖run方法
2.将需要多线程执行的代码放入run函数中
3.如果是继承Thread类直接可以new线程类,然后调用start()方法
4.如果是实现Runnable接口,则new Runnable对象,使用Thread构造方法
Thread(Runnable r)进行构造Thread线程对象,之后调用start()方法
控制台结果:
tom...i=0 Thread[Thread-0,5,main]
tom...i=1 Thread[Thread-0,5,main]
tom...i=2 Thread[Thread-0,5,main]
tom...i=3 Thread[Thread-0,5,main]
tom...i=4 Thread[Thread-0,5,main]
tom...i=5 Thread[Thread-0,5,main]
tom...i=6 Thread[Thread-0,5,main]
tom...i=7 Thread[Thread-0,5,main]
tom...i=8 Thread[Thread-0,5,main]
tom...i=9 Thread[Thread-0,5,main]
jerry...i=0 Thread[Thread-1,5,main]
jerry...i=1 Thread[Thread-1,5,main]
jerry...i=2 Thread[Thread-1,5,main]
jerry...i=3 Thread[Thread-1,5,main]
jerry...i=4 Thread[Thread-1,5,main]
jerry...i=5 Thread[Thread-1,5,main]
jerry...i=6 Thread[Thread-1,5,main]
jerry...i=7 Thread[Thread-1,5,main]
jerry...i=8 Thread[Thread-1,5,main]
jerry...i=9 Thread[Thread-1,5,main]

2.多线程的安全问题

我们看下面的一个卖票案例,有一百张票,多个线程同时进行售票

class day02{    public static void main(String[] args){        Ticket t1 = new Ticket();        Ticket t2 = new Ticket();        Ticket t3 = new Ticket();        Ticket t4 = new Ticket();        t1.start();        t2.start();        t3.start();        t4.start();    }}class Ticket extends Thread{    private int num = 100;    public void run(){        while(true){            if(num > 0){                try{                }catch(InterruptedException e)                {}                System.out.println(Thread.currentThread().getName()+                 "....sale..."+ num--);            }        }    }}

控制台会有这样的结果:

Thread-2....sale... 1Thread-1....sale... 1

我们发现两个线程居然重复进行了1号票的出售,这种结果是有问题的
线程产生问题的原因:
1.多个线程在操作共享数据
2.操作共享数据的线程代码有多条,如上面的代码 if(num > 0)是一条,
System.ou.println()输出又是一条,线程1可能执行到第一条语句就停在那儿了,并没有输出num,但是第二个线程可能就会执行到输出,所以导致原先应该是线程1输出的num被线程2输出,并且因为线程1保留着状态,所以等他得到执行权还是会输出原先的num,这样就导致了重复num的出现,从而导致安全问题

3.安全问题的解决

3.1 同步代码块

解决思路:
将多条操作共享的线程代码封装起来,当有线程在执行这些代码的时候,
其他的线程是不可以参与运算的,必须等待当前线程执行完毕后,其它线程才可以执行

同步代码块可以解决这个问题:
格式:synchronized(对象)
   {
    //需要被同步的代码块
    }

class day02{    public static void main(String[] args){        Ticket t1 = new Ticket();        Ticket t2 = new Ticket();        Ticket t3 = new Ticket();        Ticket t4 = new Ticket();        t1.start();        t2.start();        t3.start();        t4.start();    }}class Ticket extends Thread{    private int num = 100;    Object obj = new Object();    public void run(){        while(true){            synchronized(obj){                  if(num > 0){                    try{                    }catch(InterruptedException e)                    {}                    System.out.println(Thread.currentThread().getName()+                     "....sale..."+ num--);                }            }        }    }}

这样问题即可解决
同步代码块的好处与弊端:
好处:解决了线程安全问题
坏处:一定程度上影响了执行效率,因为同步外的线程都会判断同步锁,这里的锁即同步中的对象
同步的前提:必须有多个线程并使用同一个锁
我们看一个不是使用一个锁的情况,很简单,只需要将Object obj = new Object()放在执行函数run里面即可,这样每次new线程对象的时候obj就不是同一个对象,这就是不同的锁,修改代码如下:

class day02{    public static void main(String[] args){        Ticket t1 = new Ticket();        Ticket t2 = new Ticket();        Ticket t3 = new Ticket();        Ticket t4 = new Ticket();        t1.start();        t2.start();        t3.start();        t4.start();    }}class Ticket extends Thread{    private int num = 100;    //Object obj = new Object();    public void run(){        Object obj = new Object();        while(true){            synchronized(obj){                  if(num > 0){                    try{                    }catch(InterruptedException e)                    {}                    System.out.println(Thread.currentThread().getName()+                     "....sale..."+ num--);                }            }        }    }}

控制台打印:

Thread-2....sale... 1Thread-1....sale... 0Thread-0....sale... -2

出现了负数票,不合常理,出现错误,原因:用的不是同一个锁

3.2 同步函数

我们看一个银行的例子:储户向银行存钱行为

package gt;/** * Created by Cronous on 2017/10/31. * * 这里我们可以使用同步函数 同步代码块在函数内,所以可以将函数进行同步 */public class BankDemo {    public static  void main(String[] args){        Cus c = new Cus();        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 num){             sum = sum + num;//这里代码会出并发问题            System.out.println("sum=" + sum);    }}class Cus implements Runnable{    Bank b = new Bank();    @Override    public void run() {        //Bank b = new Bank();这样的意思是两储户是去两银行存钱        try {            Thread.sleep(10);        } catch (InterruptedException e) {            e.printStackTrace();        }        for(int x = 0;x < 10;x++){            b.add(100);        }    }}

控制台打印:

......sum=1700sum=1400sum=1800sum=1900

居然显示了1900,正确应该是2000
我们会发现出问题的代码在函数内部,函数本身也是一种封装,所以我们可以让函数具备同步性
这里我们可以使用另一种方式:同步函数的方式来解决

class Bank{    private int sum;    //private Object obj = new Object();    public synchronized void add(int num){             sum = sum + num;//这里代码会出并发问题            System.out.println("sum=" + sum);    }}

只需要在函数前加上修饰符 synchronized
这里我们就有个问题,同步代码块的锁是对象,那么同步函数的锁是谁?
我们继续卖票例子:

public class tikets implements Runnable{    private static int num = 100;    Object obj = new Object();    boolean flag = true;    @Override    public void run(){        System.out.println("this" + this);        if(flag){            while(true){                synchronized(obj){                if(num > 0){                    try {                        Thread.sleep(10);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(num-- + "....." + Thread.currentThread());                }                }            }        }else{            while(true){                show();            }        }    }    public  synchronized void show(){        if(num > 0){            try {                Thread.sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(num-- + ".....function" + Thread.currentThread());        }    }}class test{    public static void main(String[] args){        //System.out.println("Hello,world!");        tikets t1 = new tikets();        Thread thread1 = new Thread(t1);        Thread thread2 = new Thread(t1);        thread1.start();        try {            Thread.sleep(10);        } catch (InterruptedException e) {            e.printStackTrace();        }        t1.flag = false;        thread2.start();    }}

设置一个flag,为true执行同步代码块,false执行同步函数
控制台打印结果如下:

......5.....Thread[Thread-0,5,main]4.....functionThread[Thread-1,5,main]2.....functionThread[Thread-1,5,main]3.....Thread[Thread-0,5,main]1.....functionThread[Thread-1,5,main]1.....Thread[Thread-0,5,main]

我们会发现Thread-1Thread-2都卖了1号票,说明同步代码块和同步函数所使用的锁是不一样的
函数是被对象调用的,所以函数持有对象this,所以同步函数的锁即调用它的对象
1.同步函数的锁是固定的this
2.同步代码块锁是任意的对象
建议使用同步代码块

那么静态同步函数的锁是谁?—静态函数没有this
分析:静态函数是随着类的加载而加载,java对象建立都有自己的字节码文件对象,所以静态函数的锁即是它的类对象 synchronized(this.getClass()),如果在静态方法中则为synchronized(Xxxx.class)
下面的show()方法静态修饰了,我们看同步代码块如何修改的

public class tikets implements Runnable{    private static int num = 100;    Object obj = new Object();    boolean flag = true;    @Override    public void run(){        System.out.println("this" + this);        if(flag){            while(true){                //这里做修改this.getClass                synchronized(this.getClass()){                if(num > 0){                    try {                        Thread.sleep(10);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(num-- + "....." + Thread.currentThread());                }                }            }        }else{            while(true){                show();            }        }    }    public  static synchronized void show(){        if(num > 0){            try {                Thread.sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(num-- + ".....function" + Thread.currentThread());        }    }}class test{    public static void main(String[] args){        //System.out.println("Hello,world!");        tikets t1 = new tikets();        Thread thread1 = new Thread(t1);        Thread thread2 = new Thread(t1);        thread1.start();        try {            Thread.sleep(10);        } catch (InterruptedException e) {            e.printStackTrace();        }        t1.flag = false;        thread2.start();    }}
原创粉丝点击