第五章 .Java多线程和并发编程
1. 多进程和多线程简介
1.1 多进程
:每个独立执行的任务就是一个进程
– 程序因IO堵塞时,可以释放CPU,让CPU为其他程序服务
并发:每个任务分配一个时间片,从外部看,所有任务是同时在执行。但是在CPU上,任务是按照串行依次运行(单核CPU)
并行:如果是多核,多个进程任务可以并行。但是单个核上,多进程只能串行执行。
1.2 多线程
:一个程序可以包括多个子任务(一个进程可以有多个线程)
– 线程由进程创建
– 如果一个子任务阻塞,程序可以将CPU调度另外一个子任务进行工作。这样CPU还是保留在本程序中,而不是被调度到别的程序(进程)去。这样,提高本程序所获得CPU时间和利用率。
1.3 对比
多进程 vs 多线程
- 线程共享数据
- 线程通讯更高效, 更轻量级,更容易切换,更容易管理
多进程执行:启动两个java.exe
多线程执行:只启动一个java.exe
2. Java多线程的实现
2.1Java 多线程创建
- java.lang.Thread
– 线程继承Thread类,实现run方法 - java.lang.Runnable接口
– 线程实现Runnable接口,实现run方法
2.2 Java多线程启动
– start方法,会自动以新线程调用run方法
– Runnable启动时需要Thread类的支持
- 直接调用run方法,将变成串行执行
- 同一个线程,多次start会报错,只执行第一次start方法
- 多个线程启动,其启动的先后顺序是随机的
- main函数(线程)可能早于新线程结束,整个程序并不终止,线程无需关闭,只要其run方法执行结束后,自动关闭,整个程序终止是等所有的线程都终止(包括main函数线程
public class Thread1 extends Thread{
public void run()
{
System.out.println("hello");
}
public static void main(String[] a)
{
new Thread1().start();
}
}
public class Thread2 implements Runnable{
public void run()
{
System.out.println("hello");
}
public static void main(String[] a)
{
new Thread(new Thread2()).start();
}
}
2.3 Java 多线程实现对比
Thread vs Runnable:
- Thread占据了父类的名额,不如Runnable方便
- Runnable启动时需要Thread类的支持
- Runnable 更容易实现多线程中资源共享
3. Java多线程信息共享
- 通过共享变量达到信息共享
– static变量,同一个Runnable类的成员变量
– 同一个类型线程之间信息的传递 - JDK原生库暂不支持发送消息 (类似MPI并行库直接发送消息)
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变量是共享的,所有的线程卖100张
public void run()
{
while(true)
{
if(tickets>0)
{
System.out.println(Thread.currentThread().getName() +
" is selling ticket " + tickets);
tickets = tickets - 1;
}
else
{
break;
}
}
}
}
private int tickets=100; 卖了400张
private static int tickets=100; 卖了103张(出现脏读,幻读)
public class ThreadDemo1
{
public static void main(String [] args)
{
TestThread1 t=new TestThread1();//只实例化了一个TestThread1,共享private int tickets=100;
new Thread(t).start();
new Thread(t).start(); //创建新的线程执行run(),但tickets仍只实例了一个
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;
}
}
}
}
3.1 多线程信息共享问题
- 工作缓存副本,操作不可见
- 关键步骤缺乏加锁限制
- 操作不具有原子性
3.2 多线程信息共享问题的解决方式
- 变量副本问题的解决方法
– 采用volatile 关键字修饰变量,保证不同线程对共享变量操作时的可见性 - 关键步骤加锁限制
–互斥的关键字是synchronized
synchronized代码块/函数,只能一个线程进入
synchronized加大性能负担,但是使用简便
volatile,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) {
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--);
}
}
}
4. Java多线程管理
线程状态
– NEW 刚创建(new)
– RUNNABLE 就绪态(start)
– RUNNING 运行中(run)
– BLOCK 阻塞(sleep)
– TERMINATED 结束
4.1多线程管理方法:
• Thread的部分API已经废弃
– 暂停和恢复 suspend/resume
– 消亡 stop/destroy
• 线程阻塞/和唤醒
– sleep,时间一到,自己会醒来
– wait/notify/notifyAll,阻塞/唤醒,被动等待别人唤醒
– join,等待另外一个线程结束
– interrupt,向另外一个线程发送中断信号,该线程收到信号,会
触发InterruptedException(可解除阻塞),并进行下一步处理
生产消费者问题:
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;
}
}
4.2 死锁
线程管理方式:
- 线程被动地暂停和终止
– 依靠别的线程来拯救自己 (wait,notify…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");
}
}
死锁
– 进程推进不合理,竞争资源导致
– 每个线程互相持有别人需要的锁(哲学家吃面问题)
– 预防死锁,对资源进行等级排序
synchronized(n):对对象加锁
package deadlock;
import java.util.concurrent.TimeUnit;
public class ThreadDemo5
{
public static Integer r1 = 1; //资源数量
public static Integer r2 = 1;
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");
}
}
}
}
线程查看工具 :jvisualvm
4.3守护(后台)线程
– 普通线程的结束,是run方法运行结束
– 守护线程的结束,是run方法运行结束,或main函数结束
- 守护线程永远不要访问资源,如文件或数据库等
–来不及释放资源
TestThread4 t = new TestThread4();
t.setDaemon(true);//设置守护线程