线程与进程

      入门级多线程  

       一般我们运行的程序都是进程级的,就是运行一个进程的意思,进程之下还有一群小弟叫线程,多个进程可以同时运作,多个线程也可以,但是一般我们只认识大哥,就是进程,所以用户一般不纠结这个,但是开发人员就不一样了,得知道,一个进程其实内部还有若干个线程,一般最少会有一个主线程,在java里对应的是main方法开启的main线程,这一段要强调的是,线程才是资源调度的最小单位,而不是进程。

 进程线程区别

1、进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)
2、进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
3、线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
4、但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

并行和并发

上面说到多进程多线程的同时运作,本质上是不对的,首先要了解到计算机的发展是从单核到多核,但是即使是单核时期,也能实现所谓的多任务同时进行,前提是我们使用的都是支持多任务的系统,当有运行多任务的请求时,cpu会分别分配给各个任务一段运行时,让各个任务交替进行,因为这个运行时很短,所以一般我们感觉不到,比如,让浏览器执行0.001秒,让QQ执行0.001秒,再让音乐播放器执行0.001秒,在人看来,CPU就是在同时执行多个任务。即使是多核CPU,因为通常任务的数量远远多于CPU的核数,所以任务也是交替执行的。顺便提一下,我们使用的windows,linux都是抢占式多任务系统。那么这就是并发。
并行就不一样了,并行是指多个任务,拥有独立的运行时,独立的CPU,互不干涉,是真正的同时进行,多核时代才能做到这种程度,但是一般来说这样太浪费资源,多数情况下我们使用的都是并发,常提到的java高并发。

开启新线程

有两种方式:

1、继承一个Thread类

public class Test{
	public static void main(String args[]){
		Thread thread = new Mythread();
		thread.start();
	}
}
public class MyThread extends Thread{
	public void run(){
		System.out.println("hello thread!");
	}
}

2、实现Runnable接口

public class Test{
	public static void main(String args[]){
		Thread thread = new Thread(new Mythread());
		thread.start();
	}
}
class MyThread implements Runnable{
	public void run(){
		System.out.println("hello thread!");
	}
}

注意两种方法都一定要有run(),因为这是java线程主要执行方法,类似于main,第一种方法可以使用父类Thread实现的一些方法,比如interrupt()等等,继承后要重写run(),第二种比较简易,就一个run(),如果你不用不上父类的方法,一般推荐使用Runnable,叫轻量级的线程,继承了Thread会略显冗余。还有不要忘了调用thread实例的start(),否则线程不运行!

线程状态

在Java程序中,一个线程对象只能调用一次start()方法启动新线程,并在新线程中执行run()方法。一旦run()方法执行完毕,线程就结束了。因此,Java线程的状态有以下几种:

New:新创建的线程,尚未执行;
Runnable:运行中的线程,正在执行run()方法的Java代码;
Blocked:运行中的线程,因为某些操作被阻塞而挂起(比如:锁);
Waiting:运行中的线程,因为某些操作在等待中;
Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
Terminated:线程已终止,因为run()方法执行完毕。

线程终止:

  • 运行完代码块的内容正常终止
  • 代码运行出错异常终止
  • 调用stop()函数强行终止(不建议)

守护线程

如果我们在main方法里开启了一个计时的线程,然后main方法结束了,但是还有这个计时线程,必须要等这个线程结束才能退出程序,或者线程出现死循环,必须中断,或者强制停止。解决方案就是使用守护线程。顾名思义,守护线程守护的是除了它自己以外的所有线程,如果除了它以外别的线程都结束了,jvm就不会等待守护线程,直接退出程序。可以这样设置:

Thread t = new MyThread();
t.setDaemon(true);
t.start();

线程同步

完成线程同步要使用到关键字 synchronized ,表示同步标记。线程同步是多线程最困难的部分。
当多线程之间需要同时读写共享变量时,会出现数据不一致的问题,这个时候就需要线程同步。通用的解决方案都是采用锁的方式,即一个线程通过一个共享实例获取变量前得到锁,别的线程无法获取这个变量的锁也无法解锁,等有锁的线程执行完相关操作后就会释放锁,这样其他线程就可以获取锁在执行相关的操作了,synchronized就是这样一个互斥锁,synchronized 用法如下:

