这几天复习多线程,考虑到只是容易遗忘,在这里记录一下复习过的多线程的一些知识点。
首先来了解一下理论上的知识:
一、首先,先来了解一下什么是线程。
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");
}
}
先总结到这里,还有很多内容我还没有深入学习,就先不总结了,溜了溜了。