线程和进程

单CPU只能同时运行单个进程,多CPU可以同时运行多个进程。

一个进程可以包括多个线程。

一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。

一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。

"互斥锁"–Mutex,防止两个线程同时读写某一块内存区域。

"信号量"(Semaphore),用来保证多个线程不会互相冲突。

 

多线程的知识这里没有深入讲解,想要对多线程有更加清楚的概念需要反复钻研。

Thread和Runnable

使用Thread时,一般新建一个类来继承Thread类重写run方法,

在运行多线程程序的时候,记得打开任务管理器,设置IDE的相关性为单cpu,一旦重启IDE则需再次设置。

举个例子

MyThread.java

public class MyThread extends Thread {
	public void run() {
		for(int i = 0;i<10;i++) {
			System.out.println(Thread.currentThread().getName()+"---->"+i);
		}
	}
}

ThreadTest.java

public class ThreadTest {
	public static void main(String[] args) {
		for(int i = 0;i<10;i++) {
			System.out.println(Thread.currentThread().getName()+"---->"+i);
		}
		//新建一个线程
		MyThread myThread=new MyThread();
		//启动该线程而不是调用该线程的run方法
		//就绪态:cpu什么时候分配资源给这个线程,这个线程就什么时候运行run方法
		myThread.start();
		//myThread.run()
		//如果使用这个则不会创建线程,只是普通的调用类中的方法。
		//main方法也相当于一个线程,叫做主线程main,其他线程按创建的顺序分别叫Thread0,Thread1...
		//主线程一旦结束,整个程序都会结束
		//在运行多线程程序的时候,记得打开任务管理器,设置IDE的相关性为单cpu,一旦重启IDE则需再次设置。
	}
}

使用runable接口是因为Thread是一个类,而java是单根继承,如果一个类继承了Thread,那么就不能在继承其他 类,这个时候就可以使用Runnable接口,因为java支持多实现。一般编写多线程程序时,更推荐使用Runnable接口

举个例子

MyRunnable.java

public class MyRunnable implements Runnable{

	@Override
	public void run() {
		for(int i = 0;i<10;i++) {
			System.out.println(Thread.currentThread().getName()+"---->"+i);
		}
	}
}

RunnableTest.java

public class RunnableTest {
	public static void main(String[] args) {
		for(int i = 0;i<10;i++) {
			System.out.println(Thread.currentThread().getName()+"---->"+i);
		}
	//创建一个M有Runnable对象
	MyRunnable myRunnable=new MyRunnable();
	//将这个对象封装成Thread
	Thread thread = new Thread(myRunnable);
	//启动线程等待分配资源
	thread.start();
	//thread.run();
	//使用后者不会创建线程,记住要创建线程一定要使用start()方法
	//一般不要重写start()方法
	}
}

想要启动一个线程要调用start()方法,而不是调用用run()方法。

一般不要重写start()方法。

不要对一个线程多次start(),会报错。

线程的生命周期

新建->就绪->运行->死亡,运行时的线程可能出现堵塞,线程一旦堵塞必须重新进入就绪态才能够再次运行,不能直接运行。

堵塞有三种方式:

1.位于对象等待池的堵塞状态,线程处于运行状态,某个使用了wait()方法,则jvm会将线程放到对象等待池中。

2.位于对象锁池的堵塞状态,线程处于运行状态,该线程想要获取其他线程的同步锁,但其他线程占用了该同步锁,则jvm会将该线程方到锁池,等到这个同步锁不再被占用则位于堵塞的线程可以重新回到就绪态等待运行。

3.当前线程使用了sleep()方法,或者调用了其他线程的join()方法,或者发出了IO请求

线程的调度

线程有两种调度模型,分时调度和抢占式调度,jvm使用抢占式调度模型。如果不给线程设置优先级,则处于就绪态的 线程抢占使用cpu分配的资源,也就是线程的顺序是随机的。

 

线程的优先级,位于就绪态的线程会根据优先级放在可运行池中,优先级高的线程获得较高的运行机会,反之,则 获得较少的运行机会。

优先级用整数表示,范围是0-10之间,在Thread中有三个静态常量

MAX_PRIORITY:10  最高优先级

NORM_PRIORITY:5  默认优先级

MIN_PRIORITY:1   最低优先级

使用方法如下

MyThread mt1=new MyThread();

mt1.setPriority(Thread.Max_Priority);


后台线程:设置后台线程使用setDaemon(true),注意设置后台线程要在线程启动之前,否则异常。

前台线程创建的线程默认是前台线程,后台线程创建的线程默认是后台线程。

当所有的前台线程结束后,后台线程也会结束。

线程睡眠,调用sleep()方法,参数为毫秒。

 

线程让步:当线程在运行时,使用了Thread的静态方法yeild(),如果此时有和该线程优先级相同或者优先级比 该线程高的处于就绪态线程,则yeild()方法会把当前运行的线程放到可运行池中(就绪态),并使另一个线程运行, 如果没有和该线程优先级相同或者优先级比该线程高的线程,则yeild()方法什么都不做。

值得注意的是,让步不代表让别的线程运行结束,而是cpu分配的一次时间片,时间片结束后,cpu会重新分配资源。

 

等待其他线程结束:join()方法

当前运行的线程可以调用另一个处于就绪态线程的join()方法,当前运行的线程转到堵塞态,直到另一个线程运行 结束,该线程才可以再次转到就绪态。

线程同步

即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,

其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,是为了保证一个线程对数据的操作能够完整的进行,而不是中途让步给其他线程,从而使数据不能及时更新而出现问题。

线程同步的方法:使用同步锁即scychornized方法或者scychornized程序块,保证方法是原子性不可再分,即使使用yeild()方法也不会放其他线程运行。

使用方法 public scychornized void run(){}或者public void run(){scychornized (){}}


持有同步锁的线程在一下情况会释放同步锁

执行完同步代码块,也就是scychornized (this){}大括号中的内容

执行同步代码块时遇到异常终止

执行同步代码块时,执行了锁所属对象的wait()方法

值得注意的是,执行sleep()和yeild()都不会释放同步锁

线程通信

线程的通信是多线程中比较难理解的部分,需要反复钻研。这里没有深入讲解。

wait()方法:执行该方法的线程释放所属对象的同步锁,jvm将该线程放到等待池中(堵塞)等待其他线程将其 唤醒。

notify()方法:执行该方法的线程将唤醒一个在等待池的线程(堵塞),jvm从可运行池(就绪)随机选取一个线程,把 它放到对象的锁池中。

notifyAll()方法:执行该方法的线程会唤醒等待池(堵塞)的所有线程。

加油

java的学习记录分享差不多到这里已经结束了,虽然还有套接字socket编程和AWT控件以及其中最核心的内部类 以及设计模式中提到的适配器的使用没有分享。后续会进行js的学习记录分享。目前的方向是javaweb这一块的, 如果对没有分享的部分有兴趣的话,可以自行搜索,分享的目的其实也是为了督促自己学习,希望不辜负自己吧。 虽然java学习记录分享结束了,后续还是会有和java相关的内容发布,内容较学习记录来说应该更加多。共勉。