synchronized(lockobject){
	...statement...
}
public class SynchronizedTest1 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Thread t1 = new Hi();
		Thread t2 = new Hel();
		t1.start();
		t2.start();
		System.out.println(Count.i);
	}
}
class Count{
	public static Object key1 = new Object();
	public static Object key2 = new Object();
	public static int i= 0;
}
class Hi extends Thread{
	
	public void run() {
		synchronized(Count.key1) {
			Count.i+=1000;
		}
	}
}
class Hel extends Thread{
	
	public void run() {
		synchronized(Count.key1) {
			Count.i-=1000;
		}
	}
}

这就是同步方法
上面的代码有一个问题,在同一个实例的同步方法里调用另一个同步方法,不会出错么,对就是不会,因为java的synchronized锁是一种可重入的锁,或者嵌套的锁,因为是被同一个线程获取的所有不会有问题,那么这里要注意的就是死锁的问题了
死锁的意思就是线程获取了一个锁后在获取另一个锁

public void set(int m) {
    synchronized(lockA) { // 获得lockA的锁
        this.value += m;
        synchronized(lockB) { // 获得lockB的锁
            this.another += m;
        } // 释放lockB的锁
    } // 释放lockA的锁
}

public void get(int m) {
    synchronized(lockB) { // 获得lockB的锁
        this.another -= m;
        synchronized(lockA) { // 获得lockA的锁
            this.value -= m;
        } // 释放lockA的锁
    } // 释放lockB的锁
}

线程1执行set获取锁lockA,这个时候线程2也启动了执行get获取锁lockB,线程1想要lockB的锁,线程2想要lockA的锁,这个时候就陷入死循环了,这就是死锁,要极力避免这种情况出现。
一般很少采用同步方法的模式,如果锁住了整个实例,其他想要使用实例但是不涉及共享变量或者使用不同共享变量的线程就必须等待了,性能自然会下降

notify&wait

synchronized解决了线程同步的资源竞争问题,接下来要解决的就是线程协作的问题了,比如,一个缓存队列,我们希望多个线程写,队列不为空时读

class TaskQueue {
    Queue<String> queue = new LinkedList<>();

    public synchronized void addTask(String s) {
        this.queue.add(s);
    }

    public synchronized String getTask() {
        while (queue.isEmpty()) {
        }
        return queue.remove();
    }
}

这样写似乎没有什么问题,仔细看看,当队列为空,while进入死循环时,getTask持有this锁,并不能让出,那么写线程也执行不了,所以最终会陷入死循环,使用notify和wait方法改进一下

class TaskQueue {
    Queue<String> queue = new LinkedList<>();

    public synchronized void addTask(String s) {
        this.queue.add(s);
        this.notify();
    }

    public synchronized String getTask() {
        while (queue.isEmpty()) {
        	this.wait();
        }
        return queue.remove();
    }
}

wait()的作用就是进入等待状态,notify()的作用就是通知一个线程可以准备唤醒了,注意两个方法都必须在同步代码块中使用,并且由锁对象调用,这里锁是this所以是this调用的方法,另外,wait()实际上会释放线程持有的锁对象的锁,并进入等待,直到收到notify()的通知唤醒后会重新获取锁,wait是由native的代码实现的也就是c实现,原理很复杂,我不懂…,还有notify是唤醒任意一个等待线程,如果有多个等待的线程,会随机唤醒一个,一般不这么用,一般用notifyAll(),唤醒所有的等待线程,应该是为了避免活锁,活锁就是在资源竞争中一直无法获取资源无法执行的状态。由于notify不会导致阻塞,并且就算你提前用了,那唤醒的线程还是得等当前线程执行完毕,所以有时候需要提前执行或者可以提前执行。
 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值