Java基础系列之多线程基础第一弹

什么是进程?

要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在。
通过任务管理器我们就看到了进程的存在。
而通过观察,我们发现只有运行的程序才会出现进程。
进程:就是正在运行的程序。
进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。

多进程有什么意义呢?

单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。
举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。
并且呢,可以提高CPU的使用率。

问题:一边玩游戏,一边听音乐是同时进行的吗?

不是。因为单CPU在某一个时间点上只能做一件事情。
而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。
cpu是通过调度算法来实现的。

什么是线程呢?

在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
线程:是程序的执行单元,执行路径。是程序使用CPU的最基本单位。
单线程:如果程序只有一条执行路径。
多线程:如果程序有多条执行路径。

多线程有什么意义呢?

多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。
程序的执行其实都是在抢CPU的资源,CPU的执行权。
多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权。
我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。

举个例子

就像一堆人抢一个馒头,每个人就是进程,而馒头就是CPU,你一个人抢的时候几率比较低,
于是你雇了几个人帮你一起抢,雇的人就是线程,使用率提高了,但是也不一定能保证你能抢到。

两个词汇:并行和并发

并行是指一个时间段内同时运行多个程序,逻辑上的概念。例如三点到五点你用电脑听着网易云打着dota
并发是指一个时间点上同时运行多个程序,物理上的概念。例如同时在一个点时间上访问同一块内存区域

Java程序的启动

java命令会启动java虚拟机,也就是JVM,等于启动了一个应用程序,类似迅雷,QQ这样的,也就是启动了一个进程,
而该进程会自动启动一个主线程,然后主线程去调用某个类的main方法。所以main方法运行在主线程中。
而jvm为了垃圾回收,还会启动另一个线程用于垃圾回收,所以至少有两个线程在程序运行中启动着,所以说java天生多线程。

Java实现多线程的方式

实现方式1:继承Thread类 Demo如下

    package xin.kingsman;
     /**
     * @author song 为什么要重写run方法? 不是类中所有的代码都要被线程执行。
     *         而这时为了去分那些代码能够被线程所执行,java提供了run方法来包含那些线程被执行的代码。
    */
    public class MyThread extends Thread {
    	MyThread() {
	}

	MyThread(String name) {
		super(name);//super方法必须先被执行,如果不写默认先执行super()
	}
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(this.getId()+"---"+this.getName()+"---"+i);
		}
	}
	public static void main(String[] args) {
		MyThread myThread1 = new MyThread();
		MyThread myThread2 = new MyThread();
		myThread1.start();
		myThread2.start();
		/*
		 * MyThread myThread1 = new MyThread();
		 * myThread1.start();
		 * myThread1.start();
		 * 
		 * Exception in thread "main" java.lang.IllegalThreadStateException at
		 * java.lang.Thread.start(Thread.java:705) at
		 * xin.kingsman.MyThread.main(MyThread.java:17)
		 * 
		 * 一个线程对象启动多次可能会报错,因为在Thread对象中 有private volatile int threadStatus = 0;这么一个变量
		 * Thread的start方法是同步方法,该方法中做的第一件事就是判断threadStatus的状态,如果不为0就抛出该异常,
		 * 否在然后才会调用native方法start0启动线程
		 */
		System.out.println("完毕");
	 }
    }
    
线程的默认命名是如何实现的?	
源码如下:
//源码中的名称的get方法
public final String getName() {
        return name;
    }
//无参构造
public Thread() {
   init(null, null, "Thread-" + nextThreadNum(), 0);
}
//无参构造调用的代码
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
}
//父类中修改线程的number,是个同步方法
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
        return threadInitNumber++;
}
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
    //省略了大部分代码
        this.name = name;

    }

实现方式2:实现Runable接口 Demo如下

package xin.kingsman;

public class MyRunable implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			//注意这里的不同,实现接口方式创建的线程对象只能通过thread类的静态方法取得当前线程后拿名字
			System.out.println(Thread.currentThread().getId() + "---" + Thread.currentThread().getName() + "---" + i);
		}
	}
	
	public static void main(String[] args) {
		Thread thread = new Thread(new MyRunable());
		thread.start();
	}

}

比较两种方式的区别

1.Java是单继承的,继承了别的类就不能继承Thread了,
继承Runable接口可以避免由于单继承带来的局限性
2.适合多个相同的程序代码去处理同一个资源的情况,把线程同程序的代码,
数据和业务逻辑有效的分离,较好的体现了面向对象的思想。

###返回当前方法正执行在那个线程中

Thread.currentThread()

线程的调度

第一种:分时调度模型,平均分配CPU的时间片
第二种:抢占式调度模型,优先让优先级比较高的线程使用CPU资源,如果优先级都一样那么会随机选择一个
Java使用的是抢占式调度模型

如何设置和获取线程的优先级呢?

package xin.kingsman;

public class ThreadPriority extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(this.getName() + "-->" + i);
		}
	}

	public static void main(String[] args) {
		ThreadPriority thread1 = new ThreadPriority();
		ThreadPriority thread2 = new ThreadPriority();
		ThreadPriority thread3 = new ThreadPriority();
		thread1.setPriority(10);
		thread1.setName("线程1");
		System.out.println(thread1.getPriority());
		thread2.setName("线程2");
		System.out.println(thread2.getPriority());
		thread3.setName("线程3");
		System.out.println(thread3.getPriority());

		thread1.start();
		thread2.start();
		thread3.start();
		// 以上的线程没有设置优先级,他会有一个默认的优先级,
		// 通过 thread1.getPriority() 获取,默认优先级是5
		/*
		 * 我可以通过setPriority()设置优先级
		 * 最低值为
		 * public final static int MIN_PRIORITY = 1;
		 * 默认值为5
		 * public final static int NORM_PRIORITY = 5;
		 * 最大值为10
		 * public final static int MAX_PRIORITY = 10;
		 */
	}
}

线程类的一些方法

优先级相关的方法

上面已经讲过此处不再赘述

线程休眠 sleep()

package xin.kingsman;

public class ThreadSleep extends Thread {
	public ThreadSleep(String name) {
		super(name);
	}

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.getName() + "-->" + i);
			try {
				ThreadSleep.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
		ThreadSleep threadSleep1 = new ThreadSleep("songhao");
		ThreadSleep threadSleep2 = new ThreadSleep("huangqian");
		threadSleep1.start();
		threadSleep2.start();
	}
}

线程加入 join()

等待该线程终止
package xin.kingsman.demo;
import xin.kingsman.ThreadSleep;
public class ThreadDemo extends Thread {
	public ThreadDemo(String name) {
		super(name);
	}
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.getName() + "-->" + i);
			try {
				ThreadSleep.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		System.out.println("主线程开始...");
		ThreadDemo threadSleep1 = new ThreadDemo("songhao");
		ThreadDemo threadSleep2 = new ThreadDemo("huangqian");
		threadSleep1.start();
		threadSleep1.join();//等待该线程运行结束,别的线程才开始运行
		threadSleep2.start();
		threadSleep2.join();//等待该线程运行结束,别的线程才开始运行
		System.out.println("主线程结束...");
	}
}

线程礼让 yield()

package xin.kingsman;

public class ThreadYield extends Thread {
	public ThreadYield(String name) {
		super(name);
	}

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.getName() + "-->" + i);
			Thread.yield();//注意是在这里谦让的
		}
	}

	public static void main(String[] args) {
		ThreadYield threadSleep1 = new ThreadYield("songhao");
		ThreadYield threadSleep2 = new ThreadYield("huangqian");
		threadSleep1.start();
		threadSleep2.start();
	}
}
输出如下:
songhao-->0
huangqian-->0
songhao-->1
huangqian-->1
songhao-->2
huangqian-->2
songhao-->3
huangqian-->3
songhao-->4
huangqian-->4
songhao-->5
huangqian-->5
songhao-->6
huangqian-->6
songhao-->7
huangqian-->7
songhao-->8
songhao-->9
huangqian-->8
huangqian-->9

让多个线程的执行更加和谐,但是并不能保重你一次我一次

后台线程(守护线程) setDaemon()

package xin.kingsman;

public class ThreadDaemon extends Thread {
	public ThreadDaemon(String name) {
		super(name);
	}

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

	public static void main(String[] args) {
		ThreadDaemon threadSleep1 = new ThreadDaemon("songhao");
		ThreadDaemon threadSleep2 = new ThreadDaemon("huangqian");
		//在线程启动前将其设为守护线程
		threadSleep1.setDaemon(true);
		threadSleep2.setDaemon(true);
		threadSleep1.start();
		threadSleep2.start();
		for (int i = 0; i <3; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}
输出为:
songhao-->0
main:0
huangqian-->0
main:1
main:2  //这一刻主线程执行完毕,但是可以看到后面守护线程仍然跑了一段,所以并不是马上守护线程就被杀死了
songhao-->1
huangqian-->1
songhao-->2
huangqian-->2
songhao-->3
huangqian-->3
songhao-->4
songhao-->5
songhao-->6
songhao-->7
songhao-->8
songhao-->9

中断线程 stop() 与 interrupt()

package xin.kingsman;

public class ThreadStop extends Thread {
	public ThreadStop(String name) {
		super(name);
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(this.getName() + "-->" + i);
		}
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("interrup方法会抛出InterruptedException异常所以会执行这里");
		}
		System.out.println("执行完毕");
	}

	public static void main(String[] args) {
		ThreadStop ThreadStop = new ThreadStop("songhao");
		ThreadStop.start();
		//这个循环执行完就干掉 ThreadStop
		for (int i = 0; i <3; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
		//ThreadStop.stop();
		ThreadStop.interrupt();
	}
}

stop方法输出的结果如下
main:0
songhao-->0
main:1
songhao-->1
main:2
songhao-->2
songhao-->3
songhao-->4
songhao-->5


interrupt方法输出的结果如下
main:0
songhao-->0
songhao-->1
songhao-->2
songhao-->3
main:1
songhao-->4
main:2
songhao-->5
songhao-->6
songhao-->7
songhao-->8
songhao-->9
songhao-->10
songhao-->11
songhao-->12
songhao-->13
songhao-->14
songhao-->15
songhao-->16
songhao-->17
songhao-->18
songhao-->19
songhao-->20
songhao-->21
songhao-->22
songhao-->23
songhao-->24
songhao-->25
songhao-->26
songhao-->27
songhao-->28
songhao-->29
songhao-->30
songhao-->31
songhao-->32
songhao-->33
songhao-->34
songhao-->35
songhao-->36
songhao-->37
songhao-->38
songhao-->39
songhao-->40
songhao-->41
songhao-->42
songhao-->43
songhao-->44
songhao-->45
songhao-->46
songhao-->47
songhao-->48
songhao-->49
songhao-->50
songhao-->51
songhao-->52
songhao-->53
songhao-->54
songhao-->55
songhao-->56
songhao-->57
songhao-->58
songhao-->59
songhao-->60
songhao-->61
songhao-->62
songhao-->63
songhao-->64
songhao-->65
songhao-->66
songhao-->67
songhao-->68
songhao-->69
songhao-->70
songhao-->71
songhao-->72
songhao-->73
songhao-->74
songhao-->75
songhao-->76
songhao-->77
songhao-->78
songhao-->79
songhao-->80
songhao-->81
songhao-->82
songhao-->83
songhao-->84
songhao-->85
songhao-->86
songhao-->87
songhao-->88
songhao-->89
songhao-->90
songhao-->91
songhao-->92
songhao-->93
songhao-->94
songhao-->95
songhao-->96
songhao-->97
songhao-->98
songhao-->99
interrup方法会抛出InterruptedException异常所以会执行这里
执行完毕
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at xin.kingsman.ThreadStop.run(ThreadStop.java:14)

Object的线程相关的方法

wait() 和 notify()

线程的生命周期

1. 新建:创建线程对象
2. 就绪:有执行资格,没有执行权  
3. 运行:有执行资格,有执行权
		3.1. 阻塞:由于一些操作让线程处于该状态。没有执行资格,没有执行权
		          而另一些操作却可以把它激活,激活后处于就绪状态 
4. 死亡:线程对象变成垃圾,等待被回收
如下图:
![Java多线程运行模型](https://img-blog.csdnimg.cn/20181125214342297.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01pY2tleVRhS2lnaW4=,size_16,color_FFFFFF,t_70)

程序练习

程序题目以及一段不成熟的代码

某电影院正在上映贺岁大片,有100张票,同时有三个窗口在售票,请设计一个程序模拟该电影院售票
package xin.kingsman;

public class MyTicketRunable implements Runnable {

	private int nums =100;
	@Override
	public void run() {
		while(nums>0) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(nums--);
		}
	}
	
	public static void main(String[] args) {
		MyTicketRunable myTicketRunable = new MyTicketRunable();
		Thread t1 = new Thread(myTicketRunable);
		Thread t2 = new Thread(myTicketRunable);
		Thread t3 = new Thread(myTicketRunable);
		t1.start();
		t2.start();
		t3.start();
	}

}
输出结果如下:
100
100
99
98
97
96
95
94
93
92
91
90
89
88
87
86
85
84
83
82
81
80
79
78
77
76
75
74
73
72
71
70
69
69
68
67
66
65
64
63
62
61
61
60
59
58
57
56
56
55
54
53
52
51
50
49
48
47
46
45
44
43
42
41
40
39
39
38
37
36
35
34
33
32
31
30
29
28
27
26
24
25
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
2
1
0
-1
观察以上结果可知有同一张票被卖了两次,还有超卖现象发生
卖两次是因为:底层CPU的操作是原子性的
超卖变成了-1:随机性和延迟导致的

解决不成熟代码的问题

如何解决线程安全问题呢?
想要知道如何解决问题,那么就要知道那些原因会导致问题:
1.是否多线程环境
2.是否有共享数据
3.是否有多条语句操作共享数据
幸运的是我们上面的程序这些问题都包含了
解决问题:1、2我们都是改变不了的,我们只有从语句上入手,我么可以将这些操作共享数据的多条代码包成一个代码块,使一个线程在执行时,别的线程只能等待。而java为我们提供了线程的同步机制。
 同步代码块:
    synchronized(对象){
   	 需要同步的代码块;
    }
   问题1:对象是什么?
   答:我们先new Object(); 这个对象可以使任意对象
   问题2:需要同步的代码块是那些?
   答:多条语句操作共享数据的代码
设置同步代码块的对象时要注意,所有的线程必须是同一个对象(同一把锁),只要是同一个对象无论代码在哪里都会同步,因为一个对象就是一把锁 
package xin.kingsman;

public class MyTicketRunable implements Runnable {

	private int nums = 100;
	private Object obj = new Object();

	@Override
	public void run() {

		while (true) {
			synchronized (obj) {
				if (nums > 0) {//注意这里和上面的变化
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "--->" + nums--);
				}

			}

		}
	}

	public static void main(String[] args) {
		MyTicketRunable myTicketRunable = new MyTicketRunable();
		Thread t1 = new Thread(myTicketRunable);
		Thread t2 = new Thread(myTicketRunable);
		Thread t3 = new Thread(myTicketRunable);
		t1.start();
		t2.start();
		t3.start();
	}

}
同步会提高安全性但是同时也降低了执行效率。

同步方法的锁对象是谁?

第一种情况:当synchronized加在非静态方法上的时候

下面两段代码
第一段:
package xin.kingsman;

public class MyTicketRunable implements Runnable {

	private int nums = 100;
	private Object obj = new Object();

	@Override
	public void run() {

		while (true) {
			synchronized (this) {//注意这里和上面的变化
				if (nums > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "--->" + nums--);
				}

			}

		}
	}

	public static void main(String[] args) {
		MyTicketRunable myTicketRunable = new MyTicketRunable();
		Thread t1 = new Thread(myTicketRunable);
		Thread t2 = new Thread(myTicketRunable);
		Thread t3 = new Thread(myTicketRunable);
		t1.start();
		t2.start();
		t3.start();
	}

}
第二段:
package xin.kingsman;

public class MyTicketRunable implements Runnable {

	private int nums = 100;
	private Object obj = new Object();

	@Override
	public void run() {

		while (true) {
			doit();
		}
	}
    //synchronized加载方法上,它的同步锁对象是this,也就是main方法中实例化的myTicketRunable
	private synchronized void doit() {
		if (nums > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "--->" + nums--);
		}
	}

	public static void main(String[] args) {
		MyTicketRunable myTicketRunable = new MyTicketRunable();
		Thread t1 = new Thread(myTicketRunable);
		Thread t2 = new Thread(myTicketRunable);
		Thread t3 = new Thread(myTicketRunable);
		t1.start();
		t2.start();
		t3.start();
	}

}

第二种情况:当synchronized加在静态方法上的时候

还是两段等价的代码
第一段:
package xin.kingsman;

public class MyTicketRunable implements Runnable {

	private static int nums = 100;
	private Object obj = new Object();

	@Override
	public void run() {

		while (true) {
			doit();
		}
	}

	private static synchronized void doit() {
		if (nums > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "--->" + nums--);
		}
	}

	public static void main(String[] args) {
		MyTicketRunable myTicketRunable = new MyTicketRunable();
		Thread t1 = new Thread(myTicketRunable);
		Thread t2 = new Thread(myTicketRunable);
		Thread t3 = new Thread(myTicketRunable);
		t1.start();
		t2.start();
		t3.start();
	}

}
第二段:
package xin.kingsman;

public class MyTicketRunable implements Runnable {

	private static int nums = 100;
	private Object obj = new Object();

	@Override
	public void run() {

		while (true) {
			synchronized (this.getClass()) {//注意这里和上面的变化
				if (nums > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "--->" + nums--);
				}

			}
			
		}
	}

	private static synchronized void doit() {
		if (nums > 0) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "--->" + nums--);
		}
	}

	public static void main(String[] args) {
		MyTicketRunable myTicketRunable = new MyTicketRunable();
		Thread t1 = new Thread(myTicketRunable);
		Thread t2 = new Thread(myTicketRunable);
		Thread t3 = new Thread(myTicketRunable);
		t1.start();
		t2.start();
		t3.start();
	}

}
当synchronized加在静态方法上的时候,它的锁对象是该类的class对象

回顾之前学过的线程安全的类

stringbuffer,vector,hashtable
即时需要线程安全我也不会使用vector,Java为我们提供了一个collections的工具类,可以帮助我们吧非线程安全的集合类转成安全的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值