java进程和线程全面解析

一、进程和线程的区别

(案例源代码:https://github.com/handsomelohas/javaCode)

 在每一个进程上可以继续划分出若干个线程,那么线程的操作一定是要比进程更快的,所以多线程操作性能一定要超过多进程的操作。但是所有的线程都一定是要在进程的基础之上进行划分。所以进程一旦消失,那么线程也一定会消失。(线程永远要依附于进程存在)

 Java多线程操作

在java中对于多线程实现一定要有一个线程的主类,而这个线程的主类往往是需要操作一些资源。但是对于这个多线程主类的实现是有一定要求:

 1、继承Thread父类

2、实现Runnable接口(Callable接口)

 

 二、继承Thread类实现多线程

在java.lang包中存在有Thread类,子类继承在Thread类之后需要覆写Thread类中的run()方法,那么这个方法就属于线程的主方法,定义:public void run()
范例:实现线程的主体类:
package com.lohas.demo;
 class MyThread extends Thread{//表示实现多线程
     private String name;
    public MyThread(String name){//线程的名字
        this.name = name;
    }
    @Override
    public void run(){//覆写run()方法,线程的主方法
        //在线程的主类之中只是将内容输出10
        //(注意:所有的多线程的执行一定是并发完成的,即同一个时间段上会有多个线程线程交替执行)
        //所以为了达到这样的目的,绝对不能够直接去调用run()方法,而是应该调用Thread类中的start()方法启动多线程:public void start()
        for (int i = 0; i < 10; i++) {
            System.out.println(this.name + ",i= " + i);
        }
    }
}
 
public class TestDemoo {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("线程A");
        MyThread mt2 = new MyThread("线程B");
        MyThread mt3 = new MyThread("线程C");
        mt1.start();
        mt2.start();
        mt3.start();
    }
}
 
运行的结果:
线程B,i= 0
线程A,i= 0
线程C,i= 0
线程A,i= 1
线程B,i= 1
线程A,i= 2
线程C,i= 1
线程A,i= 3
线程B,i= 2
线程A,i= 4
线程C,i= 2
线程A,i= 5
线程B,i= 3
线程A,i= 6
线程C,i= 3
线程A,i= 7
线程B,i= 4
线程A,i= 8
线程C,i= 4
线程C,i= 5
线程C,i= 6
线程C,i= 7
线程C,i= 8
线程C,i= 9
线程A,i= 9
线程B,i= 5
线程B,i= 6
线程B,i= 7
线程B,i= 8
线程B,i= 9
所有的线程都属于交替执行,本身是没固定的执行顺序的

 

public class TestDemoo {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("线程A");
        MyThread mt2 = new MyThread("线程B");
        MyThread mt3 = new MyThread("线程C");
        mt1.run();
        mt2.run();
        mt3.run();
    }
}
运行的结果

 

线程A,i= 0

线程A,i= 1

线程A,i= 2

线程A,i= 3

线程A,i= 4

线程A,i= 5

线程A,i= 6

线程A,i= 7

线程A,i= 8

线程A,i= 9

线程B,i= 0

线程B,i= 1

线程B,i= 2

线程B,i= 3

线程B,i= 4

线程B,i= 5

线程B,i= 6

线程B,i= 7

线程B,i= 8

线程B,i= 9

线程C,i= 0

线程C,i= 1

线程C,i= 2

线程C,i= 3

线程C,i= 4

线程C,i= 5

线程C,i= 6

线程C,i= 7

线程C,i= 8

线程C,i= 9

 

结论:调用run()方法没有交替执行,按顺序执行

 

 

思考:为什么启动多线程不使用run()方法,而非要使用start()方法?

为了方便解释此问题,必须打开Thread类中的start源代码来观察(JDK源代码目录:C:\Java\jdk1.8.0_74\src.zip)。

 

public synchronized void start() {

        if (threadStatus != 0)

            throw newIllegalThreadStateException();

        group.add(this);

        boolean started = false;

        try {

            start0();

            started = true;

        } finally {

            try {

                if (!started) {

                   group.threadStartFailed(this);

                }

            } catch (Throwable ignore) {

            }

        }

    }

 

    private native void start0();

 

现在的代码之中首先可以发现,方法会抛出一个异常:IllegalThreadStateException,使用throw声明,没有使用try...catch捕获处理,而之所以会出现这样的情况是因为此异常属于RuntimeException

IllegalThreadStateException的继承图

java.lang.Object

 java.lang.Throwable

 java.lang.Exception

  java.lang.RuntimeException

   java.lang.IllegalArgumentException

    java.lang.IllegalThreadStateException

 

此异常指的是一个线程已经调用了start()方法后又重复执行了start()方法所造成的问题。

在调用start()方法里面发现会调用start0()方法,而start0()方法上使用了native 关键字定义,这个关键字指的是要调用那个本机的操作系统函数。

 

由于线程的启动需要牵扯到操作系统中资源的分配问题,所以具体的线程的启动应该要根据不同的操作系统有不同的实现,而JVM相当于根据系统定义的start0()方法来根据不同操作系统进行该方法的实现,,这样在多线程的start0()方法名称不改变。而不同的操作系统上有不同的实现。(类似接口的实现)

 

结论:只有Thread类的start()方法才能够进行操作系统资源的分配,所以启动多线程的方式永远就是调用Thread类的start()方法实现。

 

 

 

三、实现Runnable接口

  继承Thread类会产生单继承的局限操作,所以现在最好的做法是利用接口来解决问题,于是就可以使用Runnable接口来完成操作。Runnable接口的定义结构:

@Functional Interface

    public interface Runnable{

         public void run();

}

此时的代码使用的是函数式的接口,可以利用Lamda表达式完成。

 

范例:按照正常思路实现多线程

packagecom.lohas.demo;

 

class MyRunnable implements Runnable{//表示实现多线程

    private String name;

    public MyRunnable(String name){//线程的名字

        this.name = name;

    }

    @Override

    public void run(){//覆写run()方法,线程的主方法

        //在线程的主类之中只是将内容输出10次

        //(注意:所有的多线程的执行一定是并发完成的,即同一个时间段上会有多个线程线程交替执行)

        //所以为了达到这样的目的,绝对不能够直接去调用run()方法,而是应该调用Thread类中的start()方法启动多线程:public void start()

        for (int i = 0; i < 10; i++) {

            System.out.println(this.name +",i= " + i);

        }

    }

}

 

 

public class RunnableDemo {

    public static void main(String[] args) {

        MyRunnable mr1 = new MyRunnable("线程A");

        MyRunnable mr2 = new MyRunnable("线程B");

        MyRunnable mr3 = new MyRunnable("线程C");

        new Thread(mr1).start();

        new Thread(mr2).start();

        new Thread(mr3).start();

    }

}

 

如果要想启动多线程依靠只能够是Thread类中的start()方法,在之前继承Thread的时候可以直接将start()方法继承下来继续使用,但是现在实现的是Runnable接口,所以此方法没有了。

于是来观察Thread类中的构造方法:publicThread(Runnable target)。

很多时候为了方便实现,可能直接使用匿名内部类或者是Lamda表达式实现代码。

 

范例:匿名内部类

 

packagecom.lohas.demo;

 

public classAnonyRunnableDemo {

    public static void main(String[] args) {

        String name = "线程对象";

        new Thread(new Runnable() {

            @Override

            public void run() {

                for (int i = 0; i < 10; i++){

                    System.out.println(name +",i = " + i);

                }

            }

        }).start();

    }

}

 

 

范例:Lamda表达式

 

package com.lohas.demo;

public class LamdaRunnableDemo {

    public static void main(String[] args) {

        String name = "Lamda线程对象";

        new Thread(()->{

            for (int i = 0; i < 10; i++) {

                System.out.println(name +",i = " + i );

            }

        }).start();

    }

}

 

只要给出的是函数式接口基本上就都可以使用Lamda表达式或者是方法引用。

 

 

四、两种实现方式的区别
                  

对于多线程的两种实现模式:继承Thread类、实现Runnable接口,那么这两种模式本质上来讲,一定是使用Runnable接口,这样可以避免单继承局限,但是除了这样的使用原则之外,还需要清楚这两种实现方式的联系。

 

首先来观察Thread类的定义结构:publicclass Thread extends Object implements Runnable。

可以发现Thread类实现了Runnable接口。

 

通过类图描述关系可以发现,整个代码的操作中使用的就是一个代理设计模式的结构,但是与传统的代理设计还有一些差别。如果按照传统代理设计模式来讲,现在如果要想启动多线程理论应该是run()方法,但是实质上现在调用的是start()方法,所以名称不符合,之所以会这样主要是因为长期发展后产物,最早的时候设计模式就是个梦。

除了以上的继承关联之外还有一点区别:Runnable接口实现的多线程要比Thread类实现的多线程更方便的表示出数据共享的概念。

 

范例:希望有三个线程进行卖票---Thread实现

package com.lohas.demo; 

class MySellThread extends Thread{//表示实现多线程

    private int ticket = 5;

    private String name;

    public MySellThread(Stringname){//线程的名字

        this.name = name;

    }

 

    @Override

    public void run(){//覆写run()方法,线程的主方法

        for (int i = 0; i <50; i++) {

            if (this.ticket> 0 ){

               System.out.println(name + "卖票,ticket = " +this.ticket--);

            }

        }

    }

}

 

public class SellThreadDemo {

    public static voidmain(String[] args) {

        MySellThread mt1 = newMySellThread("线程A");

        MySellThread mt2 = newMySellThread("线程B");

        MySellThread mt3 = newMySellThread("线程C");

        mt1.start();

        mt2.start();

        mt3.start();

    }

}

 

 

运行结果:

线程B卖票,ticket = 5

线程A卖票,ticket = 5

线程C卖票,ticket = 5

线程A卖票,ticket = 4

线程B卖票,ticket = 4

线程A卖票,ticket = 3

线程C卖票,ticket = 4

线程A卖票,ticket = 2

线程B卖票,ticket = 3

线程A卖票,ticket = 1

线程C卖票,ticket = 3

线程B卖票,ticket = 2

线程C卖票,ticket = 2

线程B卖票,ticket = 1

线程C卖票,ticket = 1

 

发现现在的三个线程各自都是在卖着各自的票

 

 

范例:使用Runnable接口来实现多线程

 

package com.lohas.demo;

class MySellRunnable implements Runnable{//表示实现多线程

    private int ticket = 5;

    private String name;

    public MySellRunnable(String name){//线程的名字

        this.name = name;

    }

 

    @Override

    public void run(){//覆写run()方法,线程的主方法

        for (int i = 0; i < 50; i++) {

            if (this.ticket > 0 ){

                System.out.println(name +"卖票,ticket = " + this.ticket--);

            }

        }

    }

}

public class SellRunnableDemo {

    public static void main(String[] args) {

        MySellRunnable mr = newMySellRunnable("线程");

        new Thread(mr).start();

        new Thread(mr).start();

        new Thread(mr).start();

    }

}

 

运行结果:

线程卖票,ticket = 5

线程卖票,ticket = 4

线程卖票,ticket = 5

线程卖票,ticket = 2

线程卖票,ticket = 3

线程卖票,ticket = 1

 

 

 

请解释多线程的两种实现方式以及区别

多线程需要一个线程的主类,这个类要么继承Thread类,要么实现Runnable接口

使用Runnable接口可以比Thread类更好的实现数据共享的操作,并且利用Runnable接口可以避免单继承局限问题。

 

 

五、实现Callable接口

 

从JDK1.5之后对于多线程的实现多了一个Callable接口,在这个接口里面比Runnable接口唯一的强大之处在于它可以返回执行结果。此接口定义在java.util.concurrent包中定义。

@FunctionalInterface

public interfaceCallable<V>{

         public V call() throws Exception

}

 

  这个泛型表示的是返回值类型。Call()方法就相当于run()方法。

(但是现在出现了一个问题,Thread类中并没有提供接收Callable接口的对象操作。所以出现在如何启动多线程问题)

  首先来观察java.util.concurrent.FutureTask<V>类的定义结构:

java.lang.Object

java.util.concurrent.FutureTask<V>

 All Implemented Interfaces:

Runnable, Future<V>, RunnableFuture<V>

 

范例:

 

package com.lohas.demo;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;


class MySellCallable implements Callable<String> {//表示实现多线程

    private int ticket = 5;

    private String name;

    public MySellCallable(Stringname){//线程的名字

        this.name = name;

    }

 

    @Override

    public String call(){//覆写call()方法,线程的主方法

        for (int i = 0; i <50; i++) {

            if (this.ticket> 0 ){

               System.out.println(name + "卖票,ticket = " +this.ticket--);

            }

        }

        return "票卖完了";

    }

}

