Java编写程序都运行在在Java虚拟机(JVM)中,在JVM的内部,程序的多任务是通过线程来实现的。每用java命令启动一个java应用程序,就会启动一个JVM进程。在同一个JVM进程中,有且只有一个进程,就是它自己。在这个JVM环境中,所有程序代码的运行都是以线程来运行。
多线程的目的是为了最大限度的利用CPU资源。一般常见的Java应用程序都是单线程的。比如,用java命令运行一个最简单的HelloWorld的Java应用程序时,就启动了一个JVM进程,JVM找到程序程序的入口点main(),然后运行main()方法,这样就产生了一个线程,这个线程称之为主线程。当main方法结束后,主线程运行完成。JVM进程也随即退出 。
对于一个进程中的多个线程来说,多个线程共享进程的内存块,当有新的线程产生的时候,操作系统不分配新的内存,而是让新线程共享原有的进程块的内存。因此,线程间的通信很容易,速度也很快。不同的进程因为处于不同的内存块,因此进程之间的通信相对困难。实际上,操作的系统的多进程实现了多任务并发执行,程序的多线程实现了进程的并发执行。多任务、多进程、多线程的前提都是要求操作系统提供多任务、多进程、多线程的支持。
所谓的“并发执行”、“同时”其实都不是指同一时刻同时运行,而是指在同一时间段内同时运行。操作系统将进程线程进行管理,轮流分配每个进程很短的一段是时间,然后在每个线程内部,程序代码自己处理该进程内部线程的时间分配,多个线程之间相互的切换去执行,这个切换时间也是非常短的。因此多任务、多进程、多线程都是操作系统给人的一种宏观感受,从微观角度看,程序的运行是异步执行的。
一、Java通过继承Thread类实现多线程
<span style="font-size:14px;">package com.amorvos.ThreadTest;
class Ticket extends Thread {
private static int num = 100;
@Override
public void run() {
while (true) {
if (num > 0) {
System.out.println(Thread.currentThread().getName()
+ "Sell Ticket : " + num--);
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}</span>
程序启动运行该类的main方法时,java虚拟机启动一个进程。随着调用实例的start方法,启动了其他线程,整个应用就在多线程下运行。需要注意的是start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态,什么时候运行是由操作系统决定的。因此从程序运行的结果可以发现,多线程程序是乱序执行,只有乱序执行的代码才有必要设计为多线程(实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的)。二、Java通过实现Runnable接口实现多线程
<span style="font-size:14px;">class Ticket implements Runnable {
private static int num = 100;
@Override
public void run() {
while (true) {
if (num > 0) {
System.out.println(Thread.currentThread().getName()
+ "Sell Ticket : " + num--);
}
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
Runnable runnable = new Ticket();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
Thread t4 = new Thread(runnable);
t1.start();
t2.start();
t3.start();
t4.start();
}
}</span>
通过实现Runnable接口,使得该类有了多线程类的特征。Run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。实际上在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。这是代理模式的体现。
三、线程的安全问题
线程的运行状态如下图
进程间的安全问题是由进程间的切换造成的。由于多线程是并发执行的,当多个线程操作某一数据块时操作可能是不可中断的,此时线程却进行了切换,造成数据的错误。这种不安全的现象并不是说一定会出现,而是说存在安全隐患。
1、类内非静态方法采用同步代码块
同步代码块需要指定任意对象作为同步的标志,必须保证多线程同步时使用的是同一把锁。
改写上部线程不安全的代码:
@Override
public void run() {
while (true) {
synchronized (object) {
if (num > 0) {
System.out.println(Thread.currentThread().getName()
+ "Sell Ticket : " + num--);
}
}
}
}
}
2、类内的方法采用同步函数
class Ticket implements Runnable {
private static int num = 100;
public synchronized void show()
{
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "Sell Ticket : " + num--);
}
}
@Override
public void run() {
while (true) {
this.show();
}
}
}
当采用同步非静态函数时,采用的锁是this;当采用同步静态函数时,采用的锁是类的二进制文件,也就是class文件。
补充:单例类中的线程同步
当单例类采用懒汉式时会存在线程安全问题。解决方式如下:
class Single {
private static Single sg = null;
private Single() {
}
public static Single getInstance() {
if (sg == null) {
synchronized (Single.class) {
if (sg == null) {
sg = new Single();
}
}
}
return sg;
}
}
四、、生产者消费者问题—Java中的等待唤醒机制等待唤醒机制实际上是应用了线程的阻塞状态。阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中,需要其他线程唤醒该线程才能继续进行。
同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
<span style="font-size:14px;">package com.amorvos.ThreadTest;
class Resource {
private String name;
private int num;
private boolean flag;
public synchronized void set(String name) {
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
num++;
System.out.println(Thread.currentThread().getName()
+ "..............生产者.............." + this.name);
flag = true;
notifyAll();
}
public synchronized void get() {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "..消费者.."
+ this.name);
flag = false;
notifyAll();
}
}
class Producer implements Runnable {
private Resource r;
public Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.set("馒头");
}
}
}
class Customer implements Runnable {
private Resource r;
public Customer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.get();
}
}
}
public class CustomerProductProblem {
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Customer cus = new Customer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(cus);
Thread t4 = new Thread(cus);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
五、Lock,Condition的使用Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
package com.amorvos.ThreadLockDemo;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumerDemo2
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/*
Lock:替代了Synchronized
lock
unlock
newCondition()
Condition:替代了Object wait notify notifyAll
await();
signal();
signalAll();
*/
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();
public void set(String name)throws InterruptedException
{
lock.lock();
try
{
while(flag)
condition_pro.await();
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);
flag = true;
condition_con.signal();
}
finally
{
lock.unlock();//释放锁的动作一定要执行。
}
}
public void out()throws InterruptedException
{
lock.lock();
try
{
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
flag = false;
condition_pro.signal();
}
finally
{
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.set("+商品+");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.out();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}