Java 多线程和线程同步总结

转载:JAVA多线程实现和线程同步总结

1.JAVA多线程实现方式

JAVA多线程实现方式主要有三种:继承Thread类实现Runnable接口使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。

2. 继承Thread类实现多线程

继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:

public class MyThread extends Thread {
    public void run() {
        System.out.println("MyThread.run()");
    }
}

在合适的地方启动线程如下:

MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();

为什么要调用start方法而不直接调用run()方法呢?

线程的起动并不是简单的调用了你的RUN方法,而是由一个线程调度器来分别调用你的所有线程的RUN方法,我们普通的RUN方法如果没有执行完是不会返回的,也就是会一直执行下去,这样RUN方法下面的方法就不可能会执行了,可是线程里的RUN方法却不一样,它只有一定的CPU时间,执行过后就给别的线程了,这样反复的把CPU的时间切来切去,因为切换的速度很快,所以我们就感觉是很多线程在同时运行一样。简单的调用run方法是没有这样效果的,所以必须调用Thread类的start方法来启动线程。

所以启动线程有两种方法:

一是写一个类继承自Thread类,然后重写里面的run方法,用start方法启动线程。

二是写一个类实现Runnable接口,实现里面的run方法,用new Thread(Runnable target).start()方法来启动。

这两种方法都必须实现RUN方法,这样线程起动的时候,线程管理器好去调用你的RUN方法.

3.实现Runnable接口方式实现多线程

如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口,在实际开发中一个多线程的操作很少使用Thread类,而是通过Runnable接口完成。如下:

public class MyThread extends OtherClass implements Runnable {
    public void run() {
        System.out.println("MyThread.run()");
    }
}

但是在使用Runnable定义的子类中没有start()方法,只有Thread类中才有。此时观察Thread类,有一个构造方法:public Thread(Runnable target)

此构造方法接受Runnable的子类实例,也就是说可以通过Thread类来启动Runnable实现的多线程。(start()可以协调系统的资源):

为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:

MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();

事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:

public void run() {
    if (target != null) {
        target.run();
    }
}

继承Thread类有如下好处:

->避免点继承的局限,一个类可以继承多个接口。

->适合于资源的共享

 

4.使用ExecutorService、Callable、Future实现有返回结果的多线程

ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类。返回结果的线程是在JDK1.5中引入的新特征,确实很实用,有了这种特征我就不需要再为了得到返回值而大费周折了,而且即便实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:

import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
 
/**
* Java线程:有返回值的线程
*
* @author wb_qiuquan.ying
*/
@SuppressWarnings("unchecked")
public class Test {
public static void main(String[] args) throws ExecutionException,
    InterruptedException {
   System.out.println("----程序开始运行----");
   Date date1 = new Date();
 
   int taskSize = 5;
   // 创建一个线程池
   ExecutorService pool = Executors.newFixedThreadPool(taskSize);
   // 创建多个有返回值的任务
   List<Future> list = new ArrayList<Future>();
   for (int i = 0; i < taskSize; i++) {
    Callable c = new MyCallable(i + " ");
    // 执行任务并获取Future对象
    Future f = pool.submit(c);
    // System.out.println(">>>" + f.get().toString());
    list.add(f);
   }
   // 关闭线程池
   pool.shutdown();
 
   // 获取所有并发任务的运行结果
   for (Future f : list) {
    // 从Future对象上获取任务的返回值,并输出到控制台
    System.out.println(">>>" + f.get().toString());
   }
 
   Date date2 = new Date();
   System.out.println("----程序结束运行----,程序运行时间【"
     + (date2.getTime() - date1.getTime()) + "毫秒】");
}
}
 
class MyCallable implements Callable<Object> {
private String taskNum;
 
MyCallable(String taskNum) {
   this.taskNum = taskNum;
}
 
public Object call() throws Exception {
   System.out.println(">>>" + taskNum + "任务启动");
   Date dateTmp1 = new Date();
   Thread.sleep(1000);
   Date dateTmp2 = new Date();
   long time = dateTmp2.getTime() - dateTmp1.getTime();
   System.out.println(">>>" + taskNum + "任务终止");
   return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
}


 

5. 线程同步方法

5.1 synchronized

Java为我们提供了“synchronized(同步化)修饰符”来避免资源冲突,你可以将资源类中某个函数或变量声明为synchronized(同步化),每个继承自Object的类都含有一个机锁(Lock),它是余生俱来的,不需要编写任何代码来启用它。当我们调用任何synchronized(同步化)函数时,该对象将被锁定,对象中所有synchronized(同步化)函数便无法被调用,直到第一个函数执行完毕并解除机锁。

当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码

 一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的synchronized(this)同步代码块。

三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

5.2 使用特殊域变量( Volatile)实现线程同步

a.volatile关键字为域变量的访问提供了一种免锁机制

b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新

c.因此每次使用该域就要重新计算,而不是使用寄存器中的值

d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量

            private volatile int account = 100;
 
            public int getAccount() {
                return account;
            }

 

volatile不能保证原子操作导致的,因此volatile不能代替synchronized。此外volatile会组织编译器对代码优化,因此能不使用它就不适用它吧。它的原理是每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。

5.3 使用重入锁实现线程同步

JavaSE5.0中新增了一个java.util.concurrent包来支持同步。 ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。

ReenreantLock类的常用方法有:

   ReentrantLock() : 创建一个ReentrantLock实例 

   lock() : 获得锁 

   unlock() : 释放锁

class Bank {
            
            private int account = 100;
            //需要声明这个锁
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized
            public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }
                
            }
        }


 

注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 

5.4 使用局部变量实现线程同步 

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

ThreadLocal 类的常用方法:

    ThreadLocal() : 创建一个线程本地变量 

    get() : 返回此线程局部变量的当前线程副本中的值 

    initialValue() : 返回此线程局部变量的当前线程的"初始值" 

    set(T value) : 将此线程局部变量的当前线程副本中的值设置为value

private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };

 

ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题,前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式

5.5 使用阻塞队列实现线程同步

前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。 使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 
LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。 
LinkedBlockingQueue 类常用方法: 
    LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue 
    put(E e) : 在队尾添加一个元素,如果队列满则阻塞 
    size() : 返回队列中的元素个数 
    take() : 移除并返回队头元素,如果队列空则阻塞 

/**
     * 定义一个阻塞队列用来存储生产出来的商品
     */
    private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();

 

5.6 使用原子变量实现线程同步

需要使用线程同步的根本原因在于对普通变量的操作不是原子的。那么什么是原子操作呢?原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作。即-这几种行为要么同时完成,要么都不完成

java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。

 

AtomicInteger类常用方法:

AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger

addAddGet(int dalta) : 以原子方式将给定值与当前值相加

get() : 获取当前值

原子操作主要有

对于引用变量和大多数原始变量(long和double除外)的读写操作;

对于所有使用volatile修饰的变量(包括long和double)的读写操作。

class Bank {
        private AtomicInteger account = new AtomicInteger(100);
 
        public AtomicInteger getAccount() {
            return account;
        }
 
        public void save(int money) {
            account.addAndGet(money);
        }
    }

 

 

 

 

附录:使用三种方式实现的生产者/消费者模式:

import java.util.Stack;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
 
/**
 * @Description: 三种方法实现生产者/消费者
 */
public class ThreadSynchronizeTest {
 
    public static void main(String[] args) {
        ProducerConsumer producerConsumer = new ProducerConsumerViaBlockingQueue();
        producerConsumer.test();
    }
}
 
abstract class ProducerConsumer {
    protected int capacity = 10;
    protected int element = 0;
 
    protected abstract void produce() throws InterruptedException;
 
    protected abstract void consume() throws InterruptedException;
 
    public void test() {
        Thread producer = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        produce();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread consumer = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        consume();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        producer.start();
        consumer.start();
    }
}
 
/**
 * 方法一:ReentrantLock结合Condition
 */
class ProducerConsumerViaReentrantLock extends ProducerConsumer {
    private Stack<Integer> stack = new Stack<>();
    private ReentrantLock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();
 
    @Override
    protected void produce() throws InterruptedException {
        try {
            lock.lock();
            if (stack.size() == capacity) {
                notFull.await();
            }
            ++element;
            System.out.println(Thread.currentThread().getId() + " produce " + element);
            stack.push(element);
            notEmpty.signalAll();
        } finally {
            lock.unlock();
        }
    }
 
    @Override
    protected void consume() throws InterruptedException {
        try {
            lock.lock();
            if (stack.isEmpty()) {
                notEmpty.await();
            }
            int element = stack.pop();
            System.out.println(Thread.currentThread().getId() + " consume " + element);
            notFull.signalAll();
        } finally {
            lock.unlock();
        }
    }
}
 
/**
 * 方法二:synchronized结合wait/notify/notifyAll
 */
class ProducerConsumerViaObjectLock extends ProducerConsumer {
    private Stack<Integer> stack = new Stack<>();
    private Object lock = new Object();
 
    @Override
    protected void produce() throws InterruptedException {
        /**
         * 1. lock为监视器<br/>
         * 2. wait/notify/notifyAll方法必须在synchronized块内调用<br/>
         * 3. 调用wait/notify/notifyAll方法但不持有监视器的使用权将会抛出java.lang.
         * IllegalMonitorStateException<br/>
         */
        synchronized (lock) {
            if (stack.size() == capacity) {
                lock.wait();
            }
            ++element;
            System.out.println(Thread.currentThread().getId() + " produce " + element);
            stack.push(element);
            lock.notifyAll();
        }
    }
 
    @Override
    protected void consume() throws InterruptedException {
        synchronized (lock) {
            if (stack.isEmpty()) {
                lock.wait();
            }
            int element = stack.pop();
            System.out.println(Thread.currentThread().getId() + " consume " + element);
            lock.notifyAll();
        }
    }
}
 
/**
 * 方法三:BlockingQueue
 */
class ProducerConsumerViaBlockingQueue extends ProducerConsumer {
    private BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(capacity);
 
    @Override
    protected void produce() throws InterruptedException {
        ++element;
        System.out.println(Thread.currentThread().getId() + " produce " + element);
        queue.put(element);
    }
 
    @Override
    protected void consume() throws InterruptedException {
        int element = queue.take();
        System.out.println(Thread.currentThread().getId() + " consume " + element);
    }
}



转载:http://www.cnblogs.com/XHJT/p/3897440.html

 




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值