public class CallableDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //但是现在出现了一个问题,Thread类中并没有提供接收Callable接口的对象操作。所以出现在如何启动多线程问题

        MySellCallable mc =new MySellCallable("Call线程:");

 

       FutureTask<String> futureTask = new FutureTask<>(mc);//取得执行结果

        Thread thread = newThread(futureTask);

        thread.start();

       System.out.println("返回值 = [" + futureTask.get() + "]");//取得线程主方法的返回值

    }

}

 

运行结果:

Call线程:卖票,ticket= 5

Call线程:卖票,ticket= 4

Call线程:卖票,ticket= 3

Call线程:卖票,ticket= 2

Call线程:卖票,ticket= 1

返回值 = [票卖完了]


 

总结

Thread有单继承局限性所以不使用,但是所有的线程对象一定要通过Thread里中的start()方法启动。

 

 

六、线程的命名和取得操作、线程的休眠、线程的优先级

    线程的所有操作方法几乎都在Thread类中定义好了。

 

6.1线程的命名和取得操作

从本质上来讲多线程的运行状态并不是固定的,所以来讲要想确定线程的执行,唯一的区别就在于线程的名称上。在起名的时候就应该尽可能避免重名,或者避免修改名称。

在Thread类中提供有如下的方法可以实现线程名称的操作:

构造方法:publicThread(Runnable target,String name)

设置名字:publicfinal String getName()

取得名字:publicfinal String getName()

 

   既然线程的执行本身就不确定的状态,所以如果要取得线程名字的话,那么唯一能做的就是取得当前的线程名字,所以在Thread类里面提供有这样的方法:public static Thread currentThread()。

 

范例:线程的命令和取得

package com.lohas.demo;

class MyNameThread implements Runnable{

    @Override

    public void run() {

        for (int i = 0; i < 10; i++) {

           //System.out.println(Thread.currentThread().getId());

           System.out.println(Thread.currentThread().getName() + ",i = "+ i);

        }

    }

}

public class ThreadNameDemo {

    public static void main(String[] args) {

        MyNameThread mnt = new MyNameThread();

        new Thread(mnt,"线程A").start();

        new Thread(mnt).start();

        new Thread(mnt).start();

    }

}

 

运行结果:

Thread-0,i = 0

Thread-0,i = 1

线程A,i = 0

Thread-0,i = 2

线程A,i = 1

Thread-0,i = 3

线程A,i = 2

Thread-0,i = 4

线程A,i = 3

线程A,i = 4

线程A,i = 5

Thread-1,i = 0

线程A,i = 6

线程A,i = 7

Thread-0,i = 5

线程A,i = 8

Thread-1,i = 1

线程A,i = 9

Thread-0,i = 6

Thread-1,i = 2

Thread-1,i = 3

Thread-1,i = 4

Thread-0,i = 7

Thread-1,i = 5

Thread-0,i = 8

Thread-1,i = 6

Thread-0,i = 9

Thread-1,i = 7

Thread-1,i = 8

Thread-1,i = 9

 

如果在设置线程对象时没有设置具体的名字,那么就采用一个默认的名字进行定义。

 

 

范例:观察以下代码

 

package com.lohas.demo;

class MyNameThread1 implements Runnable{

    @Override

    public void run() {

       System.out.println("MyNameThread1线程类: " +Thread.currentThread().getName());

    }

}

public class MyNameDemo1 {

    public static voidmain(String[] args) {

        MyNameThread1 mnt =new MyNameThread1();

        newThread(mnt).start();//线程启动调用run()方法

        mnt.run();              //直接通过对象调用run()方法

    }

}

 

运行结果:

MyNameThread1线程类: main   (mnt.run())

MyNameThread1线程类: Thread-0   (newThread(mnt).start())

 

问题:线程一定是依附于进程存在的,但是现在的进程在哪里呢?

每当使用java命令在JVM上解释某一个程序执行的时候,那么都会默认的启动一个JVM的进程,而主方法只是这进程中的一个线程,所以整个程序一直都跑在线程的运行机制上。

每一个JVM至少会启动两个人线程:主线程、GC线程。

 

6.2 线程的休眠

 

如果要想让某些线程延缓执行,那么就可以使用休眠的方式来进行处理,在Thread类里面提供如下休眠操作:

休眠方法:public staticvoid sleep(long millis) throws InterruptedException

(InterruptedException:中断异常)如果休眠的时间没到就停止休眠了,那么就会产生中断异常。

 

范例:观察休眠

 

