复习多线程的基础总结

这几天复习多线程,考虑到只是容易遗忘,在这里记录一下复习过的多线程的一些知识点。

首先来了解一下理论上的知识:

一、首先,先来了解一下什么是线程。

1.线程是程序执行的一条路径,一个进程中可以包含多条线程

2.多线程并发执行可以提高程序的效率, 可以同时完成多项工作

二、那么知道什么是多线程后,怎样理解秉性和并发呢?

1、并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)

2、并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。

e.g. 比如我跟两个网友聊天,左手操作一个电脑跟甲聊,同时右手用另一台电脑跟乙聊天,这就叫并行。

e.g. 如果用一台电脑我先给甲发个消息,然后立刻再给乙发消息,然后再跟甲聊,再跟乙聊。这就叫并发。

三、Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程” ,然后主线程去调用某个类的main 方法。

当JVM启动之后,至少启动了垃圾回收线程和主线程,所以JVM的启动是多线程的。

一.如何创建一个线程:

创建线程一般有两种方法,第一种是直接创建一个类直接继承于Thread类。

1.      新建一个子类继承父类Thread类

2.      重写父类的run()方法

3.      把想要做的事写在重写的run()方法内

4.      创建一个新的线程对象

5.      开启线程,线程内部会自动执行run()方法,start()方法启动线程


下面给出一个例子:

Public class Demo1{
	Public static void main(String[] args){
		MyThread mt = new MyThread();				//4,创建自定义类的对象
		mt.start();					        //5,开启线程
		for(int i = 0; i < 50; i++) {
		System.out.println("bbbbbb");		
		}
	}
}
class MyThread extends Thread {						//1,定义类继承Thread
	public void run() {							//2,重写run方法
		for(int i = 0; i < 50; i++) {				//3,将要执行的代码,写在run方法中
			System.out.println("aaaaaaa");
		}
	}
}

结果如下:



第二种方法是实现Runnable接口

1.     首先定义一个类实现Runnable接口

2.     实现run方法

3.     把线程要做的事写在run方法中

4.     创建自定义的Runnable的子类对象

5.     创建一个Thread对象,传入Runnable

6.     调用start()方法开启新的线程,内部会自动地调用Runnable的run()方法

代码例子如下:

public static void main(String[] args) {
	MyRunnable mr = new MyRunnable();					//4,创建自定义类对象
	Thread t = new Thread(mr);						//5,将其当作参数传递给Thread的构造函数
	t.start();								//6,开启线程
					
	for(int i = 0; i < 3000; i++) {
		System.out.println("bbbbbb");
	}
}
		
class MyRunnable implements Runnable {					//1,自定义类实现Runnable接口
	@Override
	public void run() {						//2,重写run方法
		for(int i = 0; i < 3000; i++) {				//3,将要执行的代码,写在run方法中
			System.out.println("aaaaaaa");
		}
	}			
}

以上代码执行结果和上一个结果一样,相差不大的。

那么两种方法有什么区别呢?

1.第一种方法,代码简单,建立起来方便,还可以直接使用Thread中的方法,但是如果已经有了父类,就不能用这种方法,因为Java不支持多继承

2.第二种方法,即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的,但是不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

那么有时候,方便起见,我们可以不新建一个自定义的类,使用匿名内部类来创建一个新的线程

两种方法的代码如下:

第一种方法:

new Thread() {							//1,new 类(){}继承这个类
	public void run() {					//2,重写run方法
		for(int i = 0; i < 3000; i++) {		//3,将要执行的代码,写在run方法中
		      System.out.println("这是一个新的线程");
			}
		}
	}.start();

第二种方法:

new Thread(new Runnable() {					//1,new 接口(){}实现这个接口
	public void run() {					//2,重写run方法
		for(int i = 0; i < 3000; i++) {			//3,将要执行的代码,写在run方法中
		      System.out.println("这是一个新的线程");
			}
		}
	}).start();

两种方法都是可行的。

二、获取线程的名字

1.获取名字,通过getName()方法获取名字

2.设置名字,通过构造函数可以传入String类型的名字

(1)直接传参数进新建Thread类中

new Thread("xxx") {
			public void run() {
				System.out.println(this.getName() + ".....1");     //this表示当前进行的线程

			}
		}.start();


		new Thread("yyy") {
			public void run() {
				System.out.println(this.getName() + ".....2");    //this表示当前进行的线程
			}
		}.start();

结果如下:



(2)还可以使用setName(String)方法可以设置线程对象的名字

Thread t1 = new Thread() {
			public void run() {
				System.out.println(this.getName() + ".....1");
			}
		};

		Thread t2 = new Thread() {
			public void run() {
				System.out.println(this.getName() + ".....2");
			}
		};
		
		t1.setName("线程1");
		t2.setName("线程2");

		t1.start();
		t2.start();


结果如下:



两种方法都可以设置线程的名字

三、如何获取当前线程的对象

使用Thread.currentThread()方法,Thread中的currentThread()方法就能获取当前线程,包括获取主线程(main)。

代码例子:

