进程:就是应用程序的执行实例。进程的特性: 1、动态性,进程动态产生,动态消亡。2、并发性。3、独立性。
可以作为一个进程内部的执行单元,并完成一个独立的任务,这样的顺序控制流程我们称为线程。如果在一个进程中同时运行了多个线程,用来完成不同的工作,我们就成为多线程。
主线程就是一个进程的入口。所有进程至少包含一个线程,那就是主线程。
线程的特点:1、一个进程可以包含多个线程,而一个线程至少要有一个父进程。2、线程可以有自己的堆栈、程序计数器和局部变量。3、线程与父进程的其他线程共享进程所有的全部资源。4、独立运行,采用抢占方式。5、一个线程可以创建和删除另一个线程。6、同一个进程中的多个线程之间可以并发执行。7、线程的调度和统一管理是由进程来完成的。
注意:编程时,必须确保线程不会妨碍同一进程的其他线程。
线程分类:1、系统级线程,即核心级线程。它负责管理不同进程之间的多个线程,由操作系统直接管理,把他们按照同样的相对优先调度的方法进行调度。并且充分利用计算机的硬件资源。2、用户级线程,这类线程仅仅存在于用户空间当中,在应用程序中控制其创建、执行和消亡,用户级线程的调度算法和调度过程全部由用户自己决定。
多线程开发的优势:1、改善用户体验。2、提高资源利用率。
线程的使用步骤:1、定义一个线程。2、创建线程的实例。3、启动线程。4、终止线程。
一个进程中至少包含一个线程,即主线程。他是在程序开始运行的时候创建的。
java程序中的main方法就是主线程的入口。
java语言中要创建的所有的线程都是java.lang.Thread类或其子类的一个实例。
java中定义一个线程类通常有两种方法:1、继承java.lang.Thread类。(1、定义Thread的子类,重写父类的run()方法。2、创建Thread子类的对象。3、调用start()方法启动该线程。)2、实现java.lang.Runnable接口(1、定义Runnable接口的实现类。2、用new Thread(Runnable)的方式创建线程对象。3、调用star()方法启动该线程。)。
1、继承Thread类
public class MyThread extends Thread{
private int count = 1;
@Override
public void run() {
while(count<100)
count++;
System.out.println("MyThread执行count结果为:"+count);
}
}
public class MyThreadDemo {
public static void main(String[] args){
MyThread mt = new MyThread();
mt.start();
System.out.println("主方法执行完毕!");
}
}
2、实现Runnable接口
public class MyRunnable implements Runnable{
private int count = 1;
@Override
public void run() {
while(count<10)
count +=count;
System.out.println("MyRunnable运行结果为:"+count);
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
System.out.println("主线程运行结束!");
}
}
线程总是由操作系统来占有和管理的,一个新的线程,他只能由系统来创建和启动,当我们调用Thread对象的start方法的时候,他会调用一个本地代码,就会使操作系统初始化一个新的线程,由这个新的线程来执行Thread对象的run方法。
继承Thread类和实现Runnable接口比较:
| 优点 | 缺点 |
继承Thread类 | 1、编写简单 2、可以使用this关键字直接访问当前线程 | 无法继承其他的类 |
实现Runnable接口 | 1、可以继承其他类 2、多个线程之间可以使用同一个Runnable对象 | 编写方式稍微复杂,如需访问当前线程,则需要调用Thread类的currentThread()方法。 |
一个线程的生命周期中,可以分为四个阶段:新生(new)状态、可运行(Runnable)状态、阻塞(Watting/blocking)状态、死亡(Dead)状态。
造成阻塞的原因:1、当我们调用了Thread类的静态方法sleep()方法,当调用这个方法的时候,当前正在执行的线程就会马上进入阻塞状态,并且主动的放弃所占用的处理器资源。2、如果一个线程需要用到一个读写操作的结果,而读写操作尚未完成,这时线程将会阻塞。3、如果一个线程的执行需要得到一个对象的锁,而这个对象的锁正被别的线程占用,那么这个线程也将被阻塞。他需要等待这个对象的锁被其他的线程释放以后,他才能继续执行。4、当一个线程对象执行了suspend方法而被挂起时,这个线程也会导致阻塞。suspend方法容易导致死锁,已被JDK列为过期方法。
当一个线程的run方法运行完毕以后,或者在运行过程中出现没有被捕获的异常时,线程就会进入死亡状态。处于死亡状态的线程可能不会马上释放占用的内存资源,同时这个时候他也不是一个独立的可运行单元。也就是说一个线程只要运行异常,当线程死亡后不能调用它的start()方法让他重新执行。
线程的优先级:1、默认情况下,一个线程继承其父类的优先级。2、优先级表示为一个整数值。3、优先级越高,执行的机会越大,反之,执行的机会就越小。4、高优先级的线程可以抢占低优先级线程的CPU资源。
注意:线程的优先级与线程执行的效率没有必然联系。
java中可以使用setPriority()方法改变线程的优先级。例如将线程myThread的优先级改为3(myThread.setPriority(3);)。
注意:1、线程的优先级的范围可能根据操作系统的不同而不同。2、通常情况下,优先级的值最大为10,最小为1,默认为5.
线程调度方法:1、join()方法,作用:阻塞指定的线程等到另一个线程完成后在继续执行。2、sleep()方法,他是定义的static方法,可以通过类名.sleep直接调用,作用:可以让当前正在运行的线程在指定的毫秒内停止执行并转入被阻塞的状态。3、yield方法是另一个可以让当前线程停止执行的方法,他也是Thread类中定义的静态方法。但这个方法不是让当前线程进入阻塞的状态,而是转入可运行状态。即,调用这个方法后,让当前线程转入可运行之后,当前的线程呢他仍然可以与其他的等待执行的线程竞争处理器资源,调用这个方法后,这个线程被暂时停止运行,只有优先级与当前线程相同,或者优先级高于当前线程,才可能获得执行机会。4、setDaemon()方法,通过给setDaemon方法传递一个boolean型的参数,就可以指定一个线程,是否为后台线程。后台线程也称守护线程,(只能在线程启动之前将此线程设为后台线程,而不能在启动之后)
sleep()方法与yield()方法比较:1、sleep()方法使当前线程转入被阻塞状态,而yield()方法使当前线程转入可运行状态。2、sleep()方法总是强制当前线程停止执行,而yield()方法不一定。3、sleep()方法可以使其他等待运行的线程有同样的执行的机会,而yield()方法只使相同或者更高优先级的线程获得执行机会。4、使用sleep()方法时需要捕捉异常,而yield()方法无需捕获异常。
1、join方法
public class JoinTest extends Thread {
public JoinTest(String name){
super(name);
}
@Override
public void run() {
for(int i = 0;i<5;i++){
System.out.println(this.getName()+"运行第"+i+"次");
}
}
public static void main(String[] args) {
for(int j = 0;j<10;j++){
if(j==5){
JoinTest jt = new JoinTest("新加入的进程!");
jt.start();
try {
jt.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"运行第"+j+"次");
}
}
}
运行结果:
main运行第0次
main运行第1次
main运行第2次
main运行第3次
main运行第4次
新加入的进程!运行第0次
新加入的进程!运行第1次
新加入的进程!运行第2次
新加入的进程!运行第3次
新加入的进程!运行第4次
main运行第5次
main运行第6次
main运行第7次
main运行第8次
main运行第9次
2、sleep方法
public class SleepDemo {
public static void main(String[] args) {
for(int i = 0;i<5;i++){
try {
System.out.println("线程睡眠第"+(i+1)+"秒");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
运行结果:
线程睡眠第1秒
0
线程睡眠第2秒
1
线程睡眠第3秒
2
线程睡眠第4秒
3
线程睡眠第5秒
4
3、yield方法
public class YieldDemo {
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
t1.start();
t2.start();
}
}
class Thread1 extends Thread{
@Override
public void run() {
for(int i = 0;i<10;i++){
System.out.println("第一个线程运行第"+i+"次");
this.yield();
}
}
}
class Thread2 extends Thread{
@Override
public void run() {
for(int j = 0;j<10;j++){
System.out.println("第二个线程运行第"+j+"次");
}
}
}
运行结果:
第一个线程运行第0次
第一个线程运行第1次
第一个线程运行第2次
第二个线程运行第0次
第二个线程运行第1次
第二个线程运行第2次
第二个线程运行第3次
第二个线程运行第4次
第一个线程运行第3次
第二个线程运行第5次
第二个线程运行第6次
第二个线程运行第7次
第二个线程运行第8次
第二个线程运行第9次
第一个线程运行第4次
第一个线程运行第5次
第一个线程运行第6次
第一个线程运行第7次
第一个线程运行第8次
第一个线程运行第9次
4、setDaemon()方法
public class DaemonTest extends Thread{
public void run(){
while(true){
System.out.println(getName());
}
}
public static void main(String[] args) {
DaemonTest dt =new DaemonTest();
dt.setDaemon(true);
dt.setName("后台线程");
dt.start();
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
java语言中,线程之间的同步是用锁来实现的。java语言中,每个对象都有一个内置锁。只有在这个对象中存在着同步代码块的时候,这个锁才会发生它的作用。当我们执行一个非静态同步方法的时候,我们就自动获得当前对象的锁,而且这个锁只能被一个同步方法或同步代码块获得。
同步方法:1、同步方法。可以使这个类的同一个对象,在这些方法上面他们是互斥的,这样就可以保证在任何时刻只有一个线程可以执行该对象中被同步的方法,也就可以保证在任何时刻只有一个线程可以通过这段代码去操作共享资源,而其他的线程此时则不能够访问这个对象中的被同步的方法。设置同步方法:只需要在声明方法的时候,在返回类型的前面加上synchronized关键字就行了。
表示当一个线程已经在执行此方法的时候,这个线程就得到了当前对象的对象锁,其他的线程就不能在获得这个锁了。2、同步代码块。这种方式可以指定那几行代码需要在那个对象上进行同步,也就是说他可以指定程序要执行这部分被同步的代码。语法:synchronized(指定要收益的对象){同步语句块}。
注意:1、两个线程不能同时访问同一个对象中标注为同步的方法。2、同步一个方法对在同一个对象当中是有作用的,但是如果在不同对象中虽然它们是同一个类,但是同一个类的不同对象,就起不到同步的作用。
同步方法和同步代码块的区别:1、同步方法,是通过this关键字找到当前的对象,将当前对象上锁。同步代码块,可以指定任意一个对象。2、同步代码块可以限制更具体。
例如:同步方法
public class SharedData{
private char c;
private boolean isProduced = false; // 信号量
// 同步方法putShareChar()
public synchronized void putShareChar(char c) {
// 如果产品还未消费,则生产者等待
if (isProduced) {
try{
System.out.println("消费者还未消费,因此生产者停止生产");
wait(); // 生产者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.c = c;
isProduced = true; // 标记已经生产
notify(); // 通知消费者已经生产,可以消费
System.out.println("生产了产品" + c + " 通知消费者消费...");
}
// 同步方法getShareChar()
public synchronized char getShareChar() {
// 如果产品还未生产,则消费者等待
if (!isProduced){
try{
System.out.println("生产者还未生产,因此消费者停止消费");
wait(); // 消费者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isProduced = false; // 标记已经消费
notify(); // 通知需要生产
System.out.println("消费者消费了产品" + c + " 通知生产者生产...");
return this.c;
}
}
//生产者线程
public class Producer extends Thread {
private SharedData s;
Producer(SharedData s){
this.s = s;
}
public void run(){
for (char ch = 'A'; ch <= 'D'; ch++){
try{
Thread.sleep((int) (Math.random() * 3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
s.putShareChar(ch); // 将产品放入仓库
}
}
}
//消费者线程
class Consumer extends Thread {
private SharedData s;
Consumer(SharedData s){
this.s = s;
}
public void run(){
char ch;
do {
try {
Thread.sleep((int) (Math.random() * 3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
ch = s.getShareChar(); // 从仓库中取出产品
} while (ch != 'D');
}
}
//测试类
class CommunicationDemo{
public static void main(String[] args){
//共享同一个共享资源
SharedData s = new SharedData();
//消费者线程
new Consumer(s).start();
//生产者线程
new Producer(s).start();
}
}
死锁:就是当两个线程相互等待对方释放它们各自拥有的对象的锁的时候就会发生死锁。当程序中发生死锁的情况的时候,程序不会给我们任何提示或者抛出任何异常。但是这时所有的线程均处于阻塞的状态。
线程之间的通信的实现:1、wait()方法。2、notify()方法。3、notifyAll()方法。这三个方法都只能在同步方法或者同步代码块中使用,否则编译器将抛出异常。
wait()方法:调用该方法后,会挂起当前这个线程,并且释放共享资源的锁。
notify()方法:调用该方法可以唤醒因为调用wait方法而被挂起的那个线程。