packagecom.lohas.demo;

class MySleepThread implements Runnable{

    @Override

    public void run() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

           System.out.println(Thread.currentThread().getName() + ",i = "+ i);

        }

    }

}

public class ThreadSleepDemo {

    public static void main(String[] args) {

        MySleepThread mnt = newMySleepThread();

        new Thread(mnt, "线程A").start();

        new Thread(mnt, "线程B").start();

        new Thread(mnt, "线程C").start();

    }

}

 

运行结果:

线程A,i = 0

线程B,i = 0

线程C,i = 0

线程B,i = 1

线程C,i = 1

线程A,i = 1

线程A,i = 2

线程C,i = 2

线程B,i = 2

线程B,i = 3

线程A,i = 3

线程C,i = 3

线程A,i = 4

线程C,i = 4

线程B,i = 4

线程B,i = 5

线程C,i = 5

线程A,i = 5

线程A,i = 6

线程C,i = 6

线程B,i = 6

线程C,i = 7

线程A,i = 7

线程B,i = 7

线程B,i = 8

线程C,i = 8

线程A,i = 8

线程B,i = 9

线程C,i = 9

线程A,i = 9


以上的代码执行中感觉像是所有线程对象都同时休眠了。但是严格来讲不是同时,是有先后顺序的,只不过顺序小一点而已。

 

范例:

 

packagecom.lohas.demo;

class MySleepThread implements Runnable{

    @Override

    public void run() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

           System.out.println(Thread.currentThread().getName() + ",i = "+ i);

        }

    }

}

public class ThreadSleepDemo{

    public static void main(String[] args)throws InterruptedException {

        MySleepThread mnt = newMySleepThread();

//        new Thread(mnt, "线程A").start();

//        new Thread(mnt, "线程B").start();

//        new Thread(mnt, "线程C").start();

        //线程只能被其他线程中断,这里模仿中断

       Thread t =  new Thread(mnt, "线程A");

       t.start();

       Thread.sleep(2000);

       t.interrupt();//中断

    }

}

后续会使用休眠来进行线程的分析。

 

 

6.3 线程的优先级

 

从理论上来讲优先级越高的线程越有可能先执行。而在Thread类里面定义有以下的优先级的操作方法:

设置优先级:public finalvoid setPriority(int newPriority)

取得优先级:public finalint getPriority()

而对于优先级一共定义有三种:

最高优先级:public staticfinal int MAX_PRIORITY    ----10

中等优先级:public staticfinal int NORM_PRIORITY   ----5

最低优先级:public staticfinal int MIN_PRIORITY    -----1

 

范例:

packagecom.lohas.demo;

class MyPriorityThread implements Runnable{

    @Override

    public void run() {

        for (int i = 0; i < 10; i++) {

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

           System.out.println(Thread.currentThread().getName() + ",i = "+ i);

        }

    }

}

 

public class ThreadPriorityDemo {

    public static void main(String[] args)throws InterruptedException {

        MyPriorityThread mpt = newMyPriorityThread();

        Thread t1 = new Thread(mpt, "线程A");

        Thread t2 =  new Thread(mpt, "线程B");

        Thread t3 = new Thread(mpt, "线程C");

        t1.setPriority(Thread.MAX_PRIORITY);

        t2.setPriority(Thread.MIN_PRIORITY);

        t3.setPriority(Thread.MIN_PRIORITY);

        t1.start();

        t2.start();

        t3.start();

    }

}

 

运行结果:

 

线程C,i = 0

线程B,i = 0

线程A,i = 0

线程A,i = 1

线程C,i = 1

线程B,i = 1

线程A,i = 2

线程B,i = 2

线程C,i = 2

线程B,i = 3

线程C,i = 3

线程A,i = 3

线程A,i = 4

线程B,i = 4

线程C,i = 4

线程A,i = 5

线程C,i = 5

线程B,i = 5

线程A,i = 6

线程C,i = 6

线程B,i = 6

线程A,i = 7

线程B,i = 7

线程C,i = 7

线程A,i = 8

线程C,i = 8

线程B,i = 8

线程C,i = 9

线程B,i = 9

线程A,i = 9

 

可以看出理论上是A,但是有的是其他的

 

范例:

public class ThreadPriorityDemo{

    public static void main(String[] args)throws InterruptedException {

       System.out.println(Thread.currentThread().getPriority());

    }

}

 

