实质:操作系统的执行单元是进程(程序),每个jvm实例都是一个进程,系统中可以同时有多个jvm实例,也就是有多个java进程,每个jvm中可以有多个线程,它们共享方法区和堆内存,所以线程间可以共享方法体中的常量、静态变量和堆内存中的全局对象。多个进程之间则完全不能共享内存。(同一个程序运行多次就是多个进程)
比较
多线程状态:
多线程的实现:实现Runnable接口、继承Thread类
public class ThreadExample{ public static void main(String[] args){ //继承Thread的启动方法 Thread t1=new MyThread(); t1.start(); //实现Runnable的启动方法 Thread t2=new Thread(new MyThread2()); t2.start(); } } class MyThread extends Thread{ @Override public void run() { try { Thread.sleep(Math.round(Math.random()*100)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("1"); } } class MyThread2 implements Runnable{ @Override public void run() { try { Thread.sleep(Math.round(Math.random()*100)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("2"); } }
多线程间通信:
由于多线程共享堆内存,可以在主程序中创建对象封装数据,作为参数传给每个子线程,则子线程都可以对该对象做操作。
public class MultyThreadShareData { public static void main(String[] args){ ShareData shareData = new ShareData(); new Thread(new MyIncreRunnable(shareData)).start(); new Thread(new MyDecreRunanble(shareData)).start(); } } class MyIncreRunnable implements Runnable{ private ShareData shareData; public MyIncreRunnable(ShareData shareData) { this.shareData = shareData; } @Override public void run() { for (int i = 1;i<=10;i++){ try { Thread.sleep(Math.round(Math.random()*100)); } catch (InterruptedException e) { e.printStackTrace(); } shareData.increment(); } } } class MyDecreRunanble implements Runnable{ private ShareData shareData; public MyDecreRunanble(ShareData shareData) { this.shareData = shareData; } @Override public void run() { for(int i=1;i<=10;i++){ try { Thread.sleep(Math.round(Math.random()*100)); } catch (InterruptedException e) { e.printStackTrace(); } shareData.decrement(); } } } // 共享数据,对共享数据的操作也在这个对象中完成 class ShareData{ private int count = 0; public synchronized void increment(){ count++; System.out.println(Thread.currentThread().getName()+" inc "+count); } public synchronized void decrement(){ count--; System.out.println(Thread.currentThread().getName()+" dec " +count); } }
线程池
创建销毁线程代价较大,java中使用Executor框架创建各类线程池。
FixedThreadPool+Callable/Runnable(固定大小线程池+带返回结果/不带返回结果)
实用于数据多线程导入数据库、多线程爬虫
import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class threads { public static void main(String[] args) throws InterruptedException, ExecutionException { // TODO Auto-generated method stub int tasksize=8;//线程数 ExecutorService pool = Executors.newFixedThreadPool(tasksize); //不带返回结果时直接pool.execute(new Runnable) List<Future> list = new ArrayList<Future>(); for(int i=0;i<tasksize;i++){ list.add(pool.submit(new one(i))); } pool.shutdown(); //收集结果 String r=""; for(Future f:list){ r+=f.get()+" "; } System.out.println("OUTSIDE:"+r); } } class one implements Callable<Object> { int id; public one(int i){ this.id=i; } public Object call() throws Exception { //重写此方法,此处为线程内程序 int a=this.id; Thread.currentThread().sleep((a%3)*1000); System.out.println("INSIDE:"+a); return a; } }
java内存模型(JMM)
每个线程从共享内存中复制数据到自己私有的内存,线程只能对自己的内存进行操作,然后将数据刷入共享内存。共享内存对应方法区和堆内存,私有内存对应栈区的部分区域。JMM限制了编译器和处理器的重排序,禁止会改变结果的重排序、放行不影响结果的重排序。
volatile关键词
每个线程在执行时将堆内存中的数据存到自己的栈中,多线程时可能主存中的变量已被其他线程改变,但本线程栈中的副本没变,导致一致性问题。使用volatile关键词要求线程修改数据后立即刷入主存,读取数据时直接从主存读取。
原子性、可见性、有序性
synchronized:原子性、可见性、有序性
volatile:可见性、有序性
synchronized、lock
生产者消费者模型:一个线程生成数据,给其他线程使用。
其他小知识:
子线程异常终止,主线程和其他子线程不受影响。
Thread.holdsLock(Object ob);查看当前线程是否获得了某对象的锁
join:在一个线程中start另一个子线程会使两者同时运行,但是使用使用start()后再对子线程join(),就会等待子线程结束再继续运行主线程。
//此处加入join()后会先执行完成t1,然后main继续执行,再另启动一个子线程t2,t1、t2的顺序完全固定为串行。 public static void main(String[] args)throws InterruptedException{ ShareData shareData = new ShareData(); Thread t1=new Thread(new MyDecreRunanble(shareData)); t1.start(); t1.join(); Thread t2=new Thread(new MyIncreRunnable(shareData)); t2.start(); }
http://www.noobyard.com/article/p-zmejnohc-hw.html
http://www.noobyard.com/article/p-ragctmna-cz.html
http://www.noobyard.com/article/p-xmkmnyxh-ct.html
https://www.cnblogs.com/lemingyin/p/8878513.html
https://www.cnblogs.com/bsjl/p/7693029.html