java-多线程
一.java实现线程同步的方法(8种?)
为什么要线程同步呢?因为java允许多线程并发,如果多个线程同时访问同一个变量或对象时,既存在读操作又存在写操作,这时候就会出现变量值或者操作对象的状态出现紊乱的情况,从而导致程序异常。 先列出8种方法: 1).同步方法 用synchronized修饰的方法,这个方法可以是静态或者非静态的,但是不能是抽象类的抽象方法,也不能是接口里的接口方法。线程在执行线程方法时具有排他性,也就是说进入一个线程的一个同步方法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导致它释放了该对象的同步锁之后。在一个对象被某个线程锁定之后,其他线程是可以访问这个对象的所有非同步方法的。public synchronized void save(int money) { account += money;
2).同步代码块
同步块是用synchronized锁定一个指定的对象,来对同步块中包含的代码进行同步。 同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。public void save(int money){ synchronized(this){ account+=money; } }
3).wait与notify
wait():使一个线程处于等待状态,并且释放所持有的对象的lock。 sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。 notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。 Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。4).使用特殊域变量(volatile)实现线程同步
volatile关键字为域变量的访问提供了一种免锁机制;使用volatile相当于告诉虚拟机该域可能被其他线程;所以每次使用该域都要重新计算,而不能使用寄存器中的值;volatile不会提供任何原子操作,也不能修饰final的变量。class Bank { //需要同步的变量加上volatile private volatile int account = 100; public int getAccount() { return account; } //这里不再需要synchronized public void save(int money) { account += money; } }
5).使用重入锁
ReentrantLock() : 创建一个ReentrantLock实例 lock() : 获得锁 unlock() : 释放锁 注:关于Lock对象和synchronized关键字的选择: a.最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。 b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁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(); } } }
6).使用局部变量(空间换时间?)
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。7).使用阻塞队列
LinkedBlockingQueue 8).使用原子变量 什么是原子操作呢?原子操作就是指将读取变量值,修改变量值,保存变量值看作是一个整体来进行操作,要么都能同时完成,要么都不完成。 AtomicIntegerclass Bank { private AtomicInteger account = new AtomicInteger(100); public AtomicInteger getAccount() { return account; } public void save(int money) { account.addAndGet(money); }}
二、java实现多线程的方法
1.继承Thread,重写run函数 thread类本质上是实现了runable接口的一个实例,代表线程的一个实例。启动线程的唯一方法是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();
2.实现runable接口创建线程
如果自己的类已经有一个父类了,这时候就不能再extend了,需要用一个runable()接口来实现。 为了启动MyThread,首先要实例化一个Thread,传入自己的MyThread实例。class MyThread implements Runnable{ private int tickets = 100; public void run(){ while(true){ if(tickets > 0){ System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--); } } }}public class ThreadDemo1{ public static void main(String[] args){ MyThread t = new MyThread(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); }}
3.实现callable接口,通过FutureTask包装器创建线程
public class SomeCallableextends OtherClass implements Callable { @Override public V call() throws Exception { // TODO Auto-generated method stub return null; }}
CallableoneCallable = new SomeCallable (); //由Callable 创建一个FutureTask 对象: FutureTask oneTask = new FutureTask (oneCallable); //注释:FutureTask 是一个包装器,它通过接受Callable 来创建,它同时实现了Future和Runnable接口。 //由FutureTask 创建一个Thread对象: Thread oneThread = new Thread(oneTask); oneThread.start(); //至此,一个线程就创建完成了。
4.使用ExecutorService,Callable,Future实现有返回结果的线程
ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。可返回值的任务必须实现Callable接口。类似的,无返回值的任务必须实现Runnable接口。
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。