运行结果:

5

可以发现主线程属于一般优先级(中等优先级)。

总结

1.        线程要有名字,Thread.currentThread取得当前线程。

2.        线程的休眠是有先后顺序的。

3.        理论上线程的优先级越高越有可能先执行。

 

 

七、线程同步

如果要想进行同步的操作,那么很明显就是多个线程需要访问同一资源。

 

范例:

package com.lohas.demo;

class MySynThread implements Runnable{

    private int ticket = 5;

    @Override

    public void run() {

        for (int i = 0; i < 10; i++) {

            if(ticket > 0){

               System.out.println(Thread.currentThread().getName() + ",剩余ticket=" + ticket--);

            }

        }

    }

}

public class SynThreadDemo {

 

    public static void main(String[] args) {

        MySynThread mst = new MySynThread();

        Thread t1 = new Thread(mst,"售票机A");

        Thread t2 = new Thread(mst,"售票机B");

        Thread t3 = new Thread(mst,"售票机C");

        t1.start();

        t2.start();

        t3.start();

    }

}

 

运行结果:

售票机A,剩余ticket= 5

售票机A,剩余ticket= 3

售票机B,剩余ticket= 4

售票机A,剩余ticket= 1

售票机C,剩余ticket= 2

 

 

范例:

 

package com.lohas.demo;

class MySynThread implements Runnable{

    private int ticket = 5;

    @Override

    public void run() {

        for (int i = 0; i < 10; i++) {

            if(ticket > 0){

                try {

                    Thread.sleep(100);

                } catch (InterruptedExceptione) {

                    e.printStackTrace();

                }

               System.out.println(Thread.currentThread().getName() + ",剩余ticket=" + ticket--);

            }

        }

    }

}

public class SynThreadDemo {

    public static void main(String[] args) {

        MySynThread mst = new MySynThread();

        Thread t1 = new Thread(mst,"售票机A");

        Thread t2 = new Thread(mst,"售票机B");

        Thread t3 = new Thread(mst,"售票机C");

        t1.start();

        t2.start();

        t3.start();

    } 

}

 

运行结果:

售票机A,剩余ticket= 4

售票机B,剩余ticket= 5

售票机C,剩余ticket= 5

售票机C,剩余ticket= 3

售票机B,剩余ticket= 2

售票机A,剩余ticket= 2

售票机C,剩余ticket= 1

售票机B,剩余ticket= -1

售票机A,剩余ticket= 0

 

 

上上面的范例也有这样的问题,只是加上休眠更容易出现方便分析线程同步。

此时就可以观察出程序的问题所在了。出现了负数

 

 

那么这样的操作就属于线程的不同步的操作,所以发现多个线程操作时必须要考虑到资源不同步的问题。

分析:整个的代码发现有一个逻辑的流程错误了。以上的程序中,将是否有票、延迟、卖票分为了三个部分。那么实际上每一个线程如果执行卖票的话,其他线程应该等待当前线程执行完毕后才可以进入。

 

 

 

如果要想在若干行代码实现锁这个概念,那么就需要通过使用同步代码块或者同步方法来解决。

 

1.        同步代码块

使用synchronized关键字定义的代码块就称为同步代码块,但是在进行同步的时候需要设置有一个同步对象,那么往往可以使用this同步当前对象。

 

范例:

package com.lohas.demo;

class MySynThread implements Runnable{

    private int ticket = 20;

    @Override

    public void run() {

        for (int i = 0; i < 100; i++) {

            synchronized (this) {

                if (ticket > 0) {

                    try {

                        Thread.sleep(100);

                    } catch(InterruptedException e) {

                        e.printStackTrace();

                    }

                   System.out.println(Thread.currentThread().getName() + ",剩余ticket=" + this.ticket--);

                }

            }

        }

    }

}

public class SynThreadDemo {

    public static void main(String[] args) {

        MySynThread mst = new MySynThread();

        Thread t1 = new Thread(mst,"售票机A");

        Thread t2 = new Thread(mst,"售票机B");

        Thread t3 = new Thread(mst,"售票机C");

        t1.start();

        t2.start();

        t3.start();

    } 

}

 

运行结果:

 

售票机A,剩余ticket= 20

售票机A,剩余ticket= 19

售票机A,剩余ticket= 18

售票机A,剩余ticket= 17

售票机A,剩余ticket= 16

售票机B,剩余ticket= 15

