文章目录
知识点
多进程和多线程简介
多进程概念
OS都讲过的
- 当前的操作系统都是多任务OS,每个独立执行的任务就是一个进程。
OS将时间划分为多个时间片(时间很短),每个时间片内将CPU分配给某一个任务,时间片结束,CPU将自动回收,再分配给另外任务。
从外部看,所有任务是同时在执行。但是在CPU上,任务是按照串行依次运行(单核CPU)。如果是多核,多个进程任务可以并行。但是单个核上,多进程只能串行执行。- 多进程的优点
- 可以同时运行多个任务
- 程序因IO堵塞时,可以释放CPU,让CPU为其他程序服务
- 当系统有多个CPU时,可以为多个程序同时服务
- 我们的CPU不再提高频率,而是提高核数
- 2005年Herb Sutter的文章 The free lunch is over,指明多核和并行程序才是提高程序性能的唯一办法
- 多进程的缺点
- 太笨重,不好管理
- 太笨重,不好切换
多线程概念
- 一个程序可以包括多个子任务,可串/并行,每个子任务可以称为一个线程
- 如果一个子任务阻塞,程序可以将CPU调度另外一个子任务进行工作。这样CPU还是保留在本程序中,而不是被调度到别的程序(进程)去。这样,提高本程序所获得CPU时间和利用率。
多进程和多线程对比
多进程 vs 多线程
- 线程共享数据
- 线程通讯更高效
- 线程更轻量级,更容易切换
- 多个线程更容易管理
Java多线程实现
Java 多线程创建
- java.lang.Thread:线程继承Thread类,实现run方法
public class Thread1 extends Thread{
public void run()
{
System.out.println("hello");
}
}
- java.lang.Runnable接口:线程实现Runnable接口,实现run方法
public class Thread2 implements Runnable{
public void run()
{
System.out.println("hello");
}
}
Java的四个主要接口:
Clonable,用于对象克隆
Comparable,用于对象比较
Serializable,用于对象序列化
Runnable,用于对象线程化
Java 多线程启动
Thread方式:
- 可以提供过继承Thread类来创建线程。
- 通过start方法来启动线程的run方法。
public class Thread1 extends Thread{
public void run()
{
System.out.println("hello");
}
public static void main(String[] a)
{
new Thread1().start();
}
}
Runnable方法:
- 可以通过实现Runnable接口来创建线程
- 实现Runnable的对象必须包装在Thread类里面,才可以启动;不能直接对Runnable的对象进行start方法。
- 通过start方法来启动线程的run方法
public class Thread2 implements Runnable{
public void run()
{
System.out.println("hello");
}
public static void main(String[] a)
{
new Thread(new Thread2()).start();
}
}
第一条规则:
- 调用run方法来启动run方法,将会是串行运行。
- 调用start方法来启动run方法,将会是并行运行。
public class ThreadDemo0
{
public static void main(String args[]) throws Exception
{
//new TestThread0().run(); //串行
new TestThread0().start(); //并行
while(true)
{
System.out.println("main thread is running");
Thread.sleep(10);
}
}
}
class TestThread0
{
public void run()
{
while(true)
{
System.out.println(" TestThread1 is running");
try {
Thread.sleep(1000); //1000毫秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
第二条规则:
- main线程可能早于子线程结束。
- main线程和子线程都结束了,整个程序才算终止。
public class ThreadDemo2
{
public static void main(String args[]) throws InterruptedException
{
new TestThread2().start();
// while(true)
// {
// System.out.println("main thread is running");
// Thread.sleep(1000);
// }
}
}
class TestThread2 extends Thread
{
public void run()
{
while(true)
{
System.out.println("TestThread2" +
" is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
第三条规则:
- 实现Runnable的对象必须包装在Thread类里面,才可以启动。
- 不能直接对Runnable的对象进行start方法。
public class ThreadDemo3
{
public static void main(String args[])
{
//new TestThread3().start();
//Runnable对象必须放在一个Thread类中才能运行
TestThread3 tt= new TestThread3();//创建TestThread类的一个实例
Thread t= new Thread(tt);//创建一个Thread类的实例
t.start();//使线程进入Runnable状态
while(true)
{
System.out.println("main thread is running");
try {
Thread.sleep(1000); //1000毫秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class TestThread3 implements Runnable //extends Thread
{
//线程的代码段,当执行start()时,线程从此出开始执行
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName() +
" is running");
try {
Thread.sleep(1000); //1000毫秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
第四条规则:
- 一个线程对象不能多次start,多次start将报异常。
- 多个线程对象都start后,哪一个先执行,完全由JVM/操作系统来主导,程序员无法指定。
public class ThreadDemo4
{
public static void main(String [] args)
{
TestThread4 t=new TestThread4();
t.start();
//t.start();
//t.start();
//t.start();
TestThread4 t1=new TestThread4();
t1.start();
}
}
class TestThread4 extends Thread
{
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName() +
" is running");
try {
Thread.sleep(1000); //1000毫秒
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Java 多线程实现对比
Thread占据了父类的名额,不如Runnable方便
- Thread 类实现Runnable
- Runnable启动时需要Thread类的支持
- Runnable 更容易实现多线程中资源共享
- 结论:
建议实现Runnable接口来完成多线程
Java多线程信息共享
- 线程类
- 通过继承Thread或实现Runnable
- 通过start方法,调用run方法, run方法工作
- 线程run结束后,线程退出
- 粗粒度:子线程与子线程之间、和main线程之间缺乏交流
- 细粒度:线程之间有信息交流通讯
- 通过共享变量达到信息共享
- JDK原生库暂不支持发送消息 (类似MPI并行库直接发送消息)
static变量
同一个Runnable类的成员变量来达到共享。
示例代码(线程卖盘)
public class ThreadDemo0
{
public static void main(String [] args)
{
new TestThread0().start();
new TestThread0().start();
new TestThread0().start();
new TestThread0().start();
}
}
class TestThread0 extends Thread
{
//private int tickets=100; //每个线程卖100张,没有共享
private static int tickets=100; //static变量是共享的,所有的线程共享
public void run()
{
while(true)
{
if(tickets>0)
{
System.out.println(Thread.currentThread().getName() +
" is selling ticket " + tickets);
tickets = tickets - 1;
}
else
{
break;
}
}
}
}
部分运行结果如下:
普通成员变量
示例代码
public class ThreadDemo1
{
public static void main(String [] args)
{
TestThread1 t=new TestThread1();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class TestThread1 implements Runnable
{
private int tickets=100;
public void run()
{
while(true)
{
if(tickets>0)
{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
tickets--;
System.out.println(Thread.currentThread().getName() +" is selling ticket " + tickets);
}
else
{
break;
}
}
}
}
TestThread1只被创建一次,就是t。
而new Thread(t)并没有创建TestThread1对象,而是把t包装成线程对象,然后启动。
第7行到第10行代码使用的是同一个TestThread1的对象t。
部分运行结果如下:
存在问题
-
工作缓存副本
某线程修改了自己工作缓存中的值,其他线程并不知晓,继续用自己的工作缓存中的值,但该值不能反映最新的变量值,大家都是用的前一刻变量值。 -
关键步骤(临界区)缺乏加锁限制
一次只允许一个线程对某一变量进行修改操作。
volatile关键字
采用volatile 关键字修饰变量
,保证不同线程对共享变量操作时的可见性。
示例代码
public class ThreadDemo2
{
public static void main(String args[]) throws Exception
{
TestThread2 t = new TestThread2();
t.start();
Thread.sleep(2000);
t.flag = false;
System.out.println("main thread is exiting");
}
}
class TestThread2 extends Thread
{
//boolean flag = true; //子线程不会停止
volatile boolean flag = true; //用volatile修饰的变量可以及时在各线程里面通知
public void run()
{
int i=0;
while(flag)
{
i++;
}
System.out.println("test thread3 is exiting");
}
}
运行结果如下:
关键步骤加锁
- 关键步骤加锁限制
- 互斥:某一个线程运行一个
代码段(关键区)
,其他线程不能同时运行这个代码段 - 同步:多个线程的运行,必须按照某一种规定的先后顺序来运行
- 互斥是同步的一种特例
- 互斥:某一个线程运行一个
- 互斥的关键字是synchronized
synchronized代码块/函数,只能一个线程进入
- synchronized加大性能负担,但是使用简便
示例代码
public class ThreadDemo3 {
public static void main(String[] args) {
TestThread3 t = new TestThread3();
new Thread(t, "Thread-0").start();
new Thread(t, "Thread-1").start();
new Thread(t, "Thread-2").start();
new Thread(t, "Thread-3").start();
}
}
class TestThread3 implements Runnable {
private volatile int tickets = 100; // 多个 线程在共享的
String str = new String("");
public void run() {
while (true) {
synchronized(str){ //同步代码块
sale();
}
try {
Thread.sleep(100);
} catch (Exception e) {
System.out.println(e.getMessage());
}
if (tickets <= 0) {
break;
}
}
}
public synchronized void sale() { // 同步函数
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--);
}
}
}
部分运行结果如下:
Java多线程管理
线程状态
- NEW 刚创建(new)
- RUNNABLE 就绪态(start)
- RUNNING 运行中(run)
- BLOCK 阻塞(sleep)
- TERMINATED 结束
- Thread的部分API已经废弃
- 暂停和恢复 suspend/resume
- 消亡 stop/destroy
- 线程阻塞和唤醒
- sleep,时间一到,自己会醒来
- wait/notify/notifyAll,等待,需要别人来唤醒(不被唤醒则一直等待)
- join,等待另外一个线程结束
- interrupt,向另外一个线程发送中断信号,该线程收到信号,会触发InterruptedException(可解除阻塞),并进行下一步处理
生产者与消费者问题
- 生产者不断的往仓库中存放产品,消费者从仓库中消费产品。
- 其中生产者和消费者都可以有若干个。
- 仓库规则:容量有限,库满时不能存放,库空时不能取产品 。
主类
package product;
public class ProductTest {
public static void main(String[] args) throws InterruptedException {
Storage storage = new Storage();
Thread consumer1 = new Thread(new Consumer(storage));
consumer1.setName("消费者1");
Thread consumer2 = new Thread(new Consumer(storage));
consumer2.setName("消费者2");
Thread producer1 = new Thread(new Producer(storage));
producer1.setName("生产者1");
Thread producer2 = new Thread(new Producer(storage));
producer2.setName("生产者2");
producer1.start();
producer2.start();
Thread.sleep(1000);
consumer1.start();
consumer2.start();
}
}
仓库
package product;
/**
*仓库
*/
class Storage {
// 仓库容量为10
private Product[] products = new Product[10];
private int top = 0;
// 生产者往仓库中放入产品
public synchronized void push(Product product) {
while (top == products.length) {
try {
System.out.println("producer wait");
wait();//仓库已满,等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//把产品放入仓库
products[top++] = product;
System.out.println(Thread.currentThread().getName() + " 生产了产品"
+ product);
System.out.println("producer notifyAll");
notifyAll();//唤醒等待线程
}
// 消费者从仓库中取出产品
public synchronized Product pop() {
while (top == 0) {
try {
System.out.println("consumer wait");
wait();//仓库空,等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//从仓库中取产品
--top;
Product p = new Product(products[top].getId(), products[top].getName());
products[top] = null;
System.out.println(Thread.currentThread().getName() + " 消费了产品" + p);
System.out.println("comsumer notifyAll");
notifyAll();//唤醒等待线程
return p;
}
}
产品类
package product;
/**
* 产品类
*/
class Product {
private int id;// 产品id
private String name;// 产品名称
public Product(int id, String name) {
this.id = id;
this.name = name;
}
public String toString() {
return "(产品ID:" + id + " 产品名称:" + name + ")";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
生产者
package product;
import java.util.Random;
/**
* 生产者
*/
class Producer implements Runnable {
private Storage storage;
public Producer(Storage storage) {
this.storage = storage;
}
@Override
public void run() {
int i = 0;
Random r = new Random();
while(i<10)
{
i++;
Product product = new Product(i, "电话" + r.nextInt(100));
storage.push(product);
}
}
}
消费者
package product;
/**
* 消费者
*/
class Consumer implements Runnable {
private Storage storage;
public Consumer(Storage storage) {
this.storage = storage;
}
public void run() {
int i = 0;
while(i<10)
{
i++;
storage.pop();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
该示例中两个生产者分别生产10个产品,两个消费者分别消费10个产品。
部分运行结果如下:
线程要做自己的主
- 线程被动地暂停和终止
- 依靠别的线程来拯救自己 😕😕😕
- 没有及时释放资源
- 线程主动暂停和终止
定期监测共享变量
- 如果需要暂停或者终止,先释放资源,再主动动作 😊😊😊
- 暂停:Thread.sleep(),休眠
- 终止:run方法结束,线程终止
示例代码
package interrupt;
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
TestThread1 t1 = new TestThread1();
TestThread2 t2 = new TestThread2();
t1.start();
t2.start();
// 让线程运行一会儿后中断
Thread.sleep(2000);
t1.interrupt();
t2.flag = false;
System.out.println("main thread is exiting");
}
}
class TestThread1 extends Thread {
public void run() {
// 判断标志,当本线程被别人interrupt后,JVM会被本线程设置interrupted标记
while (!interrupted()) {
System.out.println("test thread1 is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println("test thread1 is exiting");
}
}
class TestThread2 extends Thread {
public volatile boolean flag = true;
public void run() {
// 判断标志,当本线程被别人interrupt后,JVM会被本线程设置interrupted标记
while (flag) {
System.out.println("test thread2 is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("test thread2 is exiting");
}
}
interrupted() 是Thread类的方法,用来测试当前线程是否收到一个INTERRUPT信号。
如果收到,该方法返回true,否则返回false。
运行结果如下:
两种方式比较:
- 用interrupt这个标志来中断异常的话,需要自己去添加异常处理,并且此处的异常可能会让你来不及释放资源。
- 定期去监测flag变量,当变量被修改了,就可以很优雅地释放所有资源,然后主动退出。
多线程死锁
- 每个线程互相持有别人需要的锁(哲学家吃面问题)
预防死锁,对资源进行等级排序
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
示例代码
package deadlock;
import java.util.concurrent.TimeUnit;
public class ThreadDemo5
{
public static Integer r1 = 1;
public static Integer r2 = 2;
public static void main(String args[]) throws InterruptedException
{
TestThread51 t1 = new TestThread51();
t1.start();
TestThread52 t2 = new TestThread52();
t2.start();
}
}
class TestThread51 extends Thread
{
public void run()
{
//先要r1,再要r2
synchronized(ThreadDemo5.r1)
{
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(ThreadDemo5.r2)
{
System.out.println("TestThread51 is running");
}
}
}
}
class TestThread52 extends Thread
{
public void run()
{
//先要r2,再要r1
synchronized(ThreadDemo5.r2)
{
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(ThreadDemo5.r1)
{
System.out.println("TestThread52 is running");
}
}
}
}
TimeUnit是JDK 5引入的新类
位于java.util.concurrent包中。
它提供了时间单位粒度和一些时间转换、计时和延迟等函数。
该代码段中t1拿到r1,t2拿到r2,但双发下一步需要取得的锁被对方持有从而无法进行下去,由此产生了死锁。
若两个进程都先拿r1,再拿r2则可以避免死锁
守护(后台)线程
- 普通线程的结束,是run方法运行结束
- 守护线程的结束,是run方法运行结束,或main函数结束
守护线程永远不要访问资源,如文件或数据库等
示例代码
package daemon;
public class ThreadDemo4
{
public static void main(String args[]) throws InterruptedException
{
TestThread4 t = new TestThread4();
t.setDaemon(true);
t.start();
Thread.sleep(2000);
System.out.println("main thread is exiting");
}
}
class TestThread4 extends Thread
{
public void run()
{
while(true)
{
System.out.println("TestThread4" +
" is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
运行结果如下:
练习
请分成6个线程,计算m到n的值(以1到100000000为例)的总和。要求每个线程计算的数字量之差不超过1.
class TestThread implements Runnable {
volatile int[] threadSize = new int[6];// 每个线程的大小
volatile int[] threadStart = new int[6];// 每个线程的开始数字
volatile long sum = 0;// 总和
volatile int threadNum = 0;// 代表当前线程的数字
public void run() {
int threadNum = getThreadNum();
// System.out.println(threadNum);
int size = threadSize[threadNum];
int start = threadStart[threadNum];
long partsum = 0;
// 计算每个线程部分的总和
for (int i = start; i < (size + start); i++) {
partsum = partsum + i;
}
// System.out.println("i="+i);
total(partsum);
System.out.println("Thread " + threadNum + " partsum = " + partsum);
}
private synchronized int getThreadNum() {
return threadNum++;
}
private synchronized void total(long partsum) {
this.sum += partsum;
}
}
public class ThreadDemo {
public static void main(String[] args) {
int m = 1;
int n = 100000000;
int quotient = n / 6; // 商
int remainder = n % 6; // 余数
// System.out.println(quotient);
// System.out.println(remainder);
TestThread t = new TestThread();
// 每个线程大小
for (int i = 0; i < 6; i++) {
t.threadSize[i] = quotient;
if(remainder>0) {
t.threadSize[i]++;
remainder--;
}
}
// 每个线程开始数字
t.threadStart[0] = m;
for (int i = 1; i < 6; i++) {
t.threadStart[i] = t.threadSize[i - 1] + t.threadStart[i - 1];
}
for (int i = 0; i < 6; i++) {
System.out.println();
System.out.println("size " + t.threadSize[i]);
System.out.println("start " + t.threadStart[i]);
}
new Thread(t, "Thread0").start();
new Thread(t, "Thread1").start();
new Thread(t, "Thread2").start();
new Thread(t, "Thread3").start();
new Thread(t, "Thread4").start();
new Thread(t, "Thread5").start();
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//System.out.println("t.threadNum: "+t.threadNum);
if (t.threadNum > 5) { // 如果t.seq>5,则说明6个线程已经运行完毕,结束循环
break;
}
}
// 输出最后的总和
System.out.println("------------------\n" + "sum=" + t.sum);
}
}