new Thread(new Runnable() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					System.out.println(Thread.currentThread().getName() + ".....1");
				}
			}
		}).start();

		new Thread(new Runnable() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					System.out.println(Thread.currentThread().getName() + ".....2");
				}
			}
		}).start();
		Thread.currentThread().setName("我是主线程");					//获取主函数线程的引用,并改名字
		System.out.println(Thread.currentThread().getName());		//获取主函数线程的引用,并获取名字


结果如下:


四、设置休眠线程

设置休眠线程的方法是使用sleep()方法,而它的单位是毫秒。休眠线程的意思是,设置了休眠的线程执行了一次之后就会进入休眠状态,让出内存,等到sleep设置的时间到了之后就会被唤醒,重进进入就绪态。

代码例子:

new Thread() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					System.out.println(getName() + ".....1");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();

		new Thread() {
			public void run() {
				for(int i = 0; i < 10; i++) {
					System.out.println(getName() + ".....2");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();
结果如下:



Thread-0执行一次,然后休眠,让出内存给Thread-1,同样道理Thread-1运行了一次后也休眠,所以这和代码程序是如上图所示的一次Thread-0,一次Thread-1,然后一秒后,两个又执行一次直到结束。

五、守护线程

setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出,即是如果有线程1和线程2,设置1为守护线程,当2线程结束后,就算1还没执行完run中的事,也会跟着一起结束。

代码例子如下:

Thread t1 = new Thread() {
			public void run() {
				for(int i = 0; i < 50; i++) {
					System.out.println(getName() + ".....1");
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};

		Thread t2 = new Thread() {
			public void run() {
				for(int i = 0; i < 5; i++) {
					System.out.println(getName() + ".....2");
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};

		t1.setDaemon(true);						//将t1设置为守护线程

		t1.start();
		t2.start();

结果如下:

 

由于内存会有一个缓冲过程,所以Tread-1执行结束后,Thread-0没有马上停止,而是再几次的结果打印了出来。

六、加入线程

final Thread t1 = new Thread("Thread-1") {
			public void run() {
				for(int i = 0; i < 10; i++) {
					System.out.println(getName() + "....."+i);
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		};

		Thread t2 = new Thread("Thread-2") {
			public void run() {
				for(int i = 0; i < 10; i++) {
					if(i == 2) {
						try {
							//t1.join();				//插队,加入
							t1.join(30);				//加入,有固定的时间,过了固定时间,继续交替执行
							Thread.sleep(10);
						} catch (InterruptedException e) {

							e.printStackTrace();
						}
					}
					System.out.println(getName() + "....."+i);

				}
			}
		};

		t1.start();
		t2.start();

结果如下:

 

当i=2的时候,先不执行println的操作,而是加入先执行Thread-1

七、同步代码块:

加入有以下的代码:

final Object obj = new Object();
	Thread t1 = new Thread("我是线程one"){
		public void run(){
			while(true){                 
				System.out.print("线");                   
				System.out.print("程");
				System.out.print("测");
				System.out.println("试");
			}
		}
	};
	
	Thread t2 = new Thread("我是线程two"){
		public void run(){
			while(true){
				System.out.print("继");
				System.out.print("续");
				System.out.println("来");
			}
		}
	};
		
		t1.start();
		t2.start();

会出现以下这种结果:

 

有乱码出现的情况,例如,在线程1中执行到中间打印了“测试”,然后内存的执行权被线程2抢夺去,从而还没打印完就开始执行线程2,打印继续来,那么怎样解决这个问题呢?

答案是:可以使用同步代码块synchronized()方法。

代码如下:

final Object obj = new Object();
		Thread t1 = new Thread("我是线程one"){
			public void run(){
				while(true){
					synchronized (obj) {                      //将synchronized里面的代码变为同步代码块,
						System.out.print("线");                   //ps:几个线程的锁必须是一样的才不会出错
						System.out.print("程");
						System.out.print("测");
						System.out.println("试");
					}
				}
			}
		};

		Thread t2 = new Thread("我是线程two"){
			public void run(){
				while(true){
					synchronized(obj){
						System.out.print("继");
						System.out.print("续");
						System.out.println("来");
					}
				}
			}
		};

		t1.start();
		t2.start();
 

这样就不会出现乱码的情况了

 



1.什么情况下需要同步

* 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.

* 如果两段代码是同步的, 那么同一时间只能执行一段,在一段代码没执行结束之前, 不会执行另外一段代码.

* 2.同步代码块

* 使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块

* 多个同步代码块如果使用相同的锁对象, 那么他们就是同步的

以上例子中obj就是相当于一把锁,用synchronize把代码块使用obj锁上了,就不会出现乱码了。

还能使用用synchronize去修饰一个方法

使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的

class Printer {
			public static void print1() {
				synchronized(Printer.class){			//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
					System.out.print("线");
					System.out.print("程");
					System.out.print("测");
					System.out.print("试");
					System.out.print("\r\n");
				}
			}
			/*
			 * 非静态同步函数的锁是:this
			 * 静态的同步函数的锁是:字节码对象
			 */
			public static synchronized void print2() {	
				System.out.print("继");
				System.out.print("续");
				System.out.print("来");
				System.out.print("\r\n");
			}
		}

先总结到这里,还有很多内容我还没有深入学习,就先不总结了,溜了溜了。



猜你喜欢

转载自blog.csdn.net/m0_38012174/article/details/77195981