java多线程基础

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... 1
Thread-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... 1
Thread-1....sale... 0
Thread-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=1700
sum=1400
sum=1800
sum=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();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值