售票机B,剩余ticket= 14

售票机B,剩余ticket= 13

售票机B,剩余ticket= 12

售票机B,剩余ticket= 11

售票机B,剩余ticket= 10

售票机B,剩余ticket= 9

售票机B,剩余ticket= 8

售票机B,剩余ticket= 7

售票机B,剩余ticket= 6

售票机B,剩余ticket= 5

售票机B,剩余ticket= 4

售票机B,剩余ticket= 3

售票机B,剩余ticket= 2

售票机B,剩余ticket= 1

 

加入同步之后整个的代码执行的速度已经变慢了,而且不像没有同步的时候那样,多个线程会一起进入到方法之中。异步的执行速度要快于同步的执行速度,但是异步的操作属于非线程安全的操作,而同步操作属于线程安全的操作。

但是对于同步操作,除了用于代码块定义外,也可以在方法上定义同步操作。

 

范例:

package com.lohas.demo;

class MySynMethodThread implements Runnable{

    private int ticket = 20;

    @Override

    public void run() {

        for (int i = 0; i < 100; i++) {

            sale();

        }

    }

 

    public synchronized void sale(){

        if (ticket > 0) {

            try {

                Thread.sleep(100);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

           System.out.println(Thread.currentThread().getName() + ",剩余ticket=" + this.ticket--);

        }

    }

}

 

public class SynMethodThreadDemo {

    public static void main(String[] args) {

        MySynMethodThread mst = newMySynMethodThread();

        Thread t1 = new Thread(mst,"售票机A");

        Thread t2 = new Thread(mst,"售票机B");

        Thread t3 = new Thread(mst,"售票机C");

 

        t1.start();

       t2.start();

        t3.start();

 

    }

}

 

运行结果:

售票机A,剩余ticket= 20

售票机A,剩余ticket= 19

售票机C,剩余ticket= 18

售票机B,剩余ticket= 17

售票机B,剩余ticket= 16

售票机B,剩余ticket= 15

售票机B,剩余ticket= 14

售票机B,剩余ticket= 13

售票机B,剩余ticket= 12

售票机B,剩余ticket= 11

售票机B,剩余ticket= 10

售票机B,剩余ticket= 9

售票机B,剩余ticket= 8

售票机B,剩余ticket= 7

售票机B,剩余ticket= 6

售票机B,剩余ticket= 5

售票机B,剩余ticket= 4

售票机B,剩余ticket= 3

售票机B,剩余ticket= 2

售票机B,剩余ticket= 1

 

在多个线程访问同一资源时一定要考虑到数据的同步问题,同步就使用synchronized关键字。

 

死锁分析

很明显,死锁是一种不确定的状态,对于死锁的操作应该出现的越少越好,下面的代码只是一个死锁的演示,能否出现,代码不做任何的实际意义。

 

范例:

package com.lohas.demo;

class Woman {

    //这里去掉synchronized能执行完成(一种)

    public synchronized void say(Man man){

        System.out.println("女人说:你给钱,我就过去。");

        man.get();

    }

    //这里去掉synchronized能执行完成

    public synchronized void get(){

        System.out.println("女人拿到钱,就过去了。");

    }

}

 

class Man {

    //这里去掉synchronized能执行完成(另一种)

    public synchronized void say(Woman woman){

        System.out.println("男人说:你过来,我就给钱。");

        woman.get();

    }

    //这里去掉synchronized能执行完成

    public synchronized void get(){

        System.out.println("男人艹到人,给钱了。");

    }

}

 

public class WomanAndManDemo implements Runnable {

    private Woman woman = new Woman();

    private Man man = new Man();

 

    //线程

    public WomanAndManDemo(){

        new Thread(this).start();

        woman.say(man);

    }

 

    public static void main(String[] args) {

 

        new WomanAndManDemo();

 

    }

    @Override

    public void run() {

        man.say(woman);

    }

}

 

运行结果:

没去掉synchronized的话会死锁,如下图:

 

 

问题:请问多个线程访问同一资源部时可能带来什么问题?以及会产生什么样的附加问题

1.        多个线程访问同一资源时必须考虑同步,可以使用sychronized定义同步代码块或者同步方法。

2.        程序中如果出现过多的同步那么就会产生死锁。

 

总结

如果看见了synchronized声明方法,一定要记住,这是一个同步方法,属于线程安全操作。


  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值