进程是程序向操作系统申请资源的基本单位,线程是进程中可独立执行的最小单位。通常一个进程可以包含多个线程,至少包含一个线程,同一个进程中所有线程共享该进程的资源。
1、根本区别
进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。
2、资源开销
每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间(在Java中,线程之间是共享堆区和方法区的资源,但是它们拥有着独立的栈区),每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。
3、包含关系
一个进程里可以包含多个线程。
4、内存分配
同一进程的线程共享本进程的地址空间和资源,而线程之间的地址空间和资源是相互独立的。
5、影响关系
一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
6、执行过程
每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。
这里讲解创建线程的三种方式。
使用方法:
代码如下:
//创建线程的第一种方式:编写继承Thread的线程类。
package thread;
class myThread extends Thread{
public void run(){
for(int i = 0; i < 1000; i++){
System.out.println("分支线程->" + i);
}
}
}
public class ThreadTest01 {
public static void main(String[] args) {
myThread t = new myThread();
t.start();
}
}
这种方式现在不是很推荐,好的Java程序应该将并行运行的任务与运行机制解耦合。
使用方法:
代码如下:
public class ThreadTest02 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunable());
t.start();
}
}
class MyRunable implements Runnable{
public void run(){
for(int i = 0; i < 1000; i++){
System.out.println("分支线程->" + i);
}
}
}
该类较常用,因为Java程序要面向接口编程,实现了接口的类还可以继承其他类,而继承了类的类,不能在继承其他类。
使用步骤:
public class ThreadTest09 {
public static void main(String[] args) {
FutureTask futureTask = new FutureTask(new myCallable());
Thread thread = new Thread(futureTask);
thread.start();
}
}
class myCallable implements Callable<Integer>{
public Integer call(){
System.out.println("Call----->start");
try {
Thread.sleep(1000*3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Call----->end");
return 1000;
}
}
使用该方法创建线程时,核心方法是call(),与其他方式最大的不同是:该方法有返回值,其返回值类型就是Callable接口中泛型对应的类型
。我们通过FutureTask对象
调用get()
方法,获得该线程的返回值。但是这个方法也有相应的缺点,调用get()方法后,在主线程获得该线程的返回值前,主线程会进入阻塞状态。
注意:
调用get()方法会抛出两个异常:InterruptedException
、ExecutionException
Java中有如下6种状态
新建状态 :
使用new关键字创建一个thread对象,刚刚创建出的这个线程就处于新建状态。在这个状态的线程没有与操作系真正的线程产生关联,仅仅是一个java对象。
可运行:
正在进行运行的线程,只有处于可运行状态的线程才会得到cpu资源。
可运行状态可以分为两类理解:就绪状态和运行状态:
运行状态:拥有抢夺CPU时间片的能力,但还未抢夺成功。
就绪状态:成功抢夺CPU时间片。
阻塞 :
在可运行阶段争抢锁失败的线程就会从可运行—>阻塞
等待 :
可运行状态争抢锁成功,但是资源不满足,主动放弃锁(调用wait()方法)。条件满足后再恢复可运行状态(调用notiy()方法)。
有时限等待:
类似于等待,不过区别在于有一个等待的时间,到达等待时间后或者调用notiy(),都能恢复为可运行状态。
有两种方式可以进入有时限等待:wait(Long)和sleep(Long)
终结 :代码全部执行完毕后,会进入到终结状态,释放所有的资源。
Java中可以调用interrupt()
向线程发出中断请求,从而使线程中断,中断不会对处于运行状态的线程产生影响,但是可以打断线程的阻塞状态(wait(), sleep()
),使其进入就绪状态,并抛出InterruptedException
异常。
测试代码如下:
public class ThreadTest06 {
public static void main(String[] args) {
Thread t = new Thread(new myRunnable2());
t.start();
t.interrupt();
}
}
class myRunnable2 implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName() + "------>start");
try {
Thread.sleep(1000*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "------>end");
}
}
执行结果如下:
每一个线程都有一个boolean
类型的中断状态,该中断状态初始为false
。当一个线程调用interrupted
方法时,线程的中断状态被置为true
。对于一个运行状态的线程来说,没有什么影响,但是对于阻塞状态的线程来说,遇到中断状态时(true),会抛出一个InterruptedException
,同时将中断状态重新置为false
。
测试代码如下:
public class ThreadTest06 {
public static void main(String[] args) {
Thread t = new Thread(new myRunnable2());
t.start();
}
}
class myRunnable2 implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName() + "------>start");
System.out.println("中断前:" + Thread.currentThread().isInterrupted());
Thread.currentThread().interrupt();
System.out.println("中断后(遇到堵塞之前):" + Thread.currentThread().isInterrupted());
System.out.println();
try {
Thread.sleep(1000*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("中断后(遇到堵塞之后):" + Thread.currentThread().isInterrupted());
System.out.println(Thread.currentThread().getName() + "------>end");
}
}
运行结果如下:
public void interrupt()
向线程发出中断请求,线程的中断状态被置为true。
public static boolean interrupted()
测试当前线程(正在执行的线程)是否被中断,并将中断状态重置为false。
public boolean isInterrupted()
测试线程的中断状态,但是并不会重置中断状态。
这里会对线程优先级和守护线程进行说明。
Java线程的优先级属性本质上只是一个给线程调度器的提示信息,以便于线程调度器决定优先调度哪些线程运行,也可以理解为,优先级高的线程更容易抢夺到CPU时间片。
每个线程的优先级都在1到10之间,1的优先级为最低,10的优先级为最高,在默认情况下优先级都是Thread.NORM_PRIORITY(常数 5)。
虽然开发者可以定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。
常用方法和属性
Java 中的线程分为两种:守护线程和用户线程
。任何线程都可以设置为守护线程和用户线程,通过方法setDaemon(true)
可以把该线程设置为守护线程,反之则为用户线程。
用户线程:
运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程。
守护线程:
运行在后台,为其他前台线程服务,比如垃圾回收线程,JIT(编译器)线程就可以理解为守护线程。一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作。
守护线程应该永远不去访问固有资源 ,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
注意事项:
相关方法
public final void setDaemon(boolean on)
将线程设置为守护线程。
当两个线程存取相同对象,并且每一个线程都调用了修改线程状态的方法,将会发生什么呢?
下面用代码演示:
首先创建一个银行账户类,如下:
public class Account {
//账号
private String actno;
//余额
private int banlance;
//构造器
public Account() {
}
public Account(String actno, int banlance) {
this.actno = actno;
this.banlance = banlance;
}
//setter and getter
public void setBanlance(int banlance) {
this.banlance = banlance;
}
public int getBanlance() {
return banlance;
}
//取钱操作
public void takeOut(Account act, int takeOut){
int newBanlance = act.getBanlance() - takeOut;
//这里sleep()只是为了让效果更加明显
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
act.setBanlance(newBanlance);
System.out.println( Thread.currentThread().getName() + "取出:" + takeOut + " 剩余:" + newBanlance);
}
}
然后我们实现一个Runnable类,如下:
class myRunnable implements Runnable{
//银行账户对象
Account act;
//要取出的金额
int takeOut;
//构造器
public myRunnable(){
}
public myRunnable(Account act, int takeOut){
this.act = act;
this.takeOut = takeOut;
}
public void run(){
act.takeOut(act,takeOut);
}
}
然后我们用两个线程同时对一个账户进行取钱操作,如下:
class Test{
public static void main(String[] args) {
Account act = new Account("Joken",10000);
//去五次钱,每次取五千
Thread t1 = new Thread(new myRunnable(act,5000));
Thread t2 = new Thread(new myRunnable(act,5000));
t1.start();
t2.start();
}
}
程序运行结果如下:
取了两次钱,但是余额仍然还有5000。
产生以上问题的原因在于:act.takeOut(act,takeOut)并不是一个原子操作
这条指令会被分解为:
注意: 在这里我并未将每一个操作都分解为原子操作,因为这里的样例线程较少,操作也很少,为了方便讲解所以将风险忽略,但是在实际开发中并不能忽略。
现在,线程一执行完了第一步,然后他被剥夺了CPU执行权,线程二又开始执行第一步,虽然线程一取了5000元,但是还没来得及更新,所以线程二在执行时,账户对象的余额仍然是10000元,这样就产生了错误。
将上部分代码通过synchronized改进,如下:
public void takeOut(Account act, int takeOut){
synchronized (this){
int newBanlance = act.getBanlance() - takeOut;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
act.setBanlance(newBanlance);
System.out.println( Thread.currentThread().getName() + "取出:" + takeOut + " 剩余:" + newBanlance);
}
}
对以上代码的解释:
找到锁后该线程才会进入可运行状态,没找到锁他就会进入阻塞状态,直到它在锁池中找到这个对象锁。(其它线程将这个锁释放)
。**synchronized的三种用法 **
用法一:
synchronized(共享对象){
//同步代码
}
用法二:
synchronized 加在实例方法上,锁的是this,同步整个方法体。
public synchronized void test(){......}
synchronized 加在静态方法上,找的是类锁,一个类无论new几个对象都只有一个类锁。
用法三:
public static synchronized void test(){......}
死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
死锁样例:
首先创建两个线程类,如下:
class myThread extends Thread{
Object o1;
Object o2;
public myThread(Object o1, Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o1){
System.out.println(Thread.currentThread().getName() + "拿到o1的锁,准备拿o2的锁");
synchronized (o2){
System.out.println(Thread.currentThread().getName() + "拿到o2的锁");
}
}
}
}
class myThread2 extends Thread{
Object o1;
Object o2;
public myThread2(Object o1, Object o2){
this.o1 = o1;
this.o2 = o2;
}
public void run(){
synchronized (o2){
System.out.println(Thread.currentThread().getName() + "拿到o2的锁,准备拿o1的锁");
synchronized (o1){
System.out.println(Thread.currentThread().getName() + "拿到o1的锁");
}
}
}
}
创建这两个线程类对象,并传入相同对象,如下:
public class deadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
myThread t1 = new myThread(o1,o2);
myThread2 t2 = new myThread2(o1,o2);
t1.start();
t2.start();
}
}
运行结果如下:
生产消费者问题是多线程经典问题,其中生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据
void notify()
随机选择在该对象上调用wait方法的线程,解除其阻塞状态。该方法只能在一个同步方法或同步代码块中调用。如果当前线程不是该对象锁的持有者,该方法将会抛出一个IllegalMonitorStateException
。
void notifyAll()
解除在该对象上调用wait方法的全部线程,解除其阻塞状态。该方法只能在一个同步方法或同步代码块中调用。如果当前线程不是该对象锁的持有者,该方法将会抛出一个IllegalMonitorStateException
。
void wait()
使当前线程进入阻塞状态,并释放该对象的锁,直到它被通知(nitify),并且在调用时抛出InterruptedException
,如果当前线程不是该对象锁的持有者,该方法将会抛出一个IllegalMonitorStateException
。
public class ThreadApplication {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
ConsumerThread consumer = new ConsumerThread(list);
ProcuderThread procuder = new ProcuderThread(list);
consumer.setName("消费者");
procuder.setName("生产者");
consumer.start();
procuder.start();
}
}
//消费者线程
class ConsumerThread extends Thread{
List<String> list = new ArrayList<>();
public ConsumerThread(List<String> list){
this.list = list;
}
public void run(){
while(true){
synchronized (list){
if(list.isEmpty()){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "消费了" + list.remove(0));
list.notify();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//生产者线程
class ProcuderThread extends Thread{
int i = 1;
List<String> list = new ArrayList<>();
public ProcuderThread(List<String> list){
this.list = list;
}
public void run(){
while(true){
synchronized (list){
if(list.size() == 10){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add("产品" + i);
System.out.println(Thread.currentThread().getName() + "生产了" + list.get(list.size() - 1));
i++;
list.notify();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
编译结果如下: