Java中多线程知识点总结

Java中有一个比较重要的知识点是多线程,废话就不说了,直接进入多线程的知识点。对多线程我们需要了解和知道一下的知识点:多线程的实现,多线程的安全性,多线程的线程通信,守护线程和线程的优先级等知识点。

首先,我想写点我对多线程的一点认识,其中说的语言不会很官方,但是我相信里面绝对没有坑,不会把大家带到歧途上去的。首先多线程的出现是为了更好的利用计算机的资源,提高程序的响应速度。多线程的运行由操作系统来控制,多核和单核的cpu执行同一个多线程程序的过程可能会不同,在单核中多线程程序只能在单核之间切换执行,在一个时间点上只能有一个线程处于运行的状态。但是在多核的cpu上我们会看到,同一个时间点上,多线程的程序的几个线程可能会运行在cpu的不同核上面,也就是说在同一个时间点上有多个线程同时在运行。大家这里注意点啊,平时我们说的多线程的同时运行很多的时候指的是多个线程在cpu上做快速的切换,如果是单核的cpu这个时候只是多个线程在做快速的切换,在任意一个时间点上,只有一个线程在运行,但是对于多核的cpu可能会是上面单核的情况也可能会是这样的状态:多个线程运行在不同的核心上,这个时候同一个时间点上会有多个线程在运行。

接下来我们看下关于多线程的实现,有二种方法,继承Thread 类和实现Runnable 接口,由于Java是单继承的,所以在平时的开发中我们使用的最多是实现Runnable接口来做的,具体怎么实现,自己去网上找啊。

接下来我们看看多线程的线程安全性的一点问题。为什么会出现线程的安全性那?出现这个问题的原因就是我们在多个线程之间共享和操作和同一个数据,如果不操作同一个数据,各个线程各自操作自己独立的数据,那么就不会有这样的问题出现,既然要共享和操作同一个数据,由于一个线程在执行的时候,会失去cpu的执行资格,等它再获取cpu的执行资格的时候会接着上次执行的继续执行,但是在它没有执行的这段时间会有其他的线程会操作共享的数据,这样可能就会出现错误,下面对这个做一个小的例子程序给大家看下,很简单,有经验的就不用看了。

private int total_number = 100;
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(total_number>0)
        {
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        System.out.println(total_number--);
        }
    }

加上 sleep()只是让问题凸显出现,如果不加这个程序本身也是有问题的,会在某次运行的时候出现,但是程序员在自己电脑上运行的时候可能半天不会出现,这段本身就是有问题的。打印的结果中出现0,-1,-2,为什么会出现真的问题那,我们可以这样看,当我们假设线程一运行到判断和total_number>0 的时候,total_number这个时候是1,刚进去这个时候线程一sleep了,这个时候线程二进来了它也会进行判断,这个时候total_number 还是1,它判断之后也进入sleep ,这个时候我们可以假设线程一sleep的时间到了,并且获得了cpu的执行资格,这个时候我们会看到线程一会接下来执行打印的操作,当然就打印出了-1的错误数据。

出现了错误的数据,我们应该怎么办啊,Java为我们提供的解决办法是使用锁synchronized,使用锁之后我们可以让一个线程使用的的资源在没有完全释放的时候,其他线程无法进行访问,上面的代码可以改成:

private int total_number = 100;
    Object Ob = new Object();
    @Override
    public void run() {
        // TODO Auto-generated method stub
        synchronized (Ob) {
            while(total_number>0)
            {
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            System.out.println(total_number--);
            }
        }
    } 

在这里我们使用了Object对象的锁。关于这里锁的使用,我们可以任意的选择。

在使用锁的时候使用的时候最常出现的一个问题就是死锁问题,死锁的出现就是二个线程都想访问对方的资源但是占着自己的资源不放,这样会造成相互的等待,就出现了死锁,解决这个问题的的办法就是,每一个线程都把自己需要访问的资源都给加上锁,这样就不会出现死锁的问题了。

我们可以把锁加在一段代码块上,同样我们可以把锁加在函数上,加在静态函数的锁使用的是该函数所在类的.class,而在普通函数的锁是this.比如下面的例子:

class A{

    public static  synchronized  void method_a(){

    }

    public synchronized void method_b(){

    }

}

上面的二个函数都加了锁,第一个函数是 static 的,所以它的锁是A.class,而函数method_b 是非静态的,它的锁是this.

接下来我们看线程之间的通信,所谓线程的通信就是指多个线程为了协调完成一个任务,这个任务由这多个线程之间协调完成,并且这个任务的几个部分之间有先后执行的顺序,这个时候我们就需要进行线程之间的通信了,一个线程的任务执行完成之后,通知其他的线程去做,下面是一个简单的例子代码,我们结合代码会更生动点啊。

 /**
 * 2016年6月21日21:36:18
 * @author wukj
 * 这是一个测试线程之间通信例子,在这个程序中我们模拟一个工厂生产物品和消费物品的过程,
 * 在一个线程中生产物品,在另一线程中消费这个物品,这两个线程之间要通信,只有在生产的
 * 线程生产完成了一个物品之后,消费的线程才可以消费掉这个物品。
 *
 */
public class Resource {

    private  int res_id = 0;  // 物品的id
    boolean flag = true;// 标志位,是为了线程之间通信使用的
    /**
     * 这是商品生产的类,如果 flag 则进行商品的生产并且在生产之后将标志位 设成
     * false,并且通知持有同一个锁的其他线程开始自己的线程执行。
     */
    public  synchronized void create() {
        if (flag) {
            System.out.println(++res_id+"is created");
            flag = false;
            this.notifyAll();
        }else {
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }

    public synchronized void sell(){
        if(!flag){
            System.out.println(res_id+"is sold");
            flag = true;
            this.notifyAll();
        }else{
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }

    public int  get_id(){
        return res_id;
    }
}
/**
 * 2016年6月21日22:43:22
 * @author wukj
 * 这类是实现了 Runable 接口的 Create 类 主要用于多线程生产
 */
public class Create  implements Runnable{

    private Resource resource;
    public Create(Resource resource) {
        // TODO Auto-generated constructor stub
        this.resource = resource;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (resource.get_id()<200) {
            resource.create();

        }

    }

}
/**
 * 2016年6月21日22:44:35
 * @author wukj
 * 这个类是实现了Runnable 的sell 类主要用于 对产品进行消费
 *
 */
public class Sell implements Runnable {

    private Resource resource;
    public Sell(Resource resource){ 

        this.resource = resource;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(resource.get_id()<201){
            resource.sell();
        }

    }

}

public class MTest {

    public static void main(String [] args){

        Resource resource = new Resource();
        Create create = new Create(resource);
        Sell sell = new Sell(resource);
        Thread t1 = new Thread(create);
        Thread t2 = new Thread(sell);
        Thread t3 = new Thread(create);
        Thread t4 = new Thread(sell);
        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }

}

在上面的代码中我们使用到wait(),notify(),和notifyAll(),三者是在线程通信的时候必须被使用到的,三者的配合可以很好的完成线程的通信,首先我们看下这三个API。

wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能。因为都个对像都有锁,锁是每个对像的基础,当然操作锁的方法也是最基础了。

wait():

等待对象的同步锁,需要获得该对象的同步锁才可以调用这个方法,否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException。

调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。

notify():

唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

notifyAll():

唤醒所有等待的线程,注意唤醒的是notify之前wait的线程,对于notify之后的wait线程是没有效果的。

这里我们可以看到执行wait和notify之后,该对象上的锁也被释放。我们可以进而思考这样的一个问题。我们可以结合下面的一个简单的代码辅助我们思考这个问题:

public synchronized void sold(){

        if(....)
            this.wait();
        ....;
        ....;
    }

假设有一个线程 Thread_1 进入执行 sold 了,正好满足 if 的条件,会执行this.wait(),执行之后,Thread_1持有的锁会被释放掉,这个时候如果Thread_2也进入之后,也正好满足if 条件也会执行this.wait(),执行之后它也会释放掉锁。如果通过其他线程的notify 或者 notifyAll 唤醒了Thread_1 和Thread_2他们都会接着执行wait后面的代码。在这里我们会想到 sleep(),wait() 和sleep()的区别在于wait 会释放掉锁,而 sleep() 并不会释放掉锁。

有了上面的基础之后,我们简单分析下上面的线程通信的代码例子程序,上面的例子中我们使用了二个线程进行产品的生产,二个线程进行产品的消费,在线程进行生产或者消费之后都要唤醒等待的线程去执行操作。如果只有一个线程进行生产,一个线程进行消费,可以使用notify(),但是这里使用的是二个线程在进行生产和消费,所以要是notifyAll,因为有notify 只能每次唤醒其中的一个线程,在执行代码的时候,可能会出现四个线程都 wait 的情况。

接下来我们看看守护线程,感觉守护是不是有点难理解,换个说法后台线程,好理解了吧,当我们把一个线程设成守护线程之后,当所有的前台线程执行完成之后,这个线程自动会被jvm 杀死,把一个线程设成守护线程也很简单,就是下面的简单例子所示:

Thread t5 = new Thread();
        t5.setDaemon(true);

参数是 true就是守护线程,false就不是。

上面的文章就是简单的介绍了多线程中使用的一些东西,算是自己的笔记,大家如果发现里面有什么问题可以和我取得联系,修改其中的问题,共同进步。

时间: 2024-08-04 22:18:46

Java中多线程知识点总结的相关文章

java中多线程模拟(多生产,多消费,Lock实现同步锁,替代synchronized同步代码块)

import java.util.concurrent.locks.*; class DuckMsg{ int size;//烤鸭的大小 String id;//烤鸭的厂家和标号 DuckMsg(){ } DuckMsg(int size, String id){ this.size=size; this.id=id; } public String toString(){ return id + " 大小为:" + size; } } class Duck{ private int

java中多线程的实例代码

今天开始学习java中的多线程,在看书的过程中写了一个实例来练习多线程的用法,下面把代码放到博文里,里面很多的注释,可以帮助理解.     运行结果如下: main:启动MessageLoop线程...main:等待MessageLoop线程结束...main:继续等待.main:继续等待.main:继续等待.main:继续等待.Thread-0:消息1main:继续等待.main:继续等待.main:继续等待.main:继续等待.Thread-0:消息2main:继续等待.main:继续等待.

Java 中多线程

很多核心Java面试题来源于多线程(Multi-Threading)和集合框架(Collections Framework),理解核心线程概念时,娴熟的实际经验是必需的.这篇文章收集了 Java 线程方面一些典型的问题,这些问题经常被高级工程师所问到. 0.Java 中多线程同步是什么? 在多线程程序下,同步能控制对共享资源的访问.如果没有同步,当一个 Java 线程在修改一个共享变量时,另外一个线程正在使用或者更新同一个变量,这样容易导致程序出现错误的结果. 1.解释实现多线程的几种方法? 一

java中多线程通信实例:生产者消费者模式

线程间的通信: 其实就是多个线程再操作同一个资源,但是操作的动作不同   当某个线程进入synchronized块后,共享数据的状态不一定满足该线程的需要,需要其他线程改变共享数据的状态后才能运行,而由于当时线程对共享资源时独占的,它必须解除对共享资源的锁定的状态,通知其他线程可以使用该共享资源. Java中的 wait(),notify(),notifyAll()可以实现线程间的通信. 生产者--消费者问题是典型的线程同步和通信问题 /** * 生产者和消费者问题,生产者生成出产品,消费者去购

java中多线程执行时,为何调用的是start()方法而不是run()方法

Thead类中start()方法和run()方法的区别 1,start()用来启动一个线程,当调用start()方法时,系统才会开启一个线程,通过Thead类中start()方法来启动的线程处于就绪状态(可运行状态),此时并没有运行,一旦得到CPU时间片,就自动开始执行run()方法.此时不需要等待run()方法执行完也可以继续执行下面的代码,所以也由此看出run()方法并没有实现多线程. 2,run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的.如果直接调用run(),其实就相当

Java中Api知识点总结笔记带案列

博客园 CSND 开源中国 w3school前端网站 一个汉字俩个字节 一个英文一个字节 <Arraylist的用法> 包含了 list.size() list.get() list.add()方法 list.set() list.removepackage ArrayList; import java.util.ArrayList; public class Newguanli { /*public void showNew(){ //1先创建集合对象 ArrayList list=new

黑马程序员------Java中多线程学习总结(一)

Java培训.Android培训.iOS培训..Net培训</a>.期待与您交流! 一.多线程的概念 进程:是一种“自包容”的运行程序,有自己的地址空间. 基于进程的特点是允许计算机同时运行两个或更多的程序 线程:是进程内部单一的一个顺序控制流 . 基于线程的多任务处理环境中,线程是最小的处理单位. 在Java中,一个应用程序可以包含多个线程.每个线程执行特定的任务,并可与其他线程并发执行.多线程使系统的空转时间减少,提高了CPU的利用率.多线程编程隐藏了CPU在任务之间切换的事实. 二.创建

Java中多线程技术

***********************************************声明****************************************************** 原创作品,出自 "晓风残月xj" 博客,欢迎转载,转载时请务必注明出处(http://blog.csdn.net/xiaofengcanyuexj). 由于各种原因,可能存在诸多不足,欢迎斧正! *******************************************

java中数组排序.知识点

import java.util.*; //选择排序 class SwitchTest { public static void main(String[] args) { int[] arr = {3,5,6,23,45,2}; //排序前 printArray(arr); //排序后 // selectSort(arr); Arrays.sort(arr);//java中已经定义好的一种排序方式,开发中,对数组排序,要使用该句代码 // bubbleSort(arr); printArray