这是小卡斯蒂的第一篇学习博客。其实这些天来完全忘记了要在这发学习博客的任务呢。 虽然多线程早学完了,但是忘了好多,所以写篇博客用来巩固一下。 浅用下markdown(不是很会用)
多线程
一共有四种方式创建线程:jdk5.0后又添加了2种、
重点: 1.线程的创建与使用
2.线程的同步
3.jdk5.0新增线程创建方式
程序,进程,线程
程序:一段静态代码
进程:正在运行的一个程序。有自身的产生,存在和消亡的过程-称之为生命周期。是动态的
线程:进程可进一步细化为线程,是程序内部的一条执行路径
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程都有独立的运行栈(图1.1中的虚拟机栈)和程序计数器(图1.1有所体现),方法区和堆则是一份进程一份(图1.1也有所体现),线程转换的开销小
一个进程可能包含着多个线程,那么这些线程就会共享相同的内存空间
内存结构:图1.1
多线程的缺点:可能会带来安全隐患,也就是线程不安全
这时就需要线程同步来解决
单核CPU和多核CPU
并行与并发
并行:多个CPU同时执行多个任务。例:多个人做不同的事,每个人拿着自己的球搁那投篮
并发:一个CPU同时执行多个任务,例如:多个人做同一件事,打3v3篮球
使用多线程的优点
什么时候用到多线程:
1.程序需要同时执行2或多个任务
2.程序需要实现一些需要等待的任务,如用户输入,文件读写操作等
3.需要一些后台运行的程序时
线程的创建和使用:
PS:下图不是多线程
方式1:继承于Thread类
1.创建一个Thread的子类
2.重写Thread类中的run()方法-->将此线程执行的操作声明在run中
3.创建Thread类的子类的对象
4.通过此对象调用start()(1.启动当前线程2.调用当前线程的run方法(单独的调用run方法不叫创建线程))
例子:遍历100以内所有的偶数
具体流程是这样的:先执行主线程main方法,主线程中创建对象,再调用start方法启动副线程。若start下还有代码,则还是主线程进行执行,那么此时就会产生交互,主线程副线程一同执行,这就称之为多线程。
问题:
1.一个对象只能调用一次start
2.要想再启动一个线程,只能通过再new一个对象的方式并调用start方法
练习:创建两个分线程,其中一个遍历100以内的偶数,另一个遍历100以内的奇数
思路:由于两要求不同,因此需要创建2个子类
也可以这样子:通过匿名子类并重写run方法实现
Thread类的有关方法:
其他方法:
currentThread:静态方法,返回执行当前代码的线程(此方法很重要 )
yield:静态方法,使当前正在执行的线程向另一个线程交出运行权。
join:在线程a中调用线程b的join方法,此时线程a进入阻塞状态,直到线程b完成执行以后,线程a才结束阻塞状态。
stop:停止该线程。这个方法以及弃用
sleep(long millitime):让当前线程睡眠指定的millitime毫秒。在指定的millitime毫秒内,当前线程是阻塞状态
isAlive:判断当前线程是否还存活
线程的调度
setPriority:在start方法前就修改
说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着,只有当高线程的线程执行完后,低优先级的线程才执行
习题
创建3个窗口卖100张票
public class sold {//创建3个窗口卖100张票
public static void main(String[] args) {
window t1 = new window();
window t2 = new window();
window t3 = new window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class window extends Thread{
private static int ticket = 100;//设置为static,ticket才能被3个线程共用
@Override
public void run() {
while (true){
if(ticket>0){
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
}else {
break;
}
}
}
}
但是使用该方法时ticket需要加上static关键字才能共享数据,因此引出创建多线程的方式2
创建多线程的方式2:实现Runnable接口
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法:run
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start方法
下图为具体实现
public class threadtest2 {
public static void main(String[] args) {
Mthread mthread = new Mthread();
Thread t1 = new Thread(mthread);//利用构造器
t1.start();
}
}
class Mthread implements Runnable{//实现Runnable接口
@Override
public void run() {//重写run方法
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}
此时线程为t1而不是mthread
源码分析一下为何此时Thread调用的是Mthread中重写的run:
在Thread的源码中,构造器Thread(Runnable target)中的target若是重写了run方法,那么就调用target的run方法
在上上图中体现为:Thread调用的run方法其实是构造器中的参数Mthread中重写的run方法。
若是还想创建一个线程:
直接:Thread t2 = new Thread(Mthread);t2.start();即可
活学活用之使用上述方式完成卖票例题:
public class solds {
public static void main(String[] args) {
Mthread m = new Mthread();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Mthread implements Runnable{
private int ticket = 100;//这里不用加static的原因是三个线程共用这个Mthread
@Override
public void run() {
while (true){
if(ticket>0){
System.out.println(Thread.currentThread().getName() + "卖票,票号为" + ticket);
ticket--;
}else {
break;
}
}
}
}
两种创建方式的总结
实现接口的方式好一些
原因:1.第一种方式中,由于有些类可能要继承其他类,而java只支持单继承,因此会出现不得不使用接口的方式
2.上述例子中,说明实现的方式天然就能共享数据,而继承的方式只能由static才能共享数据
两者的联系:Thread本身也实现了Runnable接口
相同点:两种方式都要重写run方法,将线程要执行的逻辑声明在run方法中