JAVA多线程
1.进程与线程介绍
- 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)。
- 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)。
- 进程的栈内存和堆内存都是独立的,而线程的栈内存是独立的,堆内存是共享的。
2.实现多线程的两种方式
- 继承Thread类,重写该类的run()方法。
- 实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。
- 区别:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
3.线程池
Jdk1.5后提供了Executor为我们管理Thread对象,从而简化了并发编程。Executor允许你管理异步任务的执行,而无须显式地管理线程的生命周期且这是jdk1.5后启动任务的优选方法。
Executors身上有几个静态方法创建线程池:
- CachedThreadPool可以一次性预先执行高昂的线程分配,因而也就可以限制线程的数量了。这样可以节约时 间,因为你不用为每个任务都固定的付出创建线程的开销。
- FixedThreadPool在执行程序过程中通常会创建与所需要量相同的线程,然后在它回收旧线程时停止新建 线程,因此它是合理的Excutor的首选。
- SingleThreadExcutor就像线程数量为1的FixedThreadPool。对于你希望在另一个线程中连续的运行的任何事物(长期存活的任务)来说,都是很有用的。
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3); for(int i=0; i<5; i++){ newFixedThreadPool.execute(new ExceThree(i)); } newFixedThreadPool.shutdown();
4.从任务中返回值
Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在jdk1.5中引入的Callable是一种具有类型参数的泛型,他的类型参数表示的是从任务方法call()中返回的值,并且必须使用ExecutorService.submit()方法调用它。通过get()获取返回结果。
public class CallableTest implements Callable { private int id; public CallableTest(int id){ this.id = id; } public String call() throws Exception { return "This test's id = " + id; } public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); List<Future<String>> list =new ArrayList<Future<String>>(); for(int i=0; i<5; i++){ list.add(service.submit(new CallableTest(i))); } for (Future<String> future : list) { try { System.out.println(future.get()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
5. synchronized
为了解决共享资源竞争的问题,每个对象和类都会有一把锁分别为对象锁和类锁,Java以提供关键字synchronized的形式,为防止资源冲突提供了内置支持。当任务要执行被synchronized关键字保护的代码片段的时候,它会检查锁是否可用,然后获取锁,执行代码。释放锁。
synchronized修饰的对象有以下几种:
1)修饰一个代码块,被修饰的代码称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象(对象锁)。
/** * 同步线程 */ class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0; } public void run() { synchronized(this) { for (int i = 0; i < 5; i++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } public int getCount() { return count; } }
SyncThread的调用:
SyncThread syncThread = new SyncThread(); Thread thread1 = new Thread(syncThread, "SyncThread1"); Thread thread2 = new Thread(syncThread, "SyncThread2"); thread1.start(); thread2.start();
结果如下:
SyncThread1:0 SyncThread1:1 SyncThread1:2 SyncThread1:3 SyncThread1:4 SyncThread2:5 SyncThread2:6 SyncThread2:7 SyncThread2:8 SyncThread2:9*
2)修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象(对象锁)。
public synchronized void method() { // todo }
等同与:
public void method() { synchronized(this) { // todo } }
3)修饰一个静态方法,其作用范围是整个静态方法,作用的对象是这个类的所有对象(类锁)。
/** * 同步线程 */ class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0; } public synchronized static void method() { for (int i = 0; i < 5; i ++) { try { System.out.println(Thread.currentThread().getName() + ":" + (count++)); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void run() { method(); } }
调用代码:
SyncThread syncThread1 = new SyncThread(); SyncThread syncThread2 = new SyncThread(); Thread thread1 = new Thread(syncThread1, "SyncThread1"); Thread thread2 = new Thread(syncThread2, "SyncThread2"); thread1.start(); thread2.start();
结果如下:
SyncThread1:0 SyncThread1:1 SyncThread1:2 SyncThread1:3 SyncThread1:4 SyncThread2:5 SyncThread2:6 SyncThread2:7 SyncThread2:8 SyncThread2:9
4)修饰一个类,其作用范围是{}里面的部分,作用的对象是这个类的所有对象(类锁)。
class ClassName { public void method() { synchronized(ClassName.class) { // todo } } }
synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。
6. volatile
先引入两个概念:
- 原子性:在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
- 可见性:当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取 时,此时内存中可能还是原来的旧值,因此无法保证可见性。
volatile修饰的变量是能保证可见性但是不能保证原子性的。
当一个域的值依赖于它之前的值时(如n=n+1,n++),volatile就无法工作了,如果某个域的值受到其他域的限制时(如n=m+1),volatile也无法工作。
使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。
volatile的典型应用是单例模式的双重检查,为处理原版非延迟加载方式瓶颈问题,我们需要对 instance 进行第二次检查,目的是避开过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同获取锁了),但在Java中行不通,因为同步块外面的if (instance == null)可能看到已存在,但不完整的实例。JDK5.0以后版本若instance为volatile则可行:
public class Singleton { private volatile static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) {// 1 if (instance == null) {// 2 instance = new Singleton();// 3 } } } return instance; } }
7.线程状态
线程在Running的过程中可能会遇到阻塞(Blocked)情况
- 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
- 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
- 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。
来源:https://www.cnblogs.com/jospe/p